Heads up: WPILib 2027 is still in alpha. These pages are changing quickly and aren't stable or finished yet — expect edits as the APIs settle.
Gray Matter LogoGray Matter Workshop

Commands

KEY CONCEPT

Commands with WPILib Commands V3

A command is what a mechanism can do: a factory method that returns a named Command the scheduler can run. Anything that wants to move the arm does it through a command; the setters stay private, which is how the scheduler prevents two things fighting over the same motor.

On this team almost every mechanism command is a hold: it keeps re-sending its closed-loop setpoint forever, so the motor stays actively commanded until another command takes the mechanism over. Hold a button (whileTrue), the arm goes to the angle and stays there; release it, and the default command comes back.

↳ TAKEAWAY

A command is a named action from a mechanism factory. Most of ours are holds — and a hold never finishes.

The default command shape: a hold

runRepeatedly(...)re-runs its body every scheduler tick, so the closed-loop request is re-sent forever. That's the whole recipe: one line of setup, re-sent repeatedly, named with a (hold) suffix. This is how every preset on the robot is written.

THE ONE RULE

A hold never finishes, so nothing may ever wait on a hold

A hold has no finish line, so a sequence that contains a bare hold sticks on it forever. Every hold is named with (hold) so you can catch this: if a stuck routine is sitting on a (hold)command on the dashboard or in the log, that's the bug.

NOTE · NO …AndWait METHODS

Waiting happens at the call site, not in the factory

Mechanisms never bake waiting into their commands: there is no scoringAndWait(). When a chain needs the hold to end, you give it a finish line where you use it: arm.scoring().until(arm::isAtTarget). One factory per preset, and the caller decides whether to wait.

Chaining: routines out of holds

Routines that touch more than one mechanism are built where they're used (in an OpMode) by chaining the mechanisms' commands. Three tools, in order: Command.sequence for steps that finish on their own, .until(...) to give a hold a finish line, and Command.race(step, hold)for "do this step WHILE holding." Add .withTimeout(...) as the seatbelt on any step that waits on a sensor condition.

A race ends when its first member finishes and cancels the rest. And since a hold never finishes, the step is always what decides. The full drive-stow-drive version of this pattern lives in the template:

DriveStowDriveChainedOpMode.java — the chaining reference

When a command should finish on its own

Not everything is a hold. A step with its own natural ending (drive this distance, run the roller until the beam break trips) can be a self-finishing command. The workshop template ships utils/ClassicCommand, a small base class with the explicit initialize / execute / isFinished / end lifecycle. Extend it, override what you need, and the instance is a Command. You'll see it again in DriveToPoint.

Because it finishes on its own, a step like this can sit in a Command.sequence as-is (no .until(...) needed). That's the dividing line: holds get finish lines at the call site; steps bring their own.

Cancellation

A hold only ever ends by being cancelled: the driver releases a whileTrue button, a race's step finishes, an .until(...)condition trips. Usually that's fine as-is: the motor keeps its last closed-loop request in firmware until the next command sends a new one. When a command does need cleanup on interruption (stop the rollers, zero a voltage), that goes in a .whenCanceled(...) hook on the builder.

The whenCanceledcallback fires only when the command is interrupted, which for a hold is the only way it ends, so it's effectively the hold's "on the way out" hook.

The advanced dialect: coroutines (optional)

v3 commands can also be written as a single body that pauses itself from the inside: run(coroutine -> { ... }) with coroutine.await(command), fork(command), and waitUntil(condition). Reach for it only when a hold must span many steps or the logic needs loops and branches; you won't need it in this workshop. The template keeps a worked example of the same drive-stow-drive routine in that dialect:

DriveStowDriveOpMode.java — the coroutine dialect (optional)
NOTE · API STATUS

This is the WPILib 2027 alpha

runRepeatedly, the staged builder, and the compile-time enforcement of .named(...) run on Java 25 and deploy to SystemCore. The stack is the WPILib 2027 alpha (GradleRIO 2027.0.0-alpha-6), so the exact APIs are still moving between alpha builds. This page was last verified against alpha-6 in July 2026.
CHECKPOINT · 5 ITEMS