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:
If you schedule two commands that require the same subsystem in the same block: 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())runsCmdOne()thenCmdTwo(); 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: