State Machines: Logic

The Problem: Tracking State With Booleans Doesn't Scale

A mechanism often needs to remember "what am I currently doing" — an intake might be idle, actively intaking, holding a game piece, or scoring. Tracking that with a pile of booleans gets unmanageable fast:

private boolean isIdle;
private boolean isIntaking;
private boolean isHolding;
private boolean isScoring;

Nothing stops two of these from accidentally being true at the same time — a bug that's easy to introduce and hard to spot, since "impossible" combinations (isIdle and isScoring both true) compile just fine.

The State Machine Design Pattern

A state machine is a design pattern for exactly this problem: a system that is always in exactly one of a fixed set of named states, and moves between them according to defined rules. It has five parts:

  • States — every situation the mechanism can be in, each with a name: IDLE, INTAKING, HOLDING, SCORING. Each state has its own state action: what the mechanism does, every loop, while it's in that state (for example, "run the rollers inward at half speed").
  • The start state — the one state the machine is in when the robot turns on (IDLE).
  • Transitions — the allowed moves from one state to another, like INTAKING → HOLDING. A move that isn't listed can't happen.
  • Guard conditions — the condition that must be true for a transition to happen, like "the sensor sees a game piece." A guard can check a sensor, a timer, or a driver's button.
  • Entry actions — anything that should happen exactly once, at the moment the machine enters a state, like logging the change or resetting a timer. (This is different from the state action, which repeats every loop.)

Drawing It: The State Diagram

Before writing any code, draw the machine. Each box is a state, each arrow is a transition labeled with its guard, and the arrow from "start" marks the start state:

             start
               │
               ▼
         ┌───────────┐   intake button pressed   ┌───────────┐
         │   IDLE    │ ────────────────────────▶ │ INTAKING  │
         └───────────┘                           └───────────┘
               ▲                                       │
               │ sensor no longer                      │ sensor sees
               │ sees a piece                          │ a piece
               │                                       ▼
         ┌───────────┐   score button pressed    ┌───────────┐
         │  SCORING  │ ◀──────────────────────── │  HOLDING  │
         └───────────┘                           └───────────┘

The same machine as a transition table — one row per arrow:

FromToGuard condition
IDLEINTAKINGthe driver presses the intake button
INTAKINGHOLDINGthe sensor sees a game piece
HOLDINGSCORINGthe driver presses the score button
SCORINGIDLEthe sensor no longer sees a game piece

And the state actions, one per state: IDLE stops the rollers, INTAKING runs them inward, HOLDING applies gentle hold pressure, and SCORING runs them outward. The diagram and the table carry the same information; the diagram is easier to reason about, and the table maps almost line for line onto code (Lesson 25.5).

Leaving the Start State

Look at the diagram again and follow the arrows from "start." The machine begins in IDLE and stays there — every loop — until a guard on one of IDLE's outgoing arrows becomes true. Here that's the driver pressing the intake button. If the start state had no outgoing transition, the machine would sit in IDLE forever and the intake would never do anything. Every state needs at least one way out, unless it's deliberately a final state, and the guards on those exits are how the outside world — drivers, sensors, timers — steers the machine.

Representing the States: One Enum, One Field

Enums (Ch.11) are exactly the tool for the states — a fixed, named set of mutually exclusive values, with the compiler enforcing that a variable can only ever be one of them:

public enum IntakeState
{
    IDLE,
    INTAKING,
    HOLDING,
    SCORING
}

private IntakeState currentState = IntakeState.IDLE;

Now the mechanism's state is a single variable, initialized to the start state, and it's structurally impossible for it to be two states at once. The enum only covers the states, though; the transitions, guards, and actions are logic, and they're the subject of Lesson 25.5.

Real Codebases Do This at Scale

This exact pattern — an enum representing a fixed set of named states for a mechanism — is how real, competitive FRC codebases (like Team 5817's own production code) structure their mechanisms, just with more states and richer per-state behavior than this introductory example: an elevator's states might be tied to interpolated target heights that shift based on scoring distance, or a roller's states might each carry their own target voltage. The underlying idea is identical to IntakeState above — a state machine core built on an enum — dressed up with more sophisticated per-state data as a mechanism's real requirements grow. The subsystem chapters later in this course build directly on this pattern.

Common Pitfalls

  • Reaching for several independent booleans instead of one enum. This lets impossible/contradictory combinations exist in the code, even though only one is ever supposed to be true — an enum makes that mistake structurally impossible.
  • Forgetting a state machine needs an initial value. currentState should always be initialized to the real start state (like IDLE) — an uninitialized state variable is a null waiting to cause a NullPointerException (Ch.12/13) the first time anything reads it.
  • Designing a state with no way out. If no transition leaves a state — especially the start state — the machine gets stuck there for the rest of the match. Check every box in the diagram for at least one outgoing arrow.
  • Confusing an entry action with a state action. Something meant to happen once (logging the change, resetting a timer) placed in a state action runs every loop — fifty times a second — instead of once.

Key Takeaways

  • Tracking "what state am I in" with multiple independent booleans allows impossible combinations and doesn't scale.
  • A state machine is always in exactly one of a fixed set of states; it has a start state, transitions between states, guard conditions that decide when each transition happens, state actions that repeat every loop, and entry actions that run once on entering a state.
  • Draw the state diagram (or write the transition table) before the code, and make sure every state — especially the start state — has a way out.
  • A single enum field, initialized to the start state, represents the current state and guarantees it's exactly one thing at a time.
  • This pattern is exactly what real, competitive FRC codebases use for their mechanisms, and the later subsystem chapters build on it.

Derived from other-reference-repo: team-5817-training/subsystems/02-state-based-servo-subsystems.md, 03-state-based-roller-subsystems.md (T5817 31.1-4) Deck context: mechacoder-test/src/lessons/java-2.js, slide 10 ("State Machines: Logic") — IntakeState enum example