Skip to content

Writing Commands and Compositions

learnfrc.com
learnfrc.comAuthor
Veer Bajaj
Veer BajajMaintainer

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.

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 immediately
Command stopCmd = Commands.runOnce(intake::stop, intake);
// Run until a condition is met
Command 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.

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), and deadline() (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.

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.

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.

  • 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.