Skip to content

RobotContainer

RobotContainer (in com.vcs.valleylib.ftc) mirrors WPILib's class of the same name: one central place where subsystems are constructed, operator controls are bound, and the autonomous command is chosen. OpModes then become thin shells that just hand the container a HardwareMap and gamepads.

Why a container?

Without one, every OpMode re-declares subsystems and bindings, and TeleOp/Auto drift apart. With one:

  • Subsystems are created exactly once per OpMode run, in one file.
  • The same command objects power TeleOp buttons and autonomous routines.
  • Adding a mechanism touches one class, not four OpModes.

The contract

public abstract class RobotContainer {
    public abstract void configureBindings();     // wire driver controls
    public abstract Command getAutonomousCommand(); // the auto routine
    public Command getTeleOpInitCommand() {        // optional teleop startup
        return null;                               // null = nothing to run
    }
}
Member Purpose
configureBindings() Create triggers and attach commands to buttons
getAutonomousCommand() Return the routine an Autonomous OpMode should schedule
getTeleOpInitCommand() Optional command to schedule when TeleOp starts (e.g., zero the lift, set LED state). Default returns null — skip it in that case

A complete example

This is the library's shipped sample (SimpleRobot), lightly annotated:

public class SimpleRobot extends RobotContainer {

    public final SampleDriveHardware drive;
    public final SampleIntakeHardware intake;
    public final CommandGamepad driver;

    public SimpleRobot(HardwareMap hardwareMap, Gamepad gamepad1) {
        // Subsystems register themselves with the scheduler automatically
        drive = new SampleDriveHardware(hardwareMap);
        intake = new SampleIntakeHardware(hardwareMap);

        driver = CommandGamepad.forLogitechF310(gamepad1);

        configureBindings();
    }

    @Override
    public void configureBindings() {
        // Default command: tank drive from the sticks whenever drive is idle.
        // drive.run(...) carries the drivetrain requirement automatically.
        drive.setDefaultCommand(drive.run(() ->
                drive.tankDrive(driver.leftY(), driver.rightY())));

        // Hold-to-run intake controls — subsystem factories carry requirements,
        // and bindings auto-register with the polled TriggerManager.
        driver.a().whileTrue(intake.startEnd(intake::intakeIn, intake::stop));
        driver.b().whileTrue(intake.startEnd(intake::intakeOut, intake::stop));

        // Composed one-shot: pulse the intake for half a second
        driver.rightBumper().onTrue(
                intake.runOnce(intake::intakeIn)
                        .andThen(Commands.waitSeconds(0.5))
                        .andThen(intake::stop));
    }

    @Override
    public Command getAutonomousCommand() {
        // Drive forward for 2 s, then guarantee the drivetrain stops
        return drive.run(() -> drive.tankDrive(0.5, 0.5))
                .withTimeout(2.0)
                .finallyDo(drive::stop);
    }
}

Using it from OpModes

@TeleOp(name = "TeleOp")
public class MainTeleOp extends CommandOpMode {
    private SimpleRobot robot;

    @Override
    protected void initialize() {
        robot = new SimpleRobot(hardwareMap, gamepad1);
        Command init = robot.getTeleOpInitCommand();
        if (init != null) scheduler.schedule(init);
    }

    @Override
    protected void run() {}
}

Note there's no trigger wiring in the OpMode at all: the container's bindings auto-registered with the shared TriggerManager when configureBindings() ran, and CommandOpMode polls that manager every loop.

@Autonomous(name = "Auto")
public class MainAuto extends CommandOpMode {
    @Override
    protected void initialize() {
        SimpleRobot robot = new SimpleRobot(hardwareMap, gamepad1);
        scheduler.schedule(robot.getAutonomousCommand());
    }

    @Override
    protected void run() {}
}

Call configureBindings() inside the OpMode lifecycle

Trigger bindings auto-register with the shared TriggerManager, which CommandOpMode clears during init() — so construct the container (and run its configureBindings()) from the OpMode's initialize(), as shown above, and everything is wired automatically. See Triggers.