Introduction to State Machines
Every mechanism on your robot is always in exactly one condition: an intake is idle, or running, or holding a game piece. A shooter is spinning up, or ready, or cooling down. A state machine makes those conditions explicit and enforces that only defined transitions are possible. It is the single pattern that separates reliable FRC code from code that works until it doesn't.
By the end of this lesson, you will:
- Explain what a state machine is and why it handles complex mechanism behavior better than if/else chains
- Implement a state machine using an enum and switch statement for an intake subsystem
- Add entry actions (run once on state entry), during actions (run every cycle), and exit actions (run once on leave)
- Define transition conditions that move the machine between states based on sensor input and controller events
- Identify the state machine pattern in Team 2910's competition code
Why if/else Chains Break Down
Consider an intake with three behaviors: idle (stopped), running (collecting), and holding a game piece (stopped, confirmed). With if/else, you write something like: "if A button, run. If beam break, stop. If B button, eject." This works until the robot is already holding a game piece and the driver presses A again — do you restart the intake? What if the beam break triggers while ejecting? Each new edge case requires another conditional branch, and the interactions between branches become impossible to reason about.
A state machine replaces this combinatorial explosion with a simple rule: the current state determines the behavior. Transitions determine when states change. There are no hidden interactions — the behavior at any moment is completely described by one variable.
The Entry / During / Exit Pattern
Every state can have three types of actions:
- Entry action — runs exactly once when the machine transitions into this state. Starts a timer, logs a message, fires a solenoid.
- During action — runs every 20ms cycle while the machine is in this state. Commands motors, reads sensors, updates displays.
- Exit action — runs exactly once when the machine transitions out of this state. Cleans up — stops motors, resets a flag.
The during action is the only one that runs every cycle. Entry and exit run once per transition. Together they give you precise control over what happens at state boundaries — which is exactly where intake jams, scoring failures, and unexpected behaviors tend to live.
State Machine Simulator
The simulator below models a four-state intake pipeline: IDLE → INTAKING → HOLDING → SCORING. Click the event buttons to fire transitions and watch the active state, code case, and output values update. The log panel describes what just happened.
The Full Implementation
The simulator above models this code. Read through it carefully — notice how each case handles its during action, how transitions are written as if checks inside the case, and how entry actions use a separate boolean flag.
An entry action runs once when the machine enters a state. The most reliable pattern: track previousState and compare each cycle to detect a transition.
A robot-level state machine coordinates multiple subsystems. Rather than every subsystem having its own scattered if/else chains, a single RobotState enum drives the behavior of all mechanisms simultaneously.
State Transition Diagram
Before writing code, draw the diagram. Every state is a circle; every transition is an arrow with its condition. If you can't draw it, you can't code it reliably.
When I look at a team's code during inspection I search for two things: state machines and their absence. A team with a robot-level RobotState enum can tell me at any moment exactly what the robot is doing and why — because the answer is one variable read. A team with a dozen boolean flags and nested if/else chains can't. I've seen robots stop scoring mid-match because two boolean flags got out of sync in a way nobody anticipated. The state machine exists precisely to make that class of bug impossible: the current state is truth, and truth has exactly one value.
- Every mechanism that has more than two behaviors needs a state machine. Two behaviors (run/stop) can be an if/else. Three or more — intake, hold, eject, jam-recovery — need states.
- The state enum is the single source of truth. Do not maintain a parallel boolean flag (
isIntaking,isHolding) alongside the state enum. Derive any needed booleans from the state:isHolding() { return state == IntakeState.HOLDING; }. - Separate transition logic from output logic. One switch determines what state to be in, another determines what to do in that state. Mixing them creates cases where the motor command changes mid-transition in unexpected ways.
- Publish the state name to SmartDashboard.
SmartDashboard.putString("State", state.name())gives the drive team and pit crew a live status readout. State labels are the first thing to check when a mechanism stops working. - Draw the diagram before writing the code. If you cannot draw every state and every transition arrow, you don't understand the mechanism well enough to code it. The diagram is also the fastest way to explain the code to a teammate.
Knowledge Check
isIntaking = true/false and hasGamePiece = true/false as separate boolean flags. A bug causes isIntaking to be true and hasGamePiece to be true simultaneously. What would a state machine with an IntakeState enum do differently?GAME_PIECE_HELD state?case INTAKING: if (beamBreak.get()) { state = HOLDING; intakeMotor.set(0); } else { intakeMotor.set(0.65); }. What is the architectural problem?Build a Complete Subsystem State Machine
- Draw the state diagram for your intake subsystem before writing any code. Include at minimum: IDLE, INTAKING, HOLDING, EJECTING. Draw every transition arrow and label each with its condition.
- Implement the enum and the basic switch pattern (transitions in one switch, outputs in another). Wire the transitions to controller button inputs and the beam break sensor from Lesson 4.
- Add entry actions: when entering HOLDING, switch the intake motor to Brake mode and trigger a brief controller rumble. When entering IDLE from HOLDING, switch back to Coast.
- Publish the state name to SmartDashboard in
robotPeriodic(). Watch it update in real time during practice. Does it always show what you expect? - Add a fifth state: FAULT (triggered if the intake motor draws over 40A for more than 0.5 seconds — use a timer). In FAULT, stop the motor and display a dashboard alert. Add a manual reset to return to IDLE.
- Bonus: Implement a robot-level
RobotStateenum that controls both the drive and intake simultaneously. In INTAKING state, limit drive speed to 60%. In SCORING state, limit to 40% and prevent reversing. Confirm the drive behavior changes correctly when the intake state changes.