Gray Matter
WorkshopThe Command Framework
WPILib 2027 is still in alpha: these pages change as the APIs settle.
LESSON 09

The Command Framework

Robot code is built using three main concepts: triggers, mechanisms, and commands. Underneath them a scheduler runs fifty times a second and settles which command owns which motor.

9 minutes
You’ll need
  • The vocabulary from Java Basics: class, field, method, constructor, lambda, method reference.
When
Triggers
A controller button, a sensor reading, a comparison you wrote yourself. Attach a command to a trigger and the scheduler watches it for you.
What
Mechanisms
The arm. The flywheel. The drivetrain. Each one is a class that extends Mechanism, with its motors and sensors as private fields and its configuration done once, in the constructor.
How
Commands
Named actions on a mechanism. Almost every command in Workshop 3 is a hold: it re-sends the same request and never ends by itself.

The scheduler loop

None of the three does anything on its own. robotPeriodic() is called for you for as long as the robot has power. The gap between calls is 20 milliseconds, so it runs 50 times a second.

Robot.java: the one call that runs the scheduler
public class Robot extends OpModeRobot {
@Override
public void robotPeriodic() {
Scheduler.getDefault().run();
}
}

One call does the whole job. It checks every trigger, starts and cancels commands from what it finds, and runs any background code you register.

One command per mechanism

A command declares which mechanisms it needs. The command from arm.runFast() needs the arm, and while it runs, it owns the arm. No other command touches that motor at the same time.

Priorities are new in Commands v3. A second command takes a mechanism only if its priority is the same or higher than the command already holding it. Every command in this workshop carries the same priority, so a new one always gets to run.

Canceling is not stopping

A mechanism nothing has claimed runs its default command, which is the built-in idle() unless you set another. Idle has the lowest priority, so anything can take the mechanism from it, and it sends nothing at all to the motor.

Read that last part twice. Idle does not switch the motor off. Phoenix keeps applying whatever request it was last given, so canceling a command does not stop hardware. Writing Commands deals with that.

The arm and flywheel rarely reach idle in this workshop. A command with no finish condition keeps its mechanism, and every binding here replaces one such command with another.

Where bindings live

A mechanism owns hardware. An OpMode decides what the robot does during one part of the match. That means which buttons do what while a driver is in control, or which routine runs in autonomous.

The mechanisms sit on the same Robot.java as that scheduler call.

Robot.java: the same file, with the mechanisms added
public class Robot extends OpModeRobot {
// The robot's mechanisms. Public so OpModes can use them.
public final Arm arm = new Arm();
public final Flywheel flywheel = new Flywheel();
 
@Override
public void robotPeriodic() {
Scheduler.getDefault().run();
}
}

Each mode is a separate class marked @Teleop, @Autonomous or @Utility. That marking is how it shows up in the list on the driver station. Every mode is handed the Robot, so it can reach the arm and the flywheel.

MyTeleop.java: the shape of a mode class
@Teleop(name = "Teleop")
public class MyTeleop extends PeriodicOpMode {
private final CommandNiDsXboxController driver = new CommandNiDsXboxController(0);
 
public MyTeleop(Robot robot) {
// Left trigger: push the arm up while held, stop when released.
driver.leftTrigger().whileTrue(robot.arm.runFast()).whileFalse(robot.arm.stop());
}
}

The bindings live in the constructor. The framework builds this class when someone picks Teleop, and throws it away on a mode switch. The bindings go with it, so no binding from auto ever fires during teleop.

Commands with no finish condition

Almost every command in this workshop is built with runRepeatedly(...). The body runs every loop. If you have seen a while loop before, this is one. Each pass runs the body, then waits for the next loop, 20 milliseconds later.

Nothing inside it decides when to stop, so ending it is somebody else's job. Releasing the trigger is what does it above: whileTrue cancels the command on the way down, and whileFalse schedules arm.stop() in its place.

The reason to write commands this way is visibility. A command that keeps running stays the command on its mechanism, so a log always names what has the arm right now.

Here is a real one, from the arm you build two lessons from now.

Arm.java: one command, from the arm you build later
/** Push the arm with a stronger voltage and keep pushing. Never finishes. */
public Command runFast() {
return runRepeatedly(() -> setVoltage(6.0)).named("runFast (hold)");
}

runRepeatedly re-runs setVoltage every loop, so the six-volt request never goes stale. Every command on this site built that way carries the (hold) suffix, which is a promise from whoever wrote it: this command has no ending.

Six volts is a push, not a position. The arm ends up wherever gravity and friction let it.

Check yourself

Robot.java calls Scheduler.getDefault().run() inside robotPeriodic(). What breaks if you delete that line?

One command has the arm. A button fires and a second command that also needs the arm gets scheduled. Both have the ordinary priority. Who ends up with the arm?

A routine has been stuck for eight seconds, and the command it is sitting on is named "runFast (hold)". What does that name tell you?

No command is claiming the flywheel. What is the mechanism doing?

Pick an answer for each.