Skip to Content
architecturecommand-based-overview

Last Updated: 3/9/2026


Command-Based Architecture Overview

The BREAD5940 2025 robot code uses the WPILib Command-Based Programming paradigm, which provides a structured approach to organizing robot code.

What is Command-Based Programming?

Command-based programming is an organizational pattern where:

  • Subsystems represent physical mechanisms (drivetrain, intake, etc.)
  • Commands define actions that subsystems perform
  • Command Scheduler coordinates command execution
  • Triggers bind commands to operator inputs

Core Concepts

Subsystems

Subsystems are the building blocks that represent robot mechanisms.

Characteristics:

  • Extend SubsystemBase
  • Encapsulate hardware (motors, sensors)
  • Define capabilities (methods)
  • Can have a default command
  • Only one command can control a subsystem at a time

Example from our codebase:

public class Intake extends SubsystemBase { private final TalonFX motor; public void requestIntake() { // Control intake mechanism } @Override public void periodic() { // Runs every 20ms } }

Commands

Commands define actions that subsystems perform.

Command Lifecycle:

  1. initialize() - Called once when command starts
  2. execute() - Called repeatedly (every 20ms) while command runs
  3. end(interrupted) - Called once when command ends
  4. isFinished() - Returns true when command should end

Example:

public class AutoPlaceCommand extends CommandBase { private final Swerve swerve; private final Superstructure superstructure; @Override public void initialize() { // Setup for scoring } @Override public void execute() { // Drive to target and score } @Override public boolean isFinished() { return superstructure.atSetpoint(); } }

Command Scheduler

The scheduler manages command execution.

Responsibilities:

  • Run periodic methods on all subsystems
  • Execute active commands
  • Handle command interruption
  • Enforce subsystem requirements

In Robot.java:

@Override public void robotPeriodic() { CommandScheduler.getInstance().run(); }

Our Robot Architecture

Subsystem Hierarchy

Robot ├── Swerve (Drivetrain) ├── Superstructure (Coordinator) │ ├── ElevatorPivot │ ├── Intake │ ├── EndEffector │ ├── Indexer │ └── Climber ├── PhotonAprilTagVision ├── RealSenseVision └── ObjectDetection

State Machine Pattern

Our robot uses a hierarchical state machine centered around the Superstructure subsystem.

Why a State Machine?

  • Coordinates multiple subsystems
  • Ensures safe transitions
  • Handles complex game piece manipulation
  • Prevents conflicting actions

Superstructure States:

public enum SuperstructureState { STARTING_CONFIG, HOMING, IDLE_EMPTY, IDLE_HAS_CORAL, IDLE_HAS_ALGAE, INTAKING_CORAL_PREPARE, INTAKING_CORAL_GUT, PRE_PLACE_L1, PRE_PLACE_L2, PRE_PLACE_L3, PRE_PLACE_L4, SCORE, GRABBING_ALGAE_L3, CLIMBER_PRE_CLIMB, // ... and more }

Request-Based Control

Subsystems use a request pattern for state changes:

// In Robot.java (teleopPeriodic) if (RobotContainer.driver.getRightTriggerAxis() > 0.1) { RobotContainer.superstructure.requestIntake(true); } else { RobotContainer.superstructure.requestIntake(false); } // In Superstructure.java (periodic) if (requestIntake) { nextSystemState = SuperstructureState.INTAKING_CORAL_PREPARE; }

Benefits:

  • Decouples input from action
  • Allows state machine to validate transitions
  • Enables autonomous reuse of teleop logic

Command Composition

Commands can be combined to create complex behaviors.

Sequential Commands

Run commands one after another:

new SequentialCommandGroup( new AutoPlaceCommand(swerve, superstructure, () -> Level.L4), new MoveToPoseCommand(swerve, targetPose, Side.LEFT) );

Parallel Commands

Run commands simultaneously:

new ParallelCommandGroup( new DriveToTargetCommand(swerve), new PrepareToScoreCommand(superstructure) );

Conditional Commands

Choose commands based on conditions:

new ConditionalCommand( new ScoreCoralCommand(), new ScoreAlgaeCommand(), () -> endeffector.hasCoral() );

Inline Commands

Create simple commands inline:

Commands.runOnce(() -> swerve.resetPose(new Pose2d()))

Button Bindings

Commands are bound to controller inputs in RobotContainer.java.

Basic Binding

new JoystickButton(driver, XboxController.Button.kA.value) .whileTrue(new AutoPlaceCommand(...));

Trigger Types

  • onTrue() - Run once when button pressed
  • whileTrue() - Run continuously while button held
  • onFalse() - Run once when button released
  • toggleOnTrue() - Toggle command on/off

Complex Binding Example

From our codebase:

new JoystickButton(driver, XboxController.Button.kB.value) .whileTrue( Commands.either( new NetScoringCommand(swerve), new AutoPlaceCommand(swerve, superstructure, () -> numpad.getSelectedLevel(), () -> true, false), () -> endeffector.hasAlgae() ) );

This binding:

  1. Checks if robot has algae
  2. If yes, runs net scoring
  3. If no, runs auto place command

Default Commands

Subsystems can have a default command that runs when no other command is using them.

Swerve Default Command:

swerve.setDefaultCommand( new RunCommand( () -> { double x = driver.getLeftY(); double y = driver.getLeftX(); double omega = driver.getRightX(); swerve.requestPercent(new ChassisSpeeds(dx, dy, rot), true); }, swerve ) );

This allows the driver to always control the robot unless another command takes over.

Command Requirements

Commands declare which subsystems they need:

public class AutoPlaceCommand extends CommandBase { public AutoPlaceCommand(Swerve swerve, Superstructure superstructure) { addRequirements(swerve, superstructure); } }

Important: If a new command requiring a subsystem is scheduled, the current command using that subsystem is interrupted.

Autonomous Command Groups

Autonomous routines are built from command compositions.

Example from AutonomousSelector:

public Command get3PieceAuto() { return new SequentialCommandGroup( // Score preloaded coral new AutoPlaceCommand(swerve, superstructure, () -> Level.L4), // Drive to and intake first coral new ParallelCommandGroup( swerve.followPath(path1), Commands.runOnce(() -> superstructure.requestIntake(true)) ), // Score second coral new AutoPlaceCommand(swerve, superstructure, () -> Level.L3), // Continue pattern... ); }

Logging and Debugging

Command execution is automatically logged via AdvantageKit.

What’s Logged:

  • Command scheduling/interruption
  • Subsystem states
  • Sensor values
  • Command execution time

View in AdvantageScope:

  • Open log file
  • Navigate to “Commands” table
  • See command lifecycle events

Best Practices

1. Single Responsibility

Each command should do one thing well:

Good: IntakeCoralCommand - Just intakes ❌ Bad: IntakeAndScoreCommand - Too complex

2. Subsystem Encapsulation

Commands should use subsystem methods, not access hardware directly:

Good: superstructure.requestScore(true)Bad: motor.set(0.5) (from command)

3. Stateless Commands

Commands should not maintain state between runs:

Good: Check subsystem state in isFinished()Bad: Store counters in command fields

4. Timeout Safety

Add timeouts to prevent infinite loops:

new AutoPlaceCommand(...).withTimeout(3.0)

5. Interrupt Handling

Clean up in end(interrupted):

@Override public void end(boolean interrupted) { swerve.stop(); if (interrupted) { Logger.recordOutput("Command/Interrupted", true); } }

Common Patterns in Our Code

1. Supplier-Based Configuration

Commands accept suppliers for dynamic values:

public AutoPlaceCommand( Swerve swerve, Superstructure superstructure, Supplier<Level> levelSupplier // Dynamic level selection ) { this.levelSupplier = levelSupplier; }

2. Vision-Guided Commands

Commands that use vision feedback:

public class MoveToCoralCommand extends CommandBase { @Override public void execute() { Pose3d coralPose = objectDetection.getCoralPose(); if (coralPose != null) { // Drive toward coral } } }

3. State-Aware Commands

Commands that check subsystem state:

@Override public boolean isFinished() { return superstructure.getSystemState() == SuperstructureState.IDLE_HAS_CORAL; }

Next Steps

Additional Resources