Skip to content

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:

CommandWhy It’s a Good Candidate
AutoShootCommand.javaCoordinates turret, flywheel, and feeder β€” classic multi-step sequence
Intake sequence commandsDeploy β†’ run rollers β†’ detect note β†’ retract
Climbing commandsExtend β†’ grab β†’ retract β†’ lock
Any SequentialCommandGroup with conditional logicComplex 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:

  1. What is the mechanism doing? (motors running, waiting for a sensor, etc.)
  2. What condition ends this phase? (sensor triggered, timeout, target reached)
  3. What happens next? (which phase follows)

Document the States

State NameWhat HappensExit ConditionNext State

For the AutoShootCommand:

State NameWhat HappensExit ConditionNext State
IDLENothing β€” waiting to startCommand initializedPREPARING
PREPARINGTurret aims, flywheel spins upBoth on targetREADY
READYHold aim, hold speedBrief stabilization delayFEEDING
FEEDINGFeed note to shooterNote leaves (beam break)FINISHING
FINISHINGStop feeder, coast flywheelBrief delay for note to clearDONE
DONEStop all motorsβ€”Command ends
ERRORStop 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();
@Override
public void initialize() {
state = MyCommandState.IDLE;
stateTimer.restart();
}
@Override
public 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
}
@Override
public boolean isFinished() {
return state == MyCommandState.DONE || state == MyCommandState.ERROR;
}

Implementation Tips

  • Always log the state β€” SmartDashboard.putString() or Logger.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

  1. Run ./gradlew simulateJava
  2. Trigger the command
  3. Watch the state transitions in SmartDashboard or AdvantageScope
  4. Verify each state transitions correctly

On the Robot

  1. Deploy the code
  2. Run the command
  3. Monitor the state output in AdvantageScope
  4. Verify the mechanism behaves correctly in each state

Things to Verify

CheckExpected Behavior
Normal sequenceStates transition in order: IDLE β†’ … β†’ DONE
Timeout handlingIf a condition isn’t met, transitions to ERROR after timeout
InterruptionIf the command is cancelled, end() cleans up safely
State loggingCurrent state is visible in dashboard/logs

βœ…Checkpoint: State Machine Design
After implementing your state machine, answer: (1) How many states did your state machine have? List them. (2) What was the trickiest transition to get right and why? (3) How does the explicit state machine compare to the original command in terms of readability and debuggability?

Strong answers include:

  1. 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.”

  2. 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.”

  3. 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.