Skip to content

Lesson 2.2: Commands

🎯 What You’ll Learn

By the end of this lesson you will be able to:

  • Explain what a command is and how it relates to subsystems
  • Describe the four lifecycle methods every command has
  • Read through AutoShootCommand and explain what each section does
  • Identify inline commands in RobotContainer and explain how they work
  • Understand command groups and how they combine simple commands into complex behaviors

The Mental Model: Command = Instruction

In Lesson 2.1 you learned that subsystems are capabilities β€” they answer β€œwhat can the robot do?” Now it’s time for the other half:

A command is an instruction that tells one or more subsystems what to do and when to do it.

Think of it like a restaurant kitchen:

  • Subsystems are the kitchen stations β€” the grill, the fryer, the prep counter. Each station has equipment and knows how to use it.
  • Commands are the orders β€” β€œmake a burger,” β€œfry the onion rings,” β€œplate the meal.” An order tells the stations what to do, in what sequence, and when to stop.

Without commands, subsystems just sit there doing nothing. Without subsystems, commands have nothing to control. They work together.


The Command Lifecycle

Every command in WPILib follows the same four-method lifecycle. When the command scheduler runs a command, it calls these methods in order:

MethodWhen It RunsWhat It Does
initialize()Once, when the command first startsSet up initial state β€” reset timers, zero out variables
execute()Every ~20ms while the command is runningDo the actual work β€” move motors, check sensors, make decisions
isFinished()After every execute() callReturn true to end the command, false to keep going
end(interrupted)Once, when the command stopsClean up β€” stop motors, reset state. The interrupted parameter tells you if the command was cancelled early

Here’s the flow as a timeline:

Command starts
β”‚
β–Ό
initialize() ← runs once
β”‚
β–Ό
β”Œβ”€β–Ί execute() ← runs every 20ms
β”‚ β”‚
β”‚ β–Ό
β”‚ isFinished()?
β”‚ β”‚
β”‚ β”œβ”€β”€ false β”€β”€β”€β”€β”€β”€β”€β”€β”˜ (loop back to execute)
β”‚ β”‚
β”‚ └── true
β”‚ β”‚
β”‚ β–Ό
β”‚ end(false) ← normal finish
β”‚
β”‚ OR if cancelled:
β”‚ end(true) ← interrupted

βœ…Checkpoint: Command Lifecycle
A command spins a motor for 2 seconds, then stops. Which lifecycle method would contain the timer check? Which method would stop the motor?
  • isFinished() would check if 2 seconds have passed and return true when the time is up.
  • end(interrupted) would stop the motor. This is the right place for cleanup because end() runs whether the command finishes normally OR gets cancelled early. If you stopped the motor in isFinished(), it wouldn’t stop if the command was interrupted!

A common pattern: initialize() starts a timer, execute() runs the motor, isFinished() checks the timer, and end() stops the motor.


Walking Through AutoShootCommand

Now let’s read a real command from your robot project. AutoShootCommand.java is the β€œsmart shoot” command that coordinates the turret, flywheels, and feeder to score a note. Open it up and follow along.

What does AutoShootCommand do?

Coordinates turret aiming, flywheel spin-up, and note feeding. It runs continuously (never finishes on its own) and handles everything:

  1. Aims the turret at the target, compensating for robot movement
  2. Calculates distance to the target
  3. Sets hood angle and flywheel speed based on that distance
  4. Feeds the note once the turret is aimed AND flywheels are at speed

The constructor β€” declaring dependencies

Look at the constructor first:

AutoShootCommand.java β€” Constructor
public AutoShootCommand(TurretSubsystem turret, ShooterSubsystem shooter,
CommandSwerveDrivetrain drivetrain, Supplier<Pose2d> targetSupplier,
Supplier<Boolean> trajectoryPassingSupplier, boolean allianceZoneOnly) {
this.turret = turret;
this.shooter = shooter;
this.drivetrain = drivetrain;
this.targetSupplier = targetSupplier;
this.trajectoryPassingSupplier = trajectoryPassingSupplier;
this.allianceZoneOnly = allianceZoneOnly;
addRequirements(turret, shooter);
}

Two important things to notice:

  1. The subsystems are passed in as parameters β€” the command doesn’t create them. This is called dependency injection. RobotContainer creates the subsystems and hands them to the command.

  2. addRequirements(turret, shooter) β€” this tells the command scheduler β€œI need exclusive access to the turret and shooter.” If another command tries to use the turret while AutoShootCommand is running, the scheduler will cancel one of them. This prevents two commands from fighting over the same motor.

initialize() β€” resetting state

AutoShootCommand.java β€” initialize()
@Override
public void initialize() {
feeding = false;
readyTimestamp = 0.0;
targetPose = targetSupplier.get();
}

Simple and clean. Reset the feeding flag, clear the timer, and grab the current target position. This runs once when the driver presses the Y button.

execute() β€” the main loop

The execute() method is where all the action happens. It runs every 20ms and follows a clear step-by-step sequence:

AutoShootCommand.java β€” execute() overview
@Override
public void execute() {
// Step 1: Update target (changes as robot crosses field zones)
targetPose = targetSupplier.get();
// Step 2: Aim the turret with velocity compensation
turret.aimAtPose(robotPose, targetPose, fieldSpeeds);
// Step 3: Calculate distance to target
double distance = robotPose.getTranslation().getDistance(compensatedTarget);
// Step 4: Set hood angle and flywheel speed based on distance
shooter.autoAim(distance);
// Step 5: When aimed AND flywheels ready, feed the note
if (aimed && flywheelsReady && /* other safety checks */) {
shooter.runFeeder(0.85);
}
}

Notice how the command coordinates multiple subsystems. It calls methods on both turret (aiming) and shooter (flywheels, feeder, hood) every single loop. That’s the power of commands β€” they orchestrate complex behaviors across subsystems.

The feed delay β€” a clever safety pattern

AutoShootCommand doesn’t feed the note the instant everything looks ready. It waits 0.3 seconds first:

AutoShootCommand.java β€” Feed delay
private static final double FEED_DELAY = 0.3;
// In execute():
if (aimed && flywheelsReady && !turretResetting && !inTrench && allowedToShoot) {
if (readyTimestamp == 0.0) {
readyTimestamp = Timer.getFPGATimestamp();
}
if (Timer.getFPGATimestamp() - readyTimestamp >= FEED_DELAY) {
shooter.runFeeder(0.85); // Fire!
feeding = true;
}
} else {
readyTimestamp = 0.0; // Reset timer if conditions change
}

Why? Because the β€œready” check might briefly flicker true before the flywheels are truly stable. The delay ensures we only feed when everything has been consistently ready for 0.3 seconds. This is a common pattern in FRC β€” debouncing a condition before acting on it.

isFinished() β€” runs forever

AutoShootCommand.java β€” isFinished()
@Override
public boolean isFinished() {
return false;
}

This command never finishes on its own. It runs until the driver presses Y again (toggle off) or until autonomous ends. The toggleOnTrue binding in RobotContainer handles the toggle behavior.

end() β€” cleanup

AutoShootCommand.java β€” end()
@Override
public void end(boolean interrupted) {
turret.stop();
shooter.stopAll();
shooter.setHoodPosition(0.0); // Flatten hood when not shooting
}

When the command ends (for any reason), it stops everything and flattens the hood. This is critical β€” without end(), the flywheels would keep spinning after you stop shooting!

Why does AutoShootCommand call addRequirements(turret, shooter) but NOT addRequirements(drivetrain)?


Inline Commands in RobotContainer

Not every command needs its own file. For simpler behaviors, WPILib provides inline command factories that let you create commands right inside RobotContainer.java. Let’s look at the most important ones.

The Intake Toggle (X Button)

This is the most complex inline command in the project. When the driver presses X, it deploys the intake, runs the rollers, and handles retraction when toggled off:

RobotContainer.java β€” X button intake toggle
controller.x()
.toggleOnTrue(
// Phase 1: Deploy out, wait, stop deploy, then run rollers
new SequentialCommandGroup(
new InstantCommand(() -> intakeSubsystem.deployOut(), intakeSubsystem),
new WaitCommand(0.3),
new InstantCommand(() -> intakeSubsystem.stopDeploy()),
new RunCommand(() -> intakeSubsystem.runIntake(0.5), intakeSubsystem)
).finallyDo(() -> {
// When cancelled: stop rollers and start retracting
intakeSubsystem.stopIntake();
intakeSubsystem.deployIn();
})
// Phase 2: After cancel, give deploy motor 0.3s to retract, then stop
.andThen(new WaitCommand(0.3))
.andThen(new InstantCommand(() -> intakeSubsystem.stopDeploy()))
);

Let’s break down the command types used here:

Command TypeWhat It Does
InstantCommandRuns a single action once and immediately finishes β€” like flipping a switch
WaitCommandDoes nothing for a specified number of seconds, then finishes
RunCommandRuns an action every 20ms and never finishes β€” keeps going until cancelled
SequentialCommandGroupRuns commands one after another, in order

The toggleOnTrue binding means: press X once to start the command, press X again to cancel it. When cancelled, the finallyDo() block runs the cleanup (stop rollers, retract intake).

In the intake toggle, why is RunCommand used for the rollers instead of InstantCommand?

The Shoot Toggle (Y Button)

The Y button uses the AutoShootCommand class we just studied:

RobotContainer.java β€” Y button shoot toggle
controller.y()
.toggleOnTrue(new AutoShootCommand(
turretSubsystem, shooterSubsystem, drivetrain,
this::getSmartTarget, this::isTrajectoryPassingEnabled, false));

Notice how RobotContainer passes the subsystems and configuration to the command. The this::getSmartTarget syntax is a method reference β€” it passes the getSmartTarget() method itself so AutoShootCommand can call it every loop to get the latest target.

Named Commands for Autonomous

RobotContainer also registers named commands that PathPlanner autonomous routines can trigger:

RobotContainer.java β€” Named commands for auto
NamedCommands.registerCommand("AutoShoot",
new AutoShootCommand(turretSubsystem, shooterSubsystem, drivetrain,
this::getAllianceHub, () -> false, true));
NamedCommands.registerCommand("Intake", Commands.run(() -> {
intakeSubsystem.deployOut();
intakeSubsystem.runIntake(0.35);
}, intakeSubsystem));
NamedCommands.registerCommand("StopShooter", Commands.runOnce(() -> {
shooterSubsystem.stopAll();
shooterSubsystem.setHoodPosition(0.0);
}, shooterSubsystem));

These are the same subsystem methods you learned about in Lesson 2.1, just wrapped in commands and given names that PathPlanner can reference. Commands.run() creates a RunCommand (runs every 20ms), and Commands.runOnce() creates an InstantCommand (runs once).


βœ…Checkpoint: Inline Commands
Look at the X button intake toggle in RobotContainer. What happens in this exact order when the driver presses X? List the sequence of actions.

When the driver presses X, this sequence runs:

  1. deployOut() β€” the deploy motors swing the intake outward (InstantCommand, runs once)
  2. Wait 0.3 seconds β€” gives the deploy mechanism time to fully extend (WaitCommand)
  3. stopDeploy() β€” stops the deploy motors since the intake is now out (InstantCommand)
  4. runIntake(0.5) β€” rollers start spinning at 50% to grab notes (RunCommand, runs continuously)

When the driver presses X again (toggle off), the cleanup runs:

  1. stopIntake() β€” rollers stop spinning
  2. deployIn() β€” deploy motors retract the intake
  3. Wait 0.3 seconds β€” gives the mechanism time to retract
  4. stopDeploy() β€” stops the deploy motors

The SequentialCommandGroup ensures steps 1–4 happen in order, and finallyDo() ensures the cleanup always runs when the command ends.


Command Groups: Combining Commands

You’ve already seen SequentialCommandGroup in the intake toggle. WPILib provides several ways to combine commands into more complex behaviors:

SequentialCommandGroup β€” One After Another

Commands run in order. The next command starts only after the previous one finishes.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Deploy │───►│ Wait 0.3s│───►│ Run β”‚
β”‚ Out β”‚ β”‚ β”‚ β”‚ Rollers β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

This is what the intake toggle uses β€” deploy first, wait, then run rollers.

ParallelCommandGroup β€” All At Once

All commands start at the same time. The group finishes when all commands have finished.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Aim Turret │──┐
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”œβ”€β”€β–Ί Group finishes when ALL are done
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ Spin Flywheels β”‚β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Use this when you want multiple things happening simultaneously β€” like aiming the turret while spinning up the flywheels.

ParallelRaceGroup β€” First One Wins

All commands start at the same time. The group finishes when any one command finishes, and the rest are cancelled.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Run Intake │──┐
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”œβ”€β”€β–Ί Group finishes when FIRST one ends
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ Wait 3 seconds β”‚β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

This is useful for timeouts β€” β€œrun the intake, but stop after 3 seconds no matter what.”

Chaining with .andThen() and .alongWith()

Instead of creating group objects, you can chain commands with methods:

// Sequential: do A, then B
commandA.andThen(commandB)
// Parallel: do A and B at the same time
commandA.alongWith(commandB)

The intake toggle uses .andThen() to chain the retraction sequence after the main command ends.

You want the robot to deploy the intake AND spin up the flywheels at the same time, then feed the note after both are ready. Which combination would you use?


Default Commands β€” What Happens When Nothing Else Is Running

Every subsystem can have a default command β€” a command that runs whenever no other command is using that subsystem. RobotContainer sets these up:

RobotContainer.java β€” Default commands
// Turret default: always auto-aim at the smart target
turretSubsystem.setDefaultCommand(new RunCommand(
() -> turretSubsystem.aimAtPose(drivetrain.getState().Pose, getSmartTarget(),
drivetrain.getState().Speeds),
turretSubsystem));
// Drivetrain default: drive using joystick input
drivetrain.setDefaultCommand(
drivetrain.applyRequest(() -> drive
.withVelocityX(/* joystick input */)
.withVelocityY(/* joystick input */)
.withRotationalRate(/* joystick input */)));

The turret’s default command keeps it constantly aimed at the target β€” even when the driver isn’t shooting. The drivetrain’s default command reads the joystick and drives. These run in the background, all the time, unless a higher-priority command takes over.


How Commands and Subsystems Work Together β€” The Big Picture

Let’s zoom out and see how everything connects:

  1. Subsystems declare hardware and expose methods (runIntake(), aimAtPose(), etc.)
  2. Commands call those methods in a structured lifecycle (initialize β†’ execute β†’ isFinished β†’ end)
  3. RobotContainer creates both subsystems and commands, then wires buttons to commands
  4. The Command Scheduler runs the whole show β€” it calls execute() on active commands every 20ms and manages which command gets each subsystem
Driver presses Y button
β”‚
β–Ό
RobotContainer: controller.y().toggleOnTrue(AutoShootCommand)
β”‚
β–Ό
Command Scheduler starts AutoShootCommand
β”‚
β–Ό
AutoShootCommand.initialize() β€” reset state
β”‚
β–Ό
Every 20ms: AutoShootCommand.execute()
β”œβ”€β”€ turret.aimAtPose() ← calls TurretSubsystem method
β”œβ”€β”€ shooter.autoAim() ← calls ShooterSubsystem method
└── shooter.runFeeder() ← calls ShooterSubsystem method
β”‚
β–Ό
Driver presses Y again β†’ command cancelled
β”‚
β–Ό
AutoShootCommand.end(true) β€” stop turret, stop shooter, flatten hood

πŸ› οΈ Try It: Modify a Named Command

This is the first lesson where you get to write code! Here’s a small, safe modification to try.

Look at the β€œStopShooter” named command in RobotContainer:

RobotContainer.java β€” StopShooter named command
NamedCommands.registerCommand("StopShooter", Commands.runOnce(() -> {
shooterSubsystem.stopAll();
shooterSubsystem.setHoodPosition(0.0);
}, shooterSubsystem));

Your task: Add a System.out.println() statement so you can see in the console when this command runs during autonomous. Add it right before stopAll():

System.out.println(">>> AUTO: Stopping shooter <<<");

This is a safe change β€” it only adds a log message and doesn’t affect any motor behavior. After making the change, build the project to make sure it compiles. You’ll learn more about safe edits like this in Lesson 3.1.


Quick Reference: Command Types

TypeBehaviorExample Use
InstantCommandRuns once, finishes immediatelyToggle a flag, set a motor speed
RunCommandRuns every 20ms, never finishesJoystick driving, continuous intake
WaitCommandWaits N seconds, then finishesDelays between actions
SequentialCommandGroupRuns commands in orderDeploy β†’ wait β†’ run rollers
ParallelCommandGroupRuns commands simultaneously, finishes when all doneAim + spin up flywheels
ParallelRaceGroupRuns commands simultaneously, finishes when first one doneRun intake with a timeout
Custom command classFull lifecycle controlAutoShootCommand

What’s Next?

You now understand both halves of the robot’s architecture: subsystems (capabilities) and commands (instructions). In Lesson 2.3: RobotContainer and Bindings, you’ll see how RobotContainer ties everything together β€” creating subsystems, wiring buttons to commands, and setting up autonomous routines. You’ll also trace the complete path from a button press to a motor output.