Why Command-Based? Subsystems and the Scheduler
The two core ideas
Section titled “The two core ideas”The command-based framework organizes code around two abstractions:
- Subsystems — a collection of hardware that works as a unit (a drivetrain, an arm, an intake). The subsystem owns its motors and sensors and exposes clean public methods like
setSpeed()orgetAngle(). The rest of the code can’t touch the motors directly — only through the subsystem. - Commands — actions the robot performs (drive forward, raise the arm, run the intake). Commands use subsystems to do their work.
This separation keeps your code organized: hardware details stay inside subsystems, and behavior lives in commands.
Creating a subsystem
Section titled “Creating a subsystem”The recommended way is to subclass SubsystemBase (Java/C++):
public class Intake extends SubsystemBase { private final PWMSparkMax m_motor = new PWMSparkMax(5);
public void run() { m_motor.set(0.8); } public void stop() { m_motor.set(0.0); }
@Override public void periodic() { // Called automatically every 20 ms by the scheduler }}A subsystem’s periodic() is run automatically by the scheduler — perfect for logging sensor values or running closed-loop updates.
The CommandScheduler
Section titled “The CommandScheduler”The CommandScheduler is a singleton that runs the whole system. You start it by calling CommandScheduler.getInstance().run() once per loop — and the Command Robot template does this for you inrobotPeriodic(). Each loop, the scheduler:
- Runs each subsystem’s
periodic(). - Polls triggers/button bindings.
- Runs the currently scheduled commands.
- Handles commands that have finished or been interrupted.
Requirements: the resource-management rule
Section titled “Requirements: the resource-management rule”The single most important rule: each command declares which subsystems it requires. The scheduler guarantees that no two commands require the same subsystem at the same time. If a new command needs a subsystem already in use, the old command is interrupted (by default). This prevents two pieces of code from fighting over the same motor — a classic source of bugs.
Default commands
Section titled “Default commands”A subsystem can have a default command that runs whenever no other command requires it. The classic example: the drivetrain’s default command reads the joysticks and drives the robot, so the robot is always drivable unless something else (like an auto-aim command) takes over.
drivetrain.setDefaultCommand( drivetrain.run(() -> drivetrain.arcadeDrive(driver.getLeftY(), driver.getRightX())));Why this scales
Section titled “Why this scales”With subsystems + the scheduler + requirements, complex robots stay manageable: each mechanism is isolated, conflicts are impossible by design, and behavior is composed from small, reusable commands. This is why command-based is the recommended structure for nearly all teams.
Key takeaways
Section titled “Key takeaways”- Subsystems encapsulate hardware; commands encapsulate behavior.
- Subclass SubsystemBase to create a subsystem; its periodic() runs automatically.
- The CommandScheduler runs everything; the Command Robot template calls it in robotPeriodic().
- Each command declares required subsystems, so two commands can never fight over one subsystem.
- Default commands run when nothing else needs the subsystem — e.g., joystick driving.
This lesson was adapted from learnfrc.com.
