Unit 6 · Lesson 1

Why Command-Based? How Elite Teams Structure Code

Unit 5 taught you what a robot can do. Unit 6 teaches you how to organize it so that three programmers can work simultaneously, mechanisms can't conflict, autonomous routines are composable, and bugs are diagnosable in a ten-minute pit window. This is the architecture 2910 and every championship-level team uses.

By the end of this lesson, you will:

  • Articulate the specific failure modes of a large Robot.java as robot complexity grows
  • Explain the IS vs. DOES philosophy — Subsystems vs. Commands
  • Identify the three key players in Command-Based: Subsystem, Command, and RobotContainer
  • Describe the single responsibility of each player and what it must never do
  • Navigate a Command-Based project's file structure and identify where different code belongs

Why TimedRobot Doesn't Scale

In Unit 5 you wrote everything in Robot.java. For a drivetrain and an intake, that was manageable. Now imagine a competitive robot: drivetrain, intake, shooter, climber, LED system, vision processing, and four autonomous routines. All in one file, all in teleopPeriodic().

Three problems emerge immediately:

  • Conflicts. Two buttons can command the same motor simultaneously. There's no framework to prevent it — just whoever runs last wins.
  • Untestability. You cannot test the shooter without the drivetrain code running. You can't verify the climber logic in isolation. Everything is tangled.
  • Parallelism. What if you need the arm to move while the drivetrain drives forward? In a single teleopPeriodic() loop you have to build a manual state machine for each combination. It becomes a state machine managing state machines.
❌ TimedRobot at competition scale
// Robot.java — one file, 800 lines
public void teleopPeriodic() {
  // drivetrain logic...
  // intake logic...
  // shooter logic...
  // climber logic...
  // vision logic...
  // conflict checks?? who runs first?
  // manual parallel state tracking...
  // 3am. build season. you quit.
}
✅ Command-Based at competition scale
// RobotContainer.java
// Wire once. Scale forever.

// DriveSubsystem.java — 60 lines
// IntakeSubsystem.java — 45 lines
// ShooterSubsystem.java — 55 lines
// DriveWithJoysticks.java — 30 lines
// IntakeGamePiece.java — 25 lines
// ScoreNote.java — 20 lines

// Each file: one job. Testable alone.
// Conflicts: impossible by design.
🔍 LRI Perspective: "I can read a team's architecture in thirty seconds"

When I walk into a pit and ask to see the code, I look at the project structure before I read a single method. If I see one Robot.java file with 600+ lines, I know that team is spending build season fighting their own code instead of improving the robot. If I see a subsystems/ folder, a commands/ folder, and a lean RobotContainer.java, I know that team can add a mechanism in two hours, not two days. The architecture is a choice made in Week 1 that affects every week that follows.

The Core Philosophy: IS vs. DOES

Command-Based forces you to think about your robot in exactly two ways — and this separation is everything.

  • Subsystems are what the robot IS. The drivetrain, the intake, the arm. Physical mechanisms. Each one is a class that owns its hardware objects and provides methods to operate them. A subsystem does not know about joysticks, buttons, or game strategy. It only knows how to operate its own hardware.
  • Commands are what the robot DOES. Drive with joysticks. Intake a game piece. Move the arm to 90°. Each command is a single, named action that uses one or more subsystems. A command knows about game strategy, timing, and sequencing. It does not own any hardware objects directly.

This boundary is the central design rule of Command-Based. Crossing it — putting joystick reads in a subsystem, or motor declarations in a command — is the most common architecture mistake beginners make.

The Three Key Players

Every Command-Based project has three categories of code. Click each card to understand the role and, crucially, the rule it must follow.

👇 Click each card to flip it
⚙️
Subsystem
tap to learn more
What the robot IS

Owns hardware objects. Exposes simple methods (setSpeed(), getDistance()). Never reads joystick inputs directly. Never decides when to run. A subsystem is a puppet — it only acts when a command tells it to.

📋
Command
tap to learn more
What the robot DOES

Uses one or more subsystems to accomplish a goal. Declares which subsystems it requires, preventing conflicts. Never holds a motor controller object directly. Never knows which button triggered it — that's RobotContainer's job.

🔌
RobotContainer
tap to learn more
The wiring diagram

Creates all subsystem instances. Creates all controller objects. Binds commands to triggers. Nothing more. RobotContainer is the single place where "driver presses A" meets "intake runs." It's a wiring diagram in code, not a logic file.

The Project File Structure

A Command-Based project has a predictable layout. Understanding it means you always know exactly where to look for any given piece of logic. Click each tab below to see what lives there.

frc/robot/RobotContainer.java
The wiring diagram. One file, kept deliberately short. Contains:
  • One instance of each subsystem (e.g., private final DriveSubsystem m_drive = new DriveSubsystem())
  • One instance of each controller (XboxController, etc.)
  • A configureButtonBindings() method where triggers are wired to commands
  • A getAutonomousCommand() method returning the selected auto
Does NOT contain: motor declarations, sensor reads, control logic of any kind.
frc/robot/subsystems/
One file per mechanism. Each file extends SubsystemBase. Contains:
  • All hardware objects for that mechanism (motors, sensors, encoders)
  • A periodic() method for sensor reads and dashboard output
  • Public API methods commands call (e.g., setSpeed(double speed), stop())
Does NOT contain: joystick reads, button checks, knowledge of other subsystems.
frc/robot/commands/
One file per action. Each file extends CommandBase (or uses inline commands). Contains:
  • References to the subsystem(s) it uses (passed via constructor)
  • The four lifecycle methods: initialize(), execute(), isFinished(), end()
  • Logic that reads joystick inputs and calls subsystem API methods
Does NOT contain: motor controller objects, sensor declarations, hardware configuration.
frc/robot/Constants.java
One file, all numbers. Contains:
  • CAN IDs, DIO port numbers, USB port assignments
  • Physical constants: wheel circumference, gear ratios, encoder CPR
  • Tuning constants: deadband thresholds, PID gains, speed limits
Never hardcode a port number or a speed in a subsystem or command. It belongs here, referenced as Constants.kDriveMotorLeft. When wiring changes in the shop, you edit one file, not fifteen.
💡 The Rule of Single Responsibility

Every file in a Command-Based project has exactly one job. If you're writing a line of code and you can't tell which file it belongs in, that's a design signal — the line is probably trying to do two jobs at once. The question to ask: is this code about how the hardware operates (subsystem) or when and why it operates (command)?

A Quick Look at Robot.java in Command-Based

In Command-Based, Robot.java becomes deliberately thin. Its only jobs are to create the RobotContainer and call CommandScheduler.getInstance().run() each loop. All the real work happens elsewhere. Click the tokens below.

Robot.java — the Command-Based entry point
public class Robot extends TimedRobot {

  private RobotContainer m_robotContainer;

  @Override
  public void robotInit() {
    m_robotContainer = new RobotContainer();
  }

  @Override
  public void robotPeriodic() {
    CommandScheduler.getInstance().run();
  }

  @Override
  public void teleopInit() {
    // Nothing here — commands handle TeleOp logic
  }

  @Override
  public void autonomousInit() {
    Command auto = m_robotContainer.getAutonomousCommand();
    if (auto != null) auto.schedule();
  }
}
← click a highlighted token
🔌 System Check — Setting Up a Command-Based Project

Before writing any Command-Based code, verify your project is the right type:

  • In WPILib VS Code, use Ctrl+Shift+P → WPILib: Create a new project. Select Template → Java → Command Robot. This generates the correct file structure with Robot.java, RobotContainer.java, subsystems/, and commands/ pre-created.
  • If you're converting an existing TimedRobot project, do not try to migrate in-place. Create a fresh Command-Based project and manually port your hardware declarations into the appropriate subsystem files. Hybrid TimedRobot + Command-Based projects are a common source of subtle bugs.
  • Verify that Robot.java extends TimedRobot (not RobotBase) and that robotPeriodic() contains exactly one line: CommandScheduler.getInstance().run(). If there is other logic in robotPeriodic(), it belongs in a subsystem's periodic() or a command's execute().
  • All vendor libraries from your Unit 5 project must be re-added to the new project. Run WPILib: Manage Vendor Libraries → Install New Libraries (Online) for CTRE Phoenix 6, REVLib, or NavX as needed.

Knowledge Check

1. A teammate puts this line in DriveSubsystem.java: double speed = m_controller.getLeftY();. What architecture rule does this violate?

  • A Motor controllers cannot be accessed from subsystem files.
  • B Subsystems must never read joystick or button input — that's a command's job. The subsystem only knows how to operate its own hardware.
  • C The controller declaration must be in Constants.java, not in any subsystem.
  • D This is valid — subsystems are allowed to read driver input if the subsystem directly controls the drivetrain.

2. You have a ShootCommand that fires a note. A teammate asks where the XboxController object should be declared. The correct answer is:

  • A Inside ShootCommand.java as a field, so the command can read button state directly.
  • B Inside ShooterSubsystem.java, since the shooter subsystem needs to know when to fire.
  • C In RobotContainer.java, where it is used to bind the trigger to the command — the command itself never holds the controller object.
  • D In Constants.java as a static field accessible from anywhere.

3. Your competition robot has a drivetrain, shooter, intake, and climber — four subsystems, each with a dedicated command file. A new programmer joins and asks why the project has so many files instead of one big Robot.java. Which reason is most directly relevant to competition reliability?

  • A More files means faster compile times during the build season.
  • B Subsystem requirements prevent two commands from controlling the same hardware simultaneously — eliminating an entire class of conflict bugs that are nearly impossible to debug under competition pressure.
  • C FIRST rules require Command-Based for competition robots.
  • D Separate files let different programmers push to main without merge conflicts.
💪 Practice Prompt

Create Your First Command-Based Project

  1. Use WPILib VS Code to create a new Command Robot project for your team number. Explore the generated files: open Robot.java, RobotContainer.java, and the example subsystem/command files. Count the lines in each file and note how thin Robot.java is.
  2. On paper (or a whiteboard), draw the architecture of your Unit 5 intake robot in Command-Based terms. List the subsystems (with their hardware contents), the commands (with which subsystems they use), and what goes in RobotContainer. Do this before touching any code.
  3. Create a Constants.java file and move all your hardware port numbers into it as public static final int fields (e.g., kLeftMotorPort = 1, kRightMotorPort = 2). Reference them from the rest of your code using Constants.kLeftMotorPort. Delete all hardcoded numbers from every other file.
  4. Stretch goal: Read Team 2910's or Team 254's public GitHub robot repository (links in Unit 5 resources). Find their subsystems folder. List three subsystems they have, and for each one identify two public methods they expose to commands. Notice how none of those methods take an XboxController argument.