Operator Interface & RobotContainer
RobotContainer is the single file where the human meets the machine. Every subsystem is born here. Every controller is declared here. Every button-to-command binding is written here. This lesson turns it from a place you initialize things into a deliberate, documented wiring diagram your entire team can read.
By the end of this lesson, you will:
- Explain the full anatomy of a production-ready
RobotContainer - Split driver and operator controllers with separate port assignments and documented responsibilities
- Build a
SendableChooser<Command>that lets the drive team select autonomous routines live from the dashboard - Write a control map comment block documenting every button binding
- Apply the 2910 convention: one public method,
getAutonomousCommand(), is the only thingRobot.javacalls on RobotContainer
RobotContainer's Three Responsibilities
A well-designed RobotContainer does exactly three things and nothing else:
- Instantiate subsystems. One instance per mechanism, declared as
private finalfields. These are shared references — every command that uses a subsystem receives it via constructor injection from here. - Declare controllers. One
XboxController(or equivalent) per human operator, on their USB port. Driver is port 0. Operator is port 1. No logic happens here — just declarations. - Wire input to commands. All button, trigger, and axis bindings in one method:
configureButtonBindings(). Every line in this method is a trigger attached to a command. If a binding changes, this is the only file touched.
If you find yourself writing control logic in RobotContainer — deciding what to do based on robot state, reading sensor values — that logic belongs in a command or subsystem. RobotContainer is a wiring diagram, not a decision-maker.
The Complete RobotContainer
// ─── Subsystems ──────────────────────────────────
private final DriveSubsystem m_drive = new DriveSubsystem();
private final IntakeSubsystem m_intake = new IntakeSubsystem();
private final ShooterSubsystem m_shooter = new ShooterSubsystem();
// ─── Controllers ─────────────────────────────────
private final XboxController m_driver = new XboxController(0);
private final XboxController m_operator = new XboxController(1);
// ─── Autonomous chooser ───────────────────────────
private final SendableChooser<Command> m_autoChooser =
new SendableChooser<>();
public RobotContainer() {
configureButtonBindings();
configureAutonomous();
}
private void configureButtonBindings() {
/*
* DRIVER CONTROLLER (port 0)
* Left Stick Y : Forward/back speed (arcade drive)
* Right Stick X: Rotation (arcade drive)
* Start button : Zero gyro heading
*
* OPERATOR CONTROLLER (port 1)
* A (hold) : IntakeCommand
* B (press) : EjectCommand
* Y (press) : ShootCommand
*/
// Default: drive with joysticks
m_drive.setDefaultCommand(new DriveWithJoysticksCommand(m_drive, m_driver));
// Driver: zero gyro on Start
new JoystickButton(m_driver, XboxController.Button.kStart.value)
.onTrue(Commands.runOnce(() -> m_gyro.reset(), m_gyro));
// Operator: A hold = intake
new JoystickButton(m_operator, XboxController.Button.kA.value)
.whileTrue(new IntakeCommand(m_intake).withTimeout(3.0));
// Operator: B = eject once
new JoystickButton(m_operator, XboxController.Button.kB.value)
.onTrue(new EjectCommand(m_intake));
}
private void configureAutonomous() {
m_autoChooser.setDefaultOption("Drive Forward", new DriveDistanceCommand(m_drive, 72));
m_autoChooser.addOption("Score + Drive", new ScoreAndDriveAuto(m_drive, m_shooter));
m_autoChooser.addOption("Do Nothing", Commands.none());
SmartDashboard.putData("Auto Chooser", m_autoChooser);
}
// Only public method — called by Robot.java
public Command getAutonomousCommand() {
return m_autoChooser.getSelected();
}
}
The Control Map: Your Most Important Comment
Notice the block comment inside configureButtonBindings() listing every binding. This is the control map — a human-readable summary of the entire operator interface. On 2910, this comment is required and kept in sync with the actual bindings throughout the season.
Here's why: at competition, the drive coach is standing at the field explaining to a new driver what every button does. They should be able to open RobotContainer.java and read it directly. If the control map comment doesn't match reality, someone made a change without updating it — that's a code review failure.
At competition, two people hold controllers. The driver (port 0) controls locomotion: forward, back, turn, field orientation reset. Their hands must stay on the drivetrain controls — everything else is the operator's job. The operator (port 1) controls all mechanisms: intake, shooter, arm, climber. This split is physical and deliberate. A driver who also has to think about shooting will miss driving opportunities. Design your control map around this division before writing a single binding.
Interactive Control Map Explorer
Explore a sample competition control map. Click a button to see the bound command and its behavior mode.
My pre-match checklist includes asking the operator to show me every binding by pressing each button while I watch Shuffleboard. If the Scheduler widget shows the command I expect, the wiring is correct. If it shows something different — or nothing — the binding was changed without updating the comment. This 2-minute check has caught real bugs at regionals. The control map comment is not just documentation; it's a test specification.
Before qualifying:
- Open Driver Station USB Devices tab. Verify driver controller is port 0, operator is port 1. If they are swapped, the control map is mirrored in hardware. Fix the physical USB slot or update the port numbers in RobotContainer — do not fix it in mid-match.
SmartDashboard.putData("Auto Chooser", m_autoChooser)must be called in the constructor, not in a periodic method. If it's called in periodic, the chooser widget resets to the default on every loop iteration, making the drive team's selection volatile.- Verify
getAutonomousCommand()is the only public method on RobotContainer thatRobot.javacalls. IfRobot.javais reaching into RobotContainer for anything else (subsystem references, controller objects), that's a design smell.
Knowledge Check
1. A teammate puts this in RobotContainer.configureButtonBindings(): if (m_intake.hasGamePiece()) { ... }. What rule does this violate?
2. The drive team selects "Score + Drive" in the auto chooser on Thursday night. On Friday morning the Driver Station laptop is rebooted and Shuffleboard is reopened. What auto will run if the drive team doesn't reselect?
3. You have a ShootCommand that should only fire if the shooter flywheel is at target RPM. The cleanest way to implement this gate in RobotContainer is:
Build the Full RobotContainer
- Write the complete control map comment block at the top of
configureButtonBindings()before writing any binding code. List every planned input for both controllers. Then implement the bindings to match it exactly. If you find yourself writing a binding that's not in the comment, add it to the comment first. - Add a
SendableChooser<Command>with at least three autonomous options. Configure it in a privateconfigureAutonomous()method called from the constructor. Publish it viaSmartDashboard.putData(). Open Shuffleboard and confirm the dropdown appears and each option is selectable. - Add an
.onlyIf()guard to the intake binding so it only fires when the robot doesn't already have a game piece. Block the beam break by hand and confirm the binding is skipped. Unblock it and confirm it fires again. - Add
SmartDashboard.putData("Scheduler", CommandScheduler.getInstance())and run through every binding. Screenshot the Scheduler widget showing each command name as you press each button. This is your binding verification record — keep it as a comment or attached doc for the team. - Stretch goal: Create an
AutonomousChooserhelper class that encapsulates all theSendableChoosersetup and registration, returning the chooser from agetChooser()method. RobotContainer calls this helper instead of managing the chooser inline. This is the pattern elite teams use when they have 6–8 autonomous options — it keeps RobotContainer lean.