Create Your Own State Machine

Introduction

In FTC, your OpMode’s loop() runs over and over—dozens of times per second. If you put a long, linear sequence of actions (drive here, wait, shoot, drive there) in that loop with blocking calls or sleep(), the robot can’t respond to the driver or to the game clock. State machines let you break behavior into discrete states and transitions: each loop you update the current state and check whether it’s time to switch. Everything stays non-blocking so the loop keeps running and other logic (e.g. path following, telemetry) can run in the same loop.

This tutorial teaches you how to design and build your own state machine library for FTC. You’ll learn the preplanning steps (game plan → states → transitions), see a state machine in a diagram, and follow the high-level steps to implement it in Java or Kotlin. We use this approach on our robot this season. If you know basic Java (or Kotlin), you can follow along and implement your own. For the theory behind finite state machines, see Game Manual 0: Finite State Machines. For the full implementation code (Task, Sequence, Machine), see the Library tutorial.

Preplanning and design

Before writing any code, plan what your robot will do and how it will move between phases. This is the most important step.

Game plan first

Write down your autonomous (or teleop) flow in plain language. Example: “Wait at start → drive to shoot position → shoot preload → go to intake → run intake → go to gate → shoot → … → leave.” Each of these phases is a candidate for a state.

States from phases

Turn each major phase into a state: e.g. IDLE, SHOOT, INTAKE, GATE, LEAVE, END. One state per “thing the robot is doing right now” keeps the machine readable. You can use multiple states for variants (e.g. INTAKE_1, INTAKE_2, INTAKE_3) when you need different follow-up behavior depending on where you came from.

Transitions from exit conditions

For each state, ask: when do we leave? Answer with: after N seconds, when the path follower is done, when a sequence finishes, or when a sensor fires. Those become your transitions (a condition plus optional minimum time in state).

Design artifact

Optionally, sketch a simple table or list: State → What happens in this state → How we leave (condition + next state). No code yet—this is your blueprint. Real autonomous code often grows from a list like: IDLE, READ_OBELISK, PROCESS_OBELISK, SHOOT_PRELOAD, SHOOT_1, SHOOT_2, SHOOT_3, SHOOT, INTAKE, INTAKE_1, INTAKE_2, INTAKE_3, GATE, LEAVE, END—each name came from a game plan and a decision about “when do we leave?”

State machine in pictures

A UML-style state diagram shows states as rounded boxes, transitions as arrows (with a condition or event on the arrow), and a filled circle for the initial state. Here’s a simple autonomous flow: wait → shoot → leave → end.

stateDiagram-v2 [*] --> IDLE IDLE --> SHOOT : "after 4s" SHOOT --> LEAVE : "on complete" LEAVE --> END : "path done" END --> [*]
Example state machine: simple autonomous (wait, shoot, leave).

Reading the diagram: The filled circle [*] is the start state. Rounded boxes are states (IDLE, SHOOT, LEAVE, END). Arrows are transitions, labeled with conditions (e.g., "after 4s" means "after 4 seconds in IDLE"). The final [*] is the end state. This diagram maps directly to code: each state becomes a .state() call, each arrow becomes a .transition() or .onComplete() call.

Here's a more complex example with multiple intake cycles:

stateDiagram-v2 [*] --> IDLE IDLE --> SHOOT_PRELOAD : "after 4s" SHOOT_PRELOAD --> INTAKE_1 : "on complete" INTAKE_1 --> INTAKE : "path done" INTAKE --> GATE : "after 2.5s" GATE --> SHOOT : "path done" SHOOT --> INTAKE_2 : "on complete" INTAKE_2 --> INTAKE : "path done" INTAKE --> GATE : "after 2.5s" GATE --> SHOOT : "path done" SHOOT --> LEAVE : "on complete" LEAVE --> END : "path done" END --> [*]
Example state machine: complex autonomous with multiple intake/shoot cycles (Close6-style).

This diagram shows how the same design process scales: more phases in your game plan become more states and transitions. Notice how INTAKE and GATE are reused—the same states handle multiple cycles. The routing (INTAKE_1 → INTAKE → GATE → SHOOT → INTAKE_2) uses separate "intake position" states (INTAKE_1, INTAKE_2) so you can have different paths to the intake area, but they all converge on the same INTAKE and GATE states.

Concepts: states, transitions, lifecycle

A state is one of the named phases (e.g. an enum value). A transition is a rule: “when in state X, if condition C is true and at least minTime seconds have passed, go to state Y.” You evaluate transitions in order; only the first matching one runs each loop.

Each state has three optional hooks:

  • onEnter — runs once when you enter the state (e.g. set motor power, move servo).
  • onUpdate(dt) — runs every loop while you’re in that state (e.g. run PID, update telemetry). dt is delta time in seconds since the last update.
  • onExit — runs once when you leave the state (e.g. stop motor, reset).

So each loop you: compute dt, run the current state’s onUpdate(dt), then check that state’s transitions. When one fires, you run onExit, switch to the next state, run its onEnter, and stop for this loop. Order of transitions matters.

Create your first ISM: end-to-end walkthrough

This walkthrough takes you from idea to running code, step by step. We'll build a simple autonomous: wait → shoot → leave → end.

Step 1: Write the game plan

Start with plain language: "The robot starts at the starting position. It waits 4 seconds (to let the shooter spin up). Then it shoots the preload ball (runs a sequence: intake on, open gate, wait 1.5 seconds, close gate, intake off). Then it follows a path to leave the starting zone. Then it stops."

This gives us four phases: wait, shoot, leave, stop. Each phase becomes a state.

Step 2: List states and transitions

Create a table or list mapping states to transitions:

  • IDLE — Rev shooter, keep gate closed. Leave: after 4 seconds → SHOOT
  • SHOOT — Run shoot sequence. Leave: when sequence completes → LEAVE
  • LEAVE — Follow path to leave zone. Leave: when path is done → END
  • END — Stop all motors. Leave: (no transitions—final state)

This is your design artifact. Keep it handy while coding.

Step 3: Draw or describe the diagram

Visualize the flow: IDLE → (after 4s) → SHOOT → (on complete) → LEAVE → (path done) → END. You can sketch this on paper or use a Mermaid diagram (see the "State machine in pictures" section above). The diagram helps you see the flow and catch missing transitions.

Step 4: Define the enum

Create an enum with one value per state. This gives you type-safe state names:

public enum States {
    IDLE,
    SHOOT,
    LEAVE,
    END
}

The enum names match your design artifact. Use clear, descriptive names—you'll reference these throughout your code.

Step 5: Implement the Task

Create a Task class that holds three callbacks: onEnter, onUpdate(dt), and onExit. For this example, we'll create a simple task that revs the shooter:

StateTask revShooter = StateTask.create(
    onUpdate = dt -> turret.setTargetVelocity(calculatedVelocity)
);

This task runs the shooter PID every loop. For full Task implementation details, see Implementing the Task in the Library tutorial.

Step 6: Implement the Sequence

Create a Sequence for the shoot sequence. Each step is a Task plus optional condition and minTime:

StateSequence shootSeq = new StateSequence()
    .step(StateTask.create(() -> intake.setPower(1.0)))
    .step(StateTask.create(() -> gate.open()), 1.5)
    .step(StateTask.create(() -> gate.close()), 0.25)
    .step(StateTask.create(() -> intake.setPower(0.0)));

This sequence: turns intake on, opens gate (waits 1.5s), closes gate (waits 0.25s), turns intake off. For full Sequence implementation details, see Implementing the Sequence in the Library tutorial.

Step 7: Implement the Machine and wire states/transitions

Create the StateMachine and add states with their transitions. Use the fluent API to build it in one chain:

fsm = new StateMachine<States>()
    .state(States.IDLE, revShooter, closeGate)
    .transition(() -> true, States.SHOOT, 4.0)
    .blockingSequence(States.SHOOT, shootSeq)
        .onComplete(States.LEAVE)
    .state(States.LEAVE, leavePathTask)
    .transition(() -> !follower.isBusy(), States.END)
    .state(States.END, stopAll);
fsm.setInitial(States.IDLE);

Notice how the transitions follow the states: after defining IDLE, the next transition applies to IDLE. After defining SHOOT, the next transition (onComplete) applies to SHOOT. This matches your design artifact. For full Machine implementation details, see Implementing the Machine in the Library tutorial.

Step 8: Call from OpMode init/loop

In your OpMode's init(), build hardware and paths, then build the state machine and call setInitial(). In loop(), call fsm.update() once per iteration:

@Override
public void init() {
    initHardware();
    buildPaths();
    buildStateMachine();
    fsm.setInitial(States.IDLE);
}

@Override
public void loop() {
    fsm.update();
    if (follower != null) follower.update();
    telemetry.addData("State", fsm.getCurrentState());
}

That's it! Your state machine is now running. The machine will start in IDLE, wait 4 seconds, transition to SHOOT, run the sequence, transition to LEAVE when done, follow the path, and end in END.

What to watch for: Make sure you call setInitial() before the first update(). Call fsm.update() exactly once per loop—don't call it multiple times or skip it. If you use path following, call follower.update() right after fsm.update() so motion and state stay synchronized. Use telemetry to debug: log the current state and time in state to see what's happening.

Steps to build your library

High-level roadmap. For full code and Java/Kotlin snippets, see the Library tutorial.

  1. State enum from your design. Create an enum with one value per state (IDLE, SHOOT, LEAVE, END, …). This gives you type-safe state names and a clear flow when you read the machine.
  2. Task. A single unit of behavior with onEnter, onUpdate(dt), and onExit. Store them as callbacks (e.g. Runnable, Consumer<Double> in Java). Use a Task when one state does one thing (e.g. “run intake motor,” “keep shooter at speed”).
  3. Sequence. An ordered list of steps; each step is a Task plus optional condition and minTime. Each loop you run the current step’s onUpdate; when the step’s condition is true and minTime has elapsed, you run onExit, advance to the next step, run its onEnter. When there are no steps left, the sequence is “complete.” Use a Sequence when one state should do several things in order without blocking the loop.
  4. Machine. Generic over your state enum. For each enum value you define a state (what runs on enter, update, exit—often by attaching a Task or Sequence). You add transitions that leave each state (condition + next state + optional minTime). You call setInitial(enum) once and update() every loop; the machine runs the current state’s update and then checks transitions in order.

For the complete implementation (Task, Sequence, Machine) in Java and Kotlin, see the Library tutorial.

What goes into each piece

Task — Single ongoing behavior: one state, one job. Reuse the same task in multiple states if needed (e.g. “rev shooter” in IDLE and in SHOOT).

Sequence — Multi-step flow inside one state (e.g. “intake on → open gate → wait 1.5 s → close gate → intake off”). Use blocking when the machine should only check transitions (including “on complete”) after the sequence finishes; use volatile when you want to allow interrupting the sequence (e.g. driver cancel) before it finishes.

Machine — Ties everything together: states (each with optional Task or Sequence), transitions (condition + next state + optional minTime), and the update loop. Path-following states are a special case: “while in this state, run the path follower; when the path is done, transition to the next state.”

How we use it this year

Our robot uses a state machine in both autonomous and teleop. The OpMode template declares a StateMachine<States>, calls buildStateMachine() in init() (after hardware and paths are set up), and calls fsm.update() once per loop(). Path following and telemetry run in the same loop right after the FSM update so motion and state stay in sync.

Simple autonomous (e.g. Far3): IDLE (rev shooter) → after 4 seconds → SHOOT (blocking shoot sequence) → when sequence completes → LEAVE (follow path) → when path is done → END. This matches the diagram above and shows how the design (wait, shoot, leave) maps directly to states and transitions.

Richer autonomous (e.g. Close6): Multiple paths, shoot preload, then several intake cycles (INTAKE_1, INTAKE_2, INTAKE_3) with paths to intake positions and to the gate, then shoot again, then leave. The same design process scales: more phases in the game plan become more states and transitions; we use the same Task/Sequence/Machine building blocks.

The FSM library is implemented in Java; our OpModes are in Kotlin. The Library tutorial shows both Java and Kotlin so you can implement in either language.

In your OpMode

Build the machine once, typically in init() after hardware and paths are set up. Call setInitial(yourEnum.INITIAL) so the first update() enters that state. In loop(), call machine.update() once per iteration—and, if you use path following, call your follower’s update right after so motion and state stay in sync.

For full code examples (init, loop, building the machine), see Using your machine in an OpMode in the Library tutorial. For debugging, log getCurrentState() and timeInState() to telemetry so you can see which state is active and how long it’s been there.

@Override
public void loop() {
    fsm.update();
    if (follower != null) follower.update();
    telemetry.addData("State", fsm.getCurrentState());
    telemetry.addData("Time in state", fsm.timeInState());
}

Glossary & tips

Infinite State Machine (ISM)
The state machine library for FTC consisting of StateMachine, StateSequence, and StateTask. The name emphasizes extensibility—there's no built-in limit on how many states you can add. See What is an Infinite State Machine? for details.
State
One of the named phases (e.g. an enum value). Each state has optional onEnter, onUpdate, and onExit behavior.
Transition
A rule to switch from the current state to another when a condition is true and (if set) minimum time in state has elapsed. Transitions are evaluated in order; only the first matching one fires per update. For execution order and timing, see How it works in the Library tutorial.
Lifecycle (enter/update/exit)
The three phases of a state: onEnter runs once when entering, onUpdate runs every loop while in the state, onExit runs once when leaving. For the complete lifecycle explanation, see How it works in the Library tutorial.
Execution model
The order of operations each loop: compute dt, run current state's update, evaluate transitions in order, fire first matching transition. For detailed execution order, see How it works in the Library tutorial.
Task
A single unit of behavior with enter, update(dt), and exit. Reusable across states.
Sequence
An ordered list of steps; runs one step at a time and is “complete” when all steps are done.
minTime
Minimum seconds in the current state before a transition is allowed to fire.
onComplete
A transition that fires when the state finishes (e.g. a sequence completes). Use onCompleteFrom when multiple states can complete to the same next state and you need to route by previous state.

Tips

  • Keep state names and transition conditions readable so the flow is easy to follow.
  • Use telemetry (current state, time in state) to debug transitions and timing.
  • When in doubt, start with one state per “phase” of your game plan; split into more states only when you need different transitions or behavior.