Traffic Light Tutorial
Learn SCXML state machine fundamentals by building a traffic light. This tutorial progresses from a simple 3-state model to a realistic European-style traffic light with emergency mode.
What you'll learn:
- State machine basics: states, transitions, events
- Entry actions with
<onentry>and<log> - Compound (nested) states for hierarchical organization
- Delayed events with
<send delay="..."> - Variables with the ECMAScript datamodel
Part 1: Simple Traffic Light
Let's start with the basics: a traffic light that cycles through three colors.
Try It
Click Start to initialize the state machine, then click timer to cycle through the lights:
How it works:
- The machine starts in the red state
- Each timer event triggers a transition to the next state
- The cycle repeats: red → green → yellow → red
The SCXML Source
Here's the complete state machine definition:
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
datamodel="null" initial="red">
<state id="red">
<onentry>
<log label="Light" expr="'RED - Stop'"/>
</onentry>
<transition event="timer" target="green"/>
</state>
<state id="green">
<onentry>
<log label="Light" expr="'GREEN - Go'"/>
</onentry>
<transition event="timer" target="yellow"/>
</state>
<state id="yellow">
<onentry>
<log label="Light" expr="'YELLOW - Caution'"/>
</onentry>
<transition event="timer" target="red"/>
</state>
</scxml>
Key Concepts
| Element | Purpose |
|---|---|
<scxml initial="red"> |
Root element; initial sets the starting state |
<state id="red"> |
Defines a state with unique identifier |
<onentry> |
Actions to execute when entering the state |
<log> |
Output a message (appears in the Event Log panel) |
<transition event="timer" target="green"> |
When timer event occurs, go to green state |
Datamodel: We use datamodel="null" because this simple example needs no variables. The null datamodel works identically on all target platforms (Java, JavaScript, C).
Generate Code
Transform the SCXML into production code:
Java:
scxml-gen traffic-simple.scxml -t java -o TrafficLight.java --package com.example
TrafficLight sm = new TrafficLight();
sm.start(); // Enters "red"
sm.send("timer"); // → green
sm.send("timer"); // → yellow
sm.send("timer"); // → red (cycle complete)
JavaScript:
scxml-gen traffic-simple.scxml -t javascript -o traffic.js
import { TrafficLight } from './traffic.js';
const sm = new TrafficLight();
sm.start();
sm.send('timer'); // Cycles through states
C:
scxml-gen traffic-simple.scxml -t c -o traffic.c
TrafficLight sm;
TrafficLight_init(&sm);
TrafficLight_start(&sm);
TrafficLight_send(&sm, EVT_TIMER, NULL); // O(1) dispatch
Part 2: Realistic European Traffic Light
Real traffic lights are more sophisticated. European-style lights show red+yellow together before green (preparing drivers to go), and support an emergency blinking mode for maintenance or low-traffic hours.
Try It
Click Start, then watch the automatic transitions. Click emergency to switch to blinking mode, normal to return:
New features:
- Automatic timing - States transition automatically after delays (5s, 1.5s, etc.)
- Compound states -
normalandemergencycontain child states - Emergency mode - Yellow light blinks on/off continuously
- Cycle counter - Tracks how many complete cycles have occurred
The SCXML Source
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
datamodel="ecmascript" initial="normal" name="TrafficLight">
<datamodel>
<data id="cycle_count" expr="0"/>
</datamodel>
<!-- Normal Operation: automatic timed cycle -->
<state id="normal" initial="red">
<transition event="emergency" target="emergency"/>
<state id="red">
<onentry>
<log label="LIGHT" expr="'RED - Stop'"/>
<send event="timer" delay="5s"/>
</onentry>
<transition event="timer" target="red_yellow"/>
</state>
<state id="red_yellow">
<onentry>
<log label="LIGHT" expr="'RED+YELLOW - Prepare to go'"/>
<send event="timer" delay="1500ms"/>
</onentry>
<transition event="timer" target="green"/>
</state>
<state id="green">
<onentry>
<log label="LIGHT" expr="'GREEN - Go'"/>
<send event="timer" delay="5s"/>
</onentry>
<transition event="timer" target="yellow"/>
</state>
<state id="yellow">
<onentry>
<log label="LIGHT" expr="'YELLOW - Prepare to stop'"/>
<send event="timer" delay="2s"/>
</onentry>
<transition event="timer" target="red">
<assign location="cycle_count" expr="cycle_count + 1"/>
<log label="CYCLE" expr="'Completed cycle ' + cycle_count"/>
</transition>
</state>
</state>
<!-- Emergency Mode: blinking yellow -->
<state id="emergency" initial="blink_on">
<onentry>
<log label="MODE" expr="'EMERGENCY - Yellow blinking'"/>
</onentry>
<transition event="normal" target="normal"/>
<state id="blink_on">
<onentry>
<log label="BLINK" expr="'YELLOW ON'"/>
<send event="blink" delay="500ms"/>
</onentry>
<transition event="blink" target="blink_off"/>
</state>
<state id="blink_off">
<onentry>
<log label="BLINK" expr="'OFF'"/>
<send event="blink" delay="500ms"/>
</onentry>
<transition event="blink" target="blink_on"/>
</state>
</state>
</scxml>
New Concepts Explained
Compound States:
The normal state contains four child states. When normal is active, exactly one of its children (red, red_yellow, green, or yellow) is also active. This creates a hierarchy:
Active: [normal, red] ← Two states active simultaneously
Active: [normal, green] ← Parent + one child
Active: [emergency, blink_on] ← Different parent, different child
Delayed Events:
<send event="timer" delay="5s"/>
Schedules an event to fire automatically after a delay. Generated code handles this:
- Java:
ScheduledExecutorService - JavaScript:
setTimeout - C: Poll with
tick()in your main loop
Datamodel Variables:
<datamodel>
<data id="cycle_count" expr="0"/>
</datamodel>
...
<assign location="cycle_count" expr="cycle_count + 1"/>
The ecmascript datamodel enables JavaScript expressions. Variables persist across transitions.
Transition Actions:
<transition event="timer" target="red">
<assign location="cycle_count" expr="cycle_count + 1"/>
<log label="CYCLE" expr="'Completed cycle ' + cycle_count"/>
</transition>
Actions inside <transition> execute during the transition, after exiting the source state and before entering the target.
Event Visibility:
| Event | Type | Purpose |
|---|---|---|
timer |
Internal | Auto-fired by <send delay> |
blink |
Internal | Emergency blink toggle |
emergency |
Public | External command to enter emergency mode |
normal |
Public | External command to return to normal |
Testing with the Simulator
Before generating code, test your state machine in the Simulator:
$ scxml-simulator traffic-light.scxml
VSCXML Simulator v0.1.0
> start
[00:00.000] state_enter: normal
[00:00.001] state_enter: normal.red
[00:00.002] log: LIGHT = "RED - Stop"
[00:00.003] scheduled: timer @ +5000ms
> scheduled
Pending events:
timer @ 5000ms
> poll
[00:05.000] event: timer
[00:05.001] transition: red → red_yellow
[00:05.002] log: LIGHT = "RED+YELLOW - Prepare to go"
> send emergency
[00:07.100] event: emergency
[00:07.101] state_exit: normal
[00:07.102] state_enter: emergency
[00:07.103] log: MODE = "EMERGENCY - Yellow blinking"
> vars
cycle_count: 0
Useful commands:
start- Initialize and enter initial statessend <event>- Send an event manuallyscheduled- Show pending delayed eventspoll- Process delayed events (advance time)vars- Show datamodel variablesstate- Show active states
Code Generation Examples
Java with Automatic Timing
scxml-gen traffic-light.scxml -t java -o TrafficLight.java --package com.traffic
import com.traffic.TrafficLight;
import com.scxmlgen.runtime.executor.ContinuousStateMachineExecutor;
public class Main {
public static void main(String[] args) throws Exception {
TrafficLight sm = new TrafficLight();
// Executor handles delayed events automatically
try (var executor = new ContinuousStateMachineExecutor(sm)) {
executor.start();
// Watch it cycle automatically
Thread.sleep(30000);
// Trigger emergency mode
sm.send("emergency");
Thread.sleep(5000);
// Return to normal
sm.send("normal");
}
}
}
JavaScript for Web
scxml-gen traffic-light.scxml -t javascript -o traffic-light.js
import { TrafficLight } from './traffic-light.js';
const sm = new TrafficLight();
sm.setLogger((msg) => console.log('[Traffic]', msg));
sm.start();
// Delayed events fire automatically via setTimeout
// Switch to emergency after 20 seconds
setTimeout(() => sm.send('emergency'), 20000);
// Return to normal after 25 seconds
setTimeout(() => sm.send('normal'), 25000);
C for Embedded Systems
scxml-gen traffic-light.scxml -t c -o traffic_light.c --amalgamate
#include "traffic_light.h"
TrafficLight sm;
void setup() {
TrafficLight_init(&sm);
TrafficLight_start(&sm);
}
void loop() {
// Poll for delayed events (pass current time in ms)
TrafficLight_tick(&sm, millis());
// Check for emergency button
if (digitalRead(EMERGENCY_PIN) == HIGH) {
TrafficLight_send(&sm, EVT_EMERGENCY, NULL);
}
}
Summary
| Aspect | Simple Version | Realistic Version |
|---|---|---|
| States | 3 flat states | 6 states in 2 hierarchies |
| Transitions | Manual timer events |
Automatic with <send delay> |
| Datamodel | null (no variables) |
ecmascript (cycle counter) |
| Modes | Single cycle | Normal + Emergency |
| Complexity | Beginner | Intermediate |
Key takeaways:
- States represent distinct configurations of your system
- Transitions change state in response to events
- Compound states organize related states hierarchically
- Delayed events enable time-based behavior
- Datamodels add variables and expressions
Files
| File | Description |
|---|---|
| traffic-light-simple.scxml | Simple 3-state version |
| traffic-light-simple-player.html | Interactive demo (simple) |
| traffic-light.scxml | Realistic European version |
| traffic-light-player.html | Interactive demo (realistic) |
Next Steps
- Visual Editor - Design state machines visually instead of writing XML
- Simulator - Debug and test interactively
- Generator - Command-line reference for code generation
- Arduino Tutorial - Build a physical traffic light with LEDs
- Java Target - Threading, executors, GraalVM Native Image
- C Target - Memory optimization, MISRA compliance, bare-metal