498
Control Lab FRC Programming Curriculum
Fundamentals · L07 of 8

State machines

Prereqs: fundamentals-06
Objectives 0 / 3

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

Key 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

State DiagramCurrent: EMPTY
intakeButtonsensorTrippedbuttonReleasedshootButtonsensorClearedEMPTYINTAKINGHOLDINGEJECTING
Fire event:
Click an event above to fire it and watch the active state change.

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:

Code Tracer
1switch (state) {
2 case EMPTY:
3 motor.set(0);
4 if (intakeButton.get()) state = INTAKING;
5 break;
6 case INTAKING:
7 motor.set(0.8);
8 if (sensor.hasGamePiece()) state = HOLDING;
9 break;
10 case HOLDING:
11 motor.set(0.1);
12 if (shootButton.get()) state = EJECTING;
13 break;
14 case EJECTING:
15 motor.set(-1.0);
16 if (!sensor.hasGamePiece()) state = EMPTY;
17 break;
18}
State
call1
stateEMPTY
motor0
intakeBtnfalse
sensorfalse
shootBtnfalse

Step through to see values update.

Press Start to begin stepping through the code.
Initial state

Try it yourself

⚡ 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?

⚡ Check your understanding

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 enum to list the states and a switch to act on the current state.
  • Transitions happen inside the switch cases, 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

⚡ Try it yourself

Implement the transitions for a simple 3-state machine: IDLE, RUNNING, ERROR.

  • IDLE + "start"RUNNING
  • RUNNING + "done"IDLE
  • RUNNING + "fault"ERROR
  • ERROR + "reset"IDLE
  • Any other combination returns the current state unchanged.
Code EditorJavaCtrl+Enter to run
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.