Skip to content
IncredibotsAcademy
Incredibots playbook

Understanding Commands

Learn how to write Commands that tell your Subsystems what to do, when to stop, and how to combine them.

25 minrookie

By the end you can

  • Understand the lifecycle of a Command.
  • Learn how to link a Command to a Subsystem using addRequirements().
  • Understand Command Groups (Sequential and Parallel).

You have written an IntakeSubsystem that knows how to spin a motor. But how does the robot know when to spin the motor?

That is the job of a Command. In a Command-Based architecture, a Command is a self-contained action that the robot performs. Commands are sent to the Command Scheduler, which runs them.

If you want to read the official documentation, check out the FTCLib Command Docs.

The Command Lifecycle

Every command goes through a specific lifecycle, controlled by four main methods. The Command Scheduler automatically calls these methods for you:

  1. initialize(): Runs exactly once when the command first starts. Useful for resetting timers or sensor values.
  2. execute(): Runs repeatedly (every frame) as long as the command is active. This is where you tell the subsystem to move!
  3. isFinished(): Runs repeatedly. If it returns true, the scheduler stops the command. If it returns false, the command keeps running.
  4. end(boolean interrupted): Runs exactly once when the command finishes normally or is interrupted by another command. You almost always use this to turn off the motors!

How Commands and Subsystems Work Together

The most important part of a Command is declaring its Requirements.

When you write a Command, you must explicitly tell the Scheduler which Subsystems it uses by calling addRequirements(subsystem).

If Command A is running the Intake, and you suddenly press a button to start Command B (which also uses the Intake), the Scheduler sees they both require the same subsystem. The Scheduler will automatically interrupt Command A (calling its end(true) method) and start Command B. This prevents two pieces of code from fighting over the same motor!

Creating Your First Command

Let's write a simple command that turns the intake on. This command will run the intake inward as long as it is active, and stop the intake when it finishes.

  1. In Android Studio, right-click the teamcode folder and navigate to New > Package. Name it commands.
  2. Right-click the new commands folder and select New > Java Class.
  3. Name the class RunIntakeCommand.

Here is the code for our RunIntakeCommand in depth:

package org.firstinspires.ftc.teamcode.commands;
 
// Import FTCLib's CommandBase, which gives us the lifecycle methods
import com.arcrobotics.ftclib.command.CommandBase;
 
// Import the Subsystem we wrote earlier!
import org.firstinspires.ftc.teamcode.subsystems.IntakeSubsystem;
 
public class RunIntakeCommand extends CommandBase {
 
    // Store a reference to the subsystem so we can use it throughout this class
    private final IntakeSubsystem intake;
 
    // =========================================================
    // 1. THE CONSTRUCTOR
    // =========================================================
    // We pass the IntakeSubsystem in from the outside when we create this command.
    public RunIntakeCommand(IntakeSubsystem intake) {
        this.intake = intake;
        
        // CRITICAL STEP: We MUST tell the Scheduler that this command 
        // takes control of the IntakeSubsystem.
        // If we forget this, two commands could control the intake simultaneously
        // and cause the robot to crash or twitch!
        addRequirements(this.intake);
    }
 
    // =========================================================
    // 2. INITIALIZE (Runs ONCE)
    // =========================================================
    @Override
    public void initialize() {
        // Nothing special needed when the intake starts.
        // If this were a command to drive a specific distance, we might
        // reset the wheel encoders to zero right here!
    }
 
    // =========================================================
    // 3. EXECUTE (Runs REPEATEDLY)
    // =========================================================
    @Override
    public void execute() {
        // Tell the subsystem to spin the motor inward!
        // Because execute() runs repeatedly (many times a second),
        // the motor will continually receive the command to stay on.
        intake.runIntakeIn();
    }
 
    // =========================================================
    // 4. IS FINISHED (Runs REPEATEDLY)
    // =========================================================
    @Override
    public boolean isFinished() {
        // Return false so the command NEVER finishes on its own.
        // It will only stop when the Command Scheduler manually interrupts it.
        // (For example, when the driver lets go of the button on the controller).
        return false; 
    }
 
    // =========================================================
    // 5. END (Runs ONCE)
    // =========================================================
    // The 'interrupted' boolean is true if another command stole the subsystem,
    // or false if this command finished normally (if isFinished returned true).
    @Override
    public void end(boolean interrupted) {
        // When the command ends for ANY reason, stop the motor!
        // This is the most critical safety feature of Command-Based programming.
        intake.stopIntake();
    }
}

Command Groups: Combining Commands

Once you have basic commands written, FTCLib allows you to combine them into Command Groups to create complex routines (especially for Autonomous mode!).

SequentialCommandGroup

Runs commands one after another. Command 2 will not start until Command 1's isFinished() returns true.

new SequentialCommandGroup(
    // 1. Drive forward 50 inches
    new DriveToPositionCommand(driveSubsystem, 50),
    
    // 2. Wait until step 1 is done, then spin intake for 2 seconds
    new RunIntakeCommand(intakeSubsystem).withTimeout(2000), 
    
    // 3. Wait until step 2 is done, then drive back to 0
    new DriveToPositionCommand(driveSubsystem, 0)
)

ParallelCommandGroup

Runs multiple commands at the exact same time. It finishes when all the commands inside it have finished.

new ParallelCommandGroup(
    // Both of these happen simultaneously!
    // The robot will drive while spinning up the shooter flywheel.
    new DriveToPositionCommand(driveSubsystem, 50),
    new SpinUpShooterCommand(shooterSubsystem)
)

Check Your Understanding

Check yourself

What is the purpose of calling 'addRequirements()' in the constructor of a command?