Skip to content

Commands

Commands form the backbone of our robot code. They are not strictly required for a robot to function, but the command-based framework gives us a clear, testable structure. Commands must follow a specific pattern so the scheduler can run them correctly.

Why Commands?

The command-based model (WPILib) separates robot functionality into Subsystems and provides an elegant, declarative way to tell those subsystems what to do. This separation:

  • Encourages single responsibility (each subsystem owns its hardware + core behaviors).
  • Prevents conflicting access (requirements ensure only one command at a time controls a subsystem).
  • Makes complex autonomous routines easier to build by composing simple commands.

Structure of a Command

class ExampleCommand(private val foo: Boolean = false) : Command() { // Parameters (like 'foo') can customize behavior
    init {
        addRequirements(ExampleSubsystem)
        // Requirements declare which subsystem(s) this command needs exclusive control over.
        // Don't put startup logic here; use initialize() so it runs each time the command is scheduled.
    }

    override fun initialize() {
        // Runs once when the command is first scheduled.
    }

    override fun execute() {
        // Runs every scheduler tick while the command is active and not finished/canceled.
    }

    // Polled every tick. Use a code block if you need multiple lines; a single-expression body works for simple checks.
    override fun isFinished() = ExampleSubsystem.condition

    override fun end(interrupted: Boolean) { // interrupted == true if the command was canceled before finishing normally
        // Runs once after isFinished() returns true OR the command is canceled/interrupted.
    }
}

Key lifecycle points:

  • initialize(): one-time setup when scheduled.
  • execute(): repeated action while active.
  • isFinished(): return true to end the command normally.
  • end(interrupted): cleanup; interrupted tells you whether it ended early.

Scheduling and Canceling

Scheduling a command starts its lifecycle:

ExampleCommand().schedule()
If you schedule two commands that require the same subsystem in the same block:
ExampleCommand(false).schedule()
ExampleCommand(true).schedule()
The second one wins. Because both require the same subsystem, the first is immediately interrupted when the second is scheduled.

Command Groups

Groups let you compose multiple commands into higher-level behaviors. You pass other commands as parameters and the group itself behaves like a single Command instance.

Command Group Types

  • ParallelCommandGroup(CmdOne(), CmdTwo()) runs both simultaneously and finishes when all included commands finish.
  • SequentialCommandGroup(CmdOne(), CmdTwo()) runs CmdOne() then CmdTwo(); it finishes when the last command finishes.
  • ParallelRaceGroup(CmdOne(), CmdTwo()) runs commands in parallel and finishes when any one finishes (cancels the rest).
  • ParallelDeadlineGroup(deadlineCmd, otherCmd) runs all in parallel; when the first (deadline) command finishes, it cancels the others and ends.
  • InstantCommand { /* code */ } (not a group) runs the given lambda once and finishes immediately—useful for quick side effects (e.g., zeroing sensors, toggling flags).

Important Notes

  • Always ensure the commands inside a group don't require conflicting subsystems unless the group structure implies handoff (the scheduler still enforces requirements).
  • You can pass more than two commands; examples here use two for brevity.

Nesting

Because every group returns a Command object, you can nest them to build complex routines:

SequentialCommandGroup(
    InstantCommand { doThing() },
    ParallelCommandGroup(
        InstantCommand { doOtherThing() },
        ParallelRaceGroup(
            InstantCommand { doFirstRaceThing() },
            InstantCommand { doSecondRaceThing() }
        )
    ),
    InstantCommand { finalizeThing() }
)