Lesson 2.3: RobotContainer & Bindings
🎯 What You’ll Learn
By the end of this lesson you will be able to:
- Explain RobotContainer’s role as the “wiring diagram” of the robot software
- Identify where subsystems are created and how they’re stored
- Read controller button bindings and predict what each button does
- Understand how autonomous routines are selected using PathPlanner and a SendableChooser
- Trace the full path from a button press through a command to motor output
The Mental Model: RobotContainer = Wiring Diagram
You’ve learned that subsystems are the robot’s capabilities and commands are the instructions that use them. But who decides which button triggers which command? Who creates the subsystems in the first place?
That’s RobotContainer.
RobotContainer is the wiring diagram of your robot software. It creates every subsystem, maps every controller button to a command, sets up default behaviors, and configures autonomous routines.
Think of it like wiring a house:
- Subsystems are the appliances — lights, outlets, the oven
- Commands are the actions — “turn on the kitchen light,” “preheat the oven to 350°”
- RobotContainer is the electrician who connects each light switch to the right fixture
Without RobotContainer, you’d have subsystems that can do things and commands that know how to do things, but nothing connecting the driver’s controller to any of it.
Open
Section 1: Creating Subsystems
The very first thing RobotContainer does is create all the subsystem objects. Look near the top of the class:
// Each subsystem represents a physical mechanism on the robotpublic final CommandSwerveDrivetrain drivetrain = TunerConstants.createDrivetrain();private final TurretSubsystem turretSubsystem = new TurretSubsystem();private final ShooterSubsystem shooterSubsystem = new ShooterSubsystem();private final IntakeSubsystem intakeSubsystem = new IntakeSubsystem();private final ClimberSubsystem climber = new ClimberSubsystem();Each line creates one subsystem. These are instance variables — they exist for the entire lifetime of the robot program. Every command that needs a subsystem gets it passed in from here.
Notice that the drivetrain is created differently: TunerConstants.createDrivetrain(). That’s because the swerve drivetrain configuration is auto-generated by CTRE Tuner X (remember the generated/ folder from Lesson 2.1). The other subsystems use simple new constructors.
Section 2: The Controller
RobotContainer creates the Xbox controller objects that the driver and co-pilot use:
// Xbox controller on USB port 0 (driver)private final CommandXboxController controller = new CommandXboxController(0);
// Xbox controller on USB port 1 (co-pilot)private final CommandXboxController copilot = new CommandXboxController(1);CommandXboxController is a WPILib class that wraps a standard Xbox controller and gives you methods like .x(), .y(), .a(), .b() that return Trigger objects. Triggers are what let you bind buttons to commands.
Why does RobotContainer create two separate CommandXboxController objects on different USB ports?
Section 3: Button Bindings — The Core Wiring
This is the heart of RobotContainer. Inside the constructor, each button is wired to a command. Let’s look at all four driver buttons:
X Button — Intake (toggleOnTrue)
controller.x() .toggleOnTrue( new SequentialCommandGroup( new InstantCommand(() -> intakeSubsystem.deployOut(), intakeSubsystem), new WaitCommand(0.3), new InstantCommand(() -> intakeSubsystem.stopDeploy()), new RunCommand(() -> intakeSubsystem.runIntake(0.5), intakeSubsystem) ).finallyDo(() -> { intakeSubsystem.stopIntake(); intakeSubsystem.deployIn(); }) .andThen(new WaitCommand(0.3)) .andThen(new InstantCommand(() -> intakeSubsystem.stopDeploy())) );Press X once → intake deploys and rollers spin. Press X again → everything retracts and stops. The toggleOnTrue binding type means the button acts like a light switch — on/off.
Y Button — Shoot (toggleOnTrue)
controller.y() .toggleOnTrue(new AutoShootCommand( turretSubsystem, shooterSubsystem, drivetrain, this::getSmartTarget, this::isTrajectoryPassingEnabled, false));Press Y once → the AutoShootCommand starts (aims turret, spins flywheels, feeds when ready). Press Y again → everything stops. Notice how RobotContainer passes the subsystems into the command — this is the wiring in action.
A Button — Jostle (whileTrue)
controller.a() .whileTrue(new RepeatCommand(intakeSubsystem.jostleCommand()));Hold A → the intake jostles repeatedly to unstick game pieces. Release A → it stops. The whileTrue binding type means the command only runs while the button is held down.
B Button — Elevator (onTrue)
controller.b() .onTrue(new InstantCommand(() -> climber.togglePresetIndex(1)));Press B → the elevator toggles between home position and a preset height. The onTrue binding type means the command runs once when the button is pressed (not held).
Binding Types Cheat Sheet
| Binding Type | Behavior | Example |
|---|---|---|
toggleOnTrue | Press to start, press again to stop | Intake (X), Shoot (Y) |
whileTrue | Runs while button is held, stops on release | Jostle (A) |
onTrue | Runs once when button is pressed | Elevator toggle (B) |
The Y button uses toggleOnTrue. This makes sense because shooting is a continuous process — the turret needs to keep aiming, the flywheels need to keep spinning, and the feeder needs to keep running. You don’t want to hold the button the entire time you’re shooting. Press once to start the whole shooting sequence, press again when you’re done. If it used whileTrue, the driver would have to hold Y the entire time, which would make it hard to also steer with the left stick.
Section 4: Default Commands — Idle Behavior
What happens when no button is pressed? That’s where default commands come in. RobotContainer sets these up at the bottom of the constructor:
// Turret default: always auto-aim at the smart targetturretSubsystem.setDefaultCommand(new RunCommand( () -> turretSubsystem.aimAtPose(drivetrain.getState().Pose, getSmartTarget(), drivetrain.getState().Speeds), turretSubsystem));
// Drivetrain default: drive using joystick inputdrivetrain.setDefaultCommand( drivetrain.applyRequest(() -> drive .withVelocityX(xLimiter.calculate(joystickCurve(-controller.getLeftY()) * speedScale * MaxSpeed)) .withVelocityY(yLimiter.calculate(joystickCurve(-controller.getLeftX()) * speedScale * MaxSpeed)) .withRotationalRate(rotationLimiter.calculate( joystickCurve(-controller.getRightX()) * 0.85 * MaxAngularRate))));The turret’s default command keeps it constantly tracking 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.
The turret's default command constantly auto-aims at the target. What happens to this default command when the driver presses Y to start AutoShootCommand?
Section 5: Autonomous Selection with PathPlanner
RobotContainer also sets up everything needed for autonomous mode — the 15-second period at the start of each match where the robot drives itself.
Named Commands
First, RobotContainer registers named commands that PathPlanner autonomous routines can trigger:
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’ve been reading about, just wrapped in commands and given string names. When a PathPlanner auto path says “run AutoShoot,” the command scheduler looks up that name and runs the corresponding command.
The Auto Chooser
Then RobotContainer creates a dropdown menu for the dashboard so the drive team can pick which autonomous routine to run:
autoChooser = AutoBuilder.buildAutoChooser("Tests");SmartDashboard.putData("Auto Mode", autoChooser);AutoBuilder.buildAutoChooser() scans the deploy/pathplanner/autos/ folder and creates a SendableChooser with all available auto routines. The drive team selects one on the dashboard before the match starts, and getAutonomousCommand() returns whichever one they picked:
public Command getAutonomousCommand() { return autoChooser.getSelected();}Robot.java calls this method when autonomous starts, and the command scheduler runs the selected routine. The auto routine follows a pre-planned path and triggers named commands (like “AutoShoot” and “Intake”) at specific points along the path.
Where does PathPlanner look for autonomous routine files?
Section 6: Tracing Y Button → Motor Output
Now let’s put it all together. Here’s the complete path from the driver pressing the Y button to motors actually spinning. This is the kind of trace you’ll do in Activity 2.4 — follow each step and make sure you understand how one leads to the next.
That’s eight steps from button press to note launch. Every 20ms, steps 3–8 repeat — the turret keeps tracking, the flywheels keep spinning, and the feeder keeps running. When the driver presses Y again, end(interrupted) runs and stops everything.
The Big Picture: How RobotContainer Connects Everything
Here’s a summary of everything RobotContainer does, in the order it happens:
RobotContainer constructor runs │ ├── 1. Create subsystems (drivetrain, turret, shooter, intake, climber) │ ├── 2. Register named commands for PathPlanner ("AutoShoot", "Intake", etc.) │ ├── 3. Build auto chooser from PathPlanner auto files │ ├── 4. Wire driver buttons: │ X → intake toggle (toggleOnTrue) │ Y → auto-shoot toggle (toggleOnTrue) │ A → jostle (whileTrue) │ B → elevator toggle (onTrue) │ ├── 5. Wire co-pilot buttons (hood nudge, aim offset, emergency stop, etc.) │ └── 6. Set default commands: Turret → always auto-aim Drivetrain → joystick drivingEvery button press, every autonomous action, and every idle behavior flows through RobotContainer. If you ever need to know “what does this button do?” or “how does auto work?” — this is the file to read.
They need to edit RobotContainer.java. They should search for controller.a() — that’s where the A button binding is defined. Right now it’s wired to whileTrue(new RepeatCommand(intakeSubsystem.jostleCommand())), which jostles the intake while held. To change the behavior, they’d replace the command inside .whileTrue(...) with whatever new command they want.
Remember: the button binding is in RobotContainer, but the actual motor control code lives in the subsystem. If they need a new behavior that doesn’t exist yet, they’d also need to add a method to the appropriate subsystem class.
Quick Reference: RobotContainer Sections
| Section | What It Does | Where to Find It |
|---|---|---|
| Subsystem creation | Creates one instance of each subsystem | Top of the class (instance variables) |
| Controller setup | Creates Xbox controller objects | Instance variables, ports 0 and 1 |
| Named commands | Registers commands PathPlanner can call by name | Start of constructor |
| Auto chooser | Builds dashboard dropdown for auto selection | After named commands |
| Driver bindings | Maps driver buttons (X, Y, A, B) to commands | Middle of constructor |
| Co-pilot bindings | Maps co-pilot buttons to tuning/emergency commands | After driver bindings |
| Default commands | Sets idle behavior for turret and drivetrain | End of constructor |
What’s Next?
You now understand how RobotContainer ties the whole robot together — subsystems, commands, buttons, and autonomous. In Activity 2.4: Trace a Button Press, you’ll put this knowledge to work by tracing the X button (intake) from controller press all the way to motor output, documenting every step along the way. That’s your first real assignment!