Activity 6.11: Design a State Machine
π― Goal
By the end of this activity you will have:
- Identified a command or sequence in your teamβs code that would benefit from an explicit state machine
- Drawn a state diagram showing all states, transitions, and guard conditions
- Implemented the state machine using the enum + switch pattern
- Added state logging for debugging visibility
Step 1: Choose a Command to Refactor
Look through your teamβs commands for one that manages a multi-step sequence. Good candidates:
| Command | Why Itβs a Good Candidate |
|---|---|
| Coordinates turret, flywheel, and feeder β classic multi-step sequence | |
| Intake sequence commands | Deploy β run rollers β detect note β retract |
| Climbing commands | Extend β grab β retract β lock |
Any SequentialCommandGroup with conditional logic | Complex sequences with timeouts or branching |
Command Iβm refactoring: _______________
Step 2: Identify the Implicit States
Read through the command and identify the distinct phases of operation. For each phase, answer:
- What is the mechanism doing? (motors running, waiting for a sensor, etc.)
- What condition ends this phase? (sensor triggered, timeout, target reached)
- What happens next? (which phase follows)
Document the States
| State Name | What Happens | Exit Condition | Next State |
|---|---|---|---|
For the AutoShootCommand:
| State Name | What Happens | Exit Condition | Next State |
|---|---|---|---|
| IDLE | Nothing β waiting to start | Command initialized | PREPARING |
| PREPARING | Turret aims, flywheel spins up | Both on target | READY |
| READY | Hold aim, hold speed | Brief stabilization delay | FEEDING |
| FEEDING | Feed note to shooter | Note leaves (beam break) | FINISHING |
| FINISHING | Stop feeder, coast flywheel | Brief delay for note to clear | DONE |
| DONE | Stop all motors | β | Command ends |
| ERROR | Stop everything safely | β | Command ends |
Step 3: Draw the State Diagram
Before writing code, draw the state diagram. You can use text, a whiteboard, or a diagramming tool.
For each state, show:
- The state name (in a box or circle)
- Arrows to other states (transitions)
- Labels on arrows (guard conditions)
- Any timeout transitions
Template
βββββββββ ββββββββββββββ ββββββββββ STATE β βββββββΊ β STATE β βββββββΊ β STATE ββ A β guard1 β B β guard2 β C ββββββββββ ββββββββββββββ βββββββββ β β timeout βΌ βββββββββββββ β ERROR β βββββββββββββMy state diagram: (draw or describe it here)
Step 4: Define the Enum
Create the state enum based on your diagram:
public enum MyCommandState { IDLE, // Add your states here DONE, ERROR}Include an ERROR state for timeout and unexpected condition handling.
Step 5: Implement the Switch Statement
Write the execute() method with a switch on the current state:
private MyCommandState state = MyCommandState.IDLE;private Timer stateTimer = new Timer();
@Overridepublic void initialize() { state = MyCommandState.IDLE; stateTimer.restart();}
@Overridepublic void execute() { // Log current state for debugging SmartDashboard.putString("MyCommand/State", state.toString());
switch (state) { case IDLE: // Set up initial actions // Transition to first active state transitionTo(MyCommandState.NEXT_STATE); break;
// Add cases for each state...
case DONE: // Clean up break;
case ERROR: // Safe shutdown stopEverything(); break; }}
private void transitionTo(MyCommandState newState) { state = newState; stateTimer.restart(); // Reset timer for timeout tracking}
@Overridepublic boolean isFinished() { return state == MyCommandState.DONE || state == MyCommandState.ERROR;}Implementation Tips
- Always log the state β
SmartDashboard.putString()orLogger.recordOutput()so you can see state transitions in AdvantageScope - Use a timer for timeouts β reset the timer on each state transition
- Keep each case short β if a case is more than 10-15 lines, consider extracting a helper method
- Handle the default case β add a
default:that transitions to ERROR for unexpected states
Step 6: Add Timeout Guards
Every state that waits for a condition should have a timeout:
case PREPARING: turret.aimAtTarget(); shooter.spinUp(targetRPM);
if (turret.isOnTarget() && shooter.isAtSpeed()) { transitionTo(MyCommandState.READY); } else if (stateTimer.hasElapsed(3.0)) { // Timeout β something is wrong transitionTo(MyCommandState.ERROR); } break;Without timeouts, a stuck sensor or jammed mechanism could leave the state machine frozen in one state forever.
Step 7: Test Your State Machine
In Simulation
- Run
./gradlew simulateJava - Trigger the command
- Watch the state transitions in SmartDashboard or AdvantageScope
- Verify each state transitions correctly
On the Robot
- Deploy the code
- Run the command
- Monitor the state output in AdvantageScope
- Verify the mechanism behaves correctly in each state
Things to Verify
| Check | Expected Behavior |
|---|---|
| Normal sequence | States transition in order: IDLE β β¦ β DONE |
| Timeout handling | If a condition isnβt met, transitions to ERROR after timeout |
| Interruption | If the command is cancelled, end() cleans up safely |
| State logging | Current state is visible in dashboard/logs |
Strong answers include:
-
Specific states β e.g., βMy shooting state machine has 6 states: IDLE, PREPARING (turret + flywheel), STABILIZING (brief hold), FEEDING, CLEARING (note leaving), and DONE. Plus an ERROR state for timeouts.β
-
A specific challenge β e.g., βThe PREPARING β STABILIZING transition was tricky. I needed to wait for both the turret AND flywheel to be ready simultaneously, but they reach their targets at different times. I had to make sure the guard checked both conditions in the same cycle, not sequentially.β
-
Honest comparison β e.g., βThe explicit state machine is longer (more lines of code) but much easier to debug. I can see βState: PREPARINGβ in the logs and immediately know what the robot is doing. With the original command, I had to check three boolean flags to figure out the same thing. The enum also makes it impossible to be in an undefined state.β
Bonus: Add State Transition Logging
For even better debugging, log not just the current state but also state transitions:
private void transitionTo(MyCommandState newState) { SmartDashboard.putString("MyCommand/Transition", state.toString() + " β " + newState.toString()); state = newState; stateTimer.restart();}This creates a log of every transition, making it easy to trace the exact sequence of events in AdvantageScope after a match.
Whatβs Next?
Youβve designed and implemented an explicit state machine. In Activity 6.12: Compare State Machine Implementations, youβll examine how a top FRC team implements state machines in their code and compare their approach to your teamβs command structure.