Skip to content

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 RobotContainer.java and follow along as we walk through each section.


Section 1: Creating Subsystems

The very first thing RobotContainer does is create all the subsystem objects. Look near the top of the class:

RobotContainer.java — Subsystem creation
// Each subsystem represents a physical mechanism on the robot
public 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:

RobotContainer.java — Controllers
// 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)

RobotContainer.java — X button intake toggle
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)

RobotContainer.java — Y button shoot toggle
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)

RobotContainer.java — A button jostle
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)

RobotContainer.java — B button elevator
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 TypeBehaviorExample
toggleOnTruePress to start, press again to stopIntake (X), Shoot (Y)
whileTrueRuns while button is held, stops on releaseJostle (A)
onTrueRuns once when button is pressedElevator toggle (B)
Checkpoint: Button Bindings
Without looking at the code, answer: What binding type does the Y (shoot) button use, and why does that make sense for shooting?

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:

RobotContainer.java — Default commands
// Turret default: always auto-aim at the smart target
turretSubsystem.setDefaultCommand(new RunCommand(
() -> turretSubsystem.aimAtPose(drivetrain.getState().Pose, getSmartTarget(),
drivetrain.getState().Speeds),
turretSubsystem));
// Drivetrain default: drive using joystick input
drivetrain.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:

RobotContainer.java — Named commands for auto
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:

RobotContainer.java — Auto chooser
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:

RobotContainer.java — getAutonomousCommand()
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.

Y Button → AutoShootCommand → Motor Output
🟢 Driver presses Y button on the controller
RobotContainer.java controller.y().toggleOnTrue(new AutoShootCommand(...))
The Y button trigger fires. Because it's toggleOnTrue, the command scheduler starts AutoShootCommand. RobotContainer passes in turretSubsystem, shooterSubsystem, and drivetrain.
AutoShootCommand.java initialize()
The command scheduler calls initialize() once. It resets the feeding flag, clears the ready timer, and grabs the current target position from the supplier.
AutoShootCommand.java execute() — Step 1: Aim turret
Every 20ms, execute() runs. First it calls turret.aimAtPose() with the robot's current position, the target, and the robot's velocity for motion compensation.
TurretSubsystem.java aimAtPose() method
The turret subsystem calculates the angle to the target, applies velocity compensation, and sets the turret motor to rotate to that angle. The turret physically moves.
AutoShootCommand.java execute() — Step 2: Set flywheel speed
Next, execute() calculates the distance to the target and calls shooter.autoAim(distance). This sets the hood angle and flywheel speed based on interpolation tables.
ShooterSubsystem.java autoAim(distance) method
The shooter subsystem looks up the correct RPM and hood angle for the given distance, then commands the flywheel motors (CAN IDs 21, 22) and hood servo.
AutoShootCommand.java execute() — Step 3: Check ready + feed
Still in execute(), the command checks: Is the turret aimed? Are flywheels at speed? If both are true for 0.3 seconds (FEED_DELAY), it calls shooter.runFeeder(0.85).
ShooterSubsystem.java runFeeder(speed) method
The feeder motor (CAN ID 24) spins at 85% power, pushing the game piece into the spinning flywheels. The note launches toward the target.
🔵 Flywheels spin up, turret aims at target, and feeder pushes the note into the flywheels — note launches toward the hub

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 driving

Every 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.

Checkpoint: RobotContainer's Role
A teammate asks: 'I want to change what the A button does.' Which file do they need to edit, and what should they look for?

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

SectionWhat It DoesWhere to Find It
Subsystem creationCreates one instance of each subsystemTop of the class (instance variables)
Controller setupCreates Xbox controller objectsInstance variables, ports 0 and 1
Named commandsRegisters commands PathPlanner can call by nameStart of constructor
Auto chooserBuilds dashboard dropdown for auto selectionAfter named commands
Driver bindingsMaps driver buttons (X, Y, A, B) to commandsMiddle of constructor
Co-pilot bindingsMaps co-pilot buttons to tuning/emergency commandsAfter driver bindings
Default commandsSets idle behavior for turret and drivetrainEnd 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!