Commands
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.
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.
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.
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 referenceWhen 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:
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.