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.javaas 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.
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.
}
// 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.
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 itOwns 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.
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.
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.
- 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
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())
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
- CAN IDs, DIO port numbers, USB port assignments
- Physical constants: wheel circumference, gear ratios, encoder CPR
- Tuning constants: deadband thresholds, PID gains, speed limits
Constants.kDriveMotorLeft. When wiring changes in the shop, you edit one file, not fifteen.
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.
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();
}
}
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/, andcommands/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.javaextendsTimedRobot(notRobotBase) and thatrobotPeriodic()contains exactly one line:CommandScheduler.getInstance().run(). If there is other logic inrobotPeriodic(), it belongs in a subsystem'speriodic()or a command'sexecute(). - 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?
2. You have a ShootCommand that fires a note. A teammate asks where the XboxController object should be declared. The correct answer is:
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?
Create Your First Command-Based Project
- 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 thinRobot.javais. - 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. - Create a
Constants.javafile and move all your hardware port numbers into it aspublic static final intfields (e.g.,kLeftMotorPort = 1,kRightMotorPort = 2). Reference them from the rest of your code usingConstants.kLeftMotorPort. Delete all hardcoded numbers from every other file. - 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
XboxControllerargument.