Writing Commands and Compositions
What a command is
Section titled “What a command is”A command has four lifecycle pieces: initialize() (once at start), execute() (every loop), isFinished() (returns true to end), and end(boolean interrupted) (cleanup). A command runs from when it’s scheduled until it finishes or is interrupted.
You can write a full command class, but modern WPILib strongly encourages lambdas and factory methods instead — they cover almost every use case with far less code.
Inline commands with factories
Section titled “Inline commands with factories”The Commands utility class and subsystem helper methods build commands inline:
// Run the intake until the button is released (runEnd: run, then stop)Command intakeCmd = intake.runEnd(intake::run, intake::stop);
// A command that does one thing and finishes immediatelyCommand stopCmd = Commands.runOnce(intake::stop, intake);
// Run until a condition is metCommand raiseCmd = arm.run(arm::raise).until(arm::atTop);Common building blocks:
run(...)— runs an action every loop (never finishes on its own).runOnce(...)— runs once and ends.runEnd(run, end)— runs an action, then runs an end action when it stops..until(condition)— adds an end condition to any command..withTimeout(seconds)— ends after a time limit.
Using the subsystem’s own run/runEnd factory methods automatically adds that subsystem as a requirement.
Compositions: building bigger behavior
Section titled “Compositions: building bigger behavior”Commands are composable — you combine small commands into bigger ones. The main compositions:
-
Sequential — run commands one after another:
Command auto = Commands.sequence(drivetrain.driveForward(2.0),arm.raiseToScore(),intake.eject()); -
Parallel — run commands at the same time. Variants include
parallel()(waits for all),race()(ends when the first ends), anddeadline()(ends when one specific “deadline” command ends). -
Decorators —
.andThen(...),.alongWith(...),.raceWith(...)chain commands fluently:arm.raiseToScore().andThen(intake.eject());
Because a composition is itself a command, you can nest compositions inside compositions — a full autonomous routine is just one big command.
A practical pattern
Section titled “A practical pattern”Keep command factories as methods on the subsystem that returns them:
public class Intake extends SubsystemBase { public Command intakeCommand() { return runEnd(this::run, this::stop); }}Then wiring becomes a one-liner: driver.a().whileTrue(intake.intakeCommand()); (covered next). This keeps behavior close to the hardware it controls and keeps RobotContainer readable.
When to write a class
Section titled “When to write a class”Write a dedicated command class only when logic is genuinely complex (lots of state, multiple phases) and a lambda would be unreadable. For most actions, inline factories are cleaner and less error-prone.
Key takeaways
Section titled “Key takeaways”- A command has initialize / execute / isFinished / end; most are built with lambdas instead.
- Use factory methods (run, runOnce, runEnd) and decorators (.until, .withTimeout, .andThen).
- Compositions (sequence, parallel, race, deadline) combine commands into bigger behavior.
- Subsystem factory methods auto-add the subsystem as a requirement.
- Write a full command class only for genuinely complex, multi-phase logic.
This lesson was adapted from learnfrc.com.
