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:
- initialize() - Called once when command starts
- execute() - Called repeatedly (every 20ms) while command runs
- end(interrupted) - Called once when command ends
- 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
└── ObjectDetectionState 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:
- Checks if robot has algae
- If yes, runs net scoring
- 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
- Learn about Subsystem Architecture
- Explore State Machine Design
- Review Subsystem Documentation