Adding subsystems¶
This is the page I expect new mechanism work to follow. It is written to be practical: make the mechanism easy to command, easy to simulate or replay, and easy to diagnose from logs.
Start with the behavior¶
Before creating files, describe the mechanism in robot terms:
- What can it do?
- What states does it report?
- What commands should other code schedule?
- What should happen if a sensor disconnects?
- What should be logged every loop?
For example, an intake might expose:
public void runVolts(double volts)
public void stop()
public boolean hasGamePiece()
public Command intakeUntilDetected()
That is a better interface than exposing motor controllers or raw beam-break voltages.
Use an IO boundary when hardware matters¶
For mechanisms with motors, sensors, simulation needs, or replay value, use the AdvantageKit IO pattern:
The subsystem owns behavior. The IO implementation owns hardware calls. Replay mode can use an empty IO implementation.
Example subsystem shape¶
public class Intake extends SubsystemBase {
private final IntakeIO io;
private final IntakeIOInputsAutoLogged inputs = new IntakeIOInputsAutoLogged();
public Intake(IntakeIO io) {
this.io = io;
}
@Override
public void periodic() {
io.updateInputs(inputs);
Logger.processInputs("Intake", inputs);
}
public void runVolts(double volts) {
io.setVoltage(volts);
Logger.recordOutput("Intake/SetpointVolts", volts);
}
public void stop() {
runVolts(0.0);
}
public boolean hasGamePiece() {
return inputs.hasGamePiece;
}
}
The key idea is that the command can call intake.runVolts(6.0) without knowing which motor controller exists.
Example IO shape¶
public interface IntakeIO {
@AutoLog
public static class IntakeIOInputs {
public boolean connected = true;
public double appliedVolts = 0.0;
public double currentAmps = 0.0;
public boolean hasGamePiece = false;
}
public default void updateInputs(IntakeIOInputs inputs) {}
public default void setVoltage(double volts) {}
}
Keep the input struct factual. It should describe what hardware measured, not what a command hoped would happen.
Construct it in RobotContainer¶
Choose the IO implementation based on robot mode:
private Intake createIntake() {
return switch (Constants.currentMode) {
case REAL -> new Intake(new IntakeIOTalonFX());
case SIM -> new Intake(new IntakeIOSim());
case REPLAY -> new Intake(new IntakeIO() {});
};
}
This keeps mode decisions in one place. It also prevents replay from accidentally opening hardware devices.
Commands¶
Mechanism commands should be small and composable:
public Command intakeCommand() {
return Commands.startEnd(
() -> runVolts(6.0),
this::stop,
this);
}
public Command intakeUntilDetected() {
return intakeCommand().until(this::hasGamePiece).withTimeout(2.0);
}
Use timeouts on event commands. An autonomous path event that waits forever is a field failure, not a controls feature.
Driver bindings¶
Bind controls in RobotContainer. Keep controls explicit and easy to audit.
Good:
Avoid hidden behavior where a subsystem changes drive or navigation policy without a scheduled command. If the robot is doing something, it should be visible in the command structure and logs.
Autonomous event registration¶
Register mechanism events through the auto manager or event registry:
autoManager.registerEvent("intake", () -> intake.intakeUntilDetected().withTimeout(2.0));
autoManager.registerEvent("score", () -> scorer.scoreCommand().withTimeout(1.0));
Then use those event keys from BLine JSON. The string in the path file and the string in code must match.
Keep event commands defensive:
- add timeouts
- prefer commands that end cleanly
- log important state
- avoid blocking on perfect sensor behavior
Logging rules¶
Use AdvantageKit for subsystem telemetry:
Logger.processInputs("SubsystemName", inputs)for IO inputs.Logger.recordOutput("SubsystemName/Setpoint", value)for requested state.Alertfor driver-relevant hardware faults.
Do not publish normal telemetry directly to NetworkTables. AdvantageKit will publish logged outputs to NT4 when the NT4 publisher is configured.
Simulation¶
A simple simulation is better than none if it catches command mistakes.
Sim does not need to model every physical detail. It should at least let you test:
- commands schedule
- commands finish
- sensors change plausibly
- logs update
- autonomous events do not crash
For mechanisms with closed-loop control, make the simulation good enough to tune command sequencing, not necessarily final gains.
Review checklist¶
Before merging a new subsystem:
- Commands do not touch vendor device objects.
- Real, sim, and replay construction paths exist.
- IO inputs are logged.
- Setpoints or requested states are logged.
- Driver bindings are obvious.
- Autonomous events have timeouts.
- Unit tests cover pure logic where practical.
- The subsystem can be disabled safely.