State machines
Hook
You want an arm that does three things: stow when not in use, deploy to score, and briefly hold a game piece mid-air. You try to manage it with boolean flags:
boolean isDeployed = false;
boolean isHolding = false;
boolean isStowed = true;
By the time you add transitions — “if deployed AND sensor fires, go to holding” — you have a nest of if/else blocks that contradict each other. The arm randomly ends up “deployed AND stowed” at the same time.
The problem: you’re using three separate booleans to represent one thing that can only ever be in one state at a time.
Core concept
A finite state machine (FSM) is a model where a system is always in exactly one state, and events trigger transitions between states. It makes impossible combinations impossible.
An intake system might have these states:
- EMPTY — no game piece present
- INTAKING — motors running, waiting for piece
- HOLDING — piece detected, motors stopped
- EJECTING — launching piece toward target
A piece can’t be EMPTY and HOLDING simultaneously. An FSM enforces that by design.
State diagram
Every arrow is an event. Every node is a state. There’s no combination that isn’t on the diagram.
Java implementation
Use a Java enum to define the states, then a switch in periodic() to act on the current state:
enum IntakeState { EMPTY, INTAKING, HOLDING, EJECTING }
IntakeState state = IntakeState.EMPTY; // field — persists between calls
void periodic() {
switch (state) {
case EMPTY:
motor.set(0);
if (intakeButton.get()) state = IntakeState.INTAKING;
break;
case INTAKING:
motor.set(0.8);
if (sensor.hasGamePiece()) state = IntakeState.HOLDING;
if (!intakeButton.get()) state = IntakeState.EMPTY;
break;
case HOLDING:
motor.set(0.1); // small hold power
if (shootButton.get()) state = IntakeState.EJECTING;
break;
case EJECTING:
motor.set(-1.0);
if (!sensor.hasGamePiece()) state = IntakeState.EMPTY;
break;
}
}
The robot can never be in two states at once — state is a single variable.
Trace a state machine
Watch what happens across several loop cycles as a game piece is acquired then ejected:
switch (state) { case EMPTY: motor.set(0); if (intakeButton.get()) state = INTAKING; break; case INTAKING: motor.set(0.8); if (sensor.hasGamePiece()) state = HOLDING; break; case HOLDING: motor.set(0.1); if (shootButton.get()) state = EJECTING; break; case EJECTING: motor.set(-1.0); if (!sensor.hasGamePiece()) state = EMPTY; break;}Step through to see values update.
Try it yourself
An elevator has states: BOTTOM, MOVING_UP, TOP, MOVING_DOWN. A limitTop sensor triggers when it reaches the top, limitBottom when it reaches the bottom. The driver presses upButton or downButton.
Trace the state starting from BOTTOM. Events in order: upButton pressed → limitTop fires → downButton pressed → limitBottom fires.
What is the final state?
After: upButton pressed → limitTop fires → downButton pressed → limitBottom fires — what state is the elevator in?
Key takeaways
- A state machine ensures the system is in exactly one state at a time — impossible combinations become impossible.
- Use a Java
enumto list the states and aswitchto act on the current state. - Transitions happen inside the
switchcases, triggered by sensor readings or driver input. - State is a field — it must persist between calls to
periodic().
Common confusions
“Can’t I just use booleans?” You can, but two booleans allow four combinations even if only three are valid. An enum with three states allows exactly three — the compiler enforces it.
“What if two events happen at the same time?” In periodic(), transitions happen in the order you check them. The first matching condition wins. Design your checks so the priority order makes sense.
“When is a state machine overkill?” For simple two-state behavior (on/off), a boolean field is fine. Use an FSM when you have three or more distinct modes with well-defined transitions.
Challenge
Implement the transitions for a simple 3-state machine: IDLE, RUNNING, ERROR.
IDLE+"start"→RUNNINGRUNNING+"done"→IDLERUNNING+"fault"→ERRORERROR+"reset"→IDLE- Any other combination returns the current state unchanged.
Stuck? Show hint
Use a series of if/else-if checks: if (state.equals('IDLE') && event.equals('start')) return 'RUNNING'; etc.
What’s next
In Lesson 08, we’ll look at systematic debugging — how to isolate whether a bug is in your logic, a sensor reading, or hardware.