Traffic Light Tutorial

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

bash
scxml-gen traffic-simple.scxml -t java -o TrafficLight.java --package com.example
java
TrafficLight sm = new TrafficLight();
sm.start();                    // Enters "red"
sm.send("timer");         // → green
sm.send("timer");         // → yellow
sm.send("timer");         // → red (cycle complete)

JavaScript:

bash
scxml-gen traffic-simple.scxml -t javascript -o traffic.js
javascript
import { TrafficLight } from './traffic.js';

const sm = new TrafficLight();
sm.start();
sm.send('timer');  // Cycles through states

C:

bash
scxml-gen traffic-simple.scxml -t c -o traffic.c
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 - normal and emergency contain child states
  • Emergency mode - Yellow light blinks on/off continuously
  • Cycle counter - Tracks how many complete cycles have occurred

The SCXML Source

xml
<?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:

xml
<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:

xml
<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:

xml
<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 states
  • send <event> - Send an event manually
  • scheduled - Show pending delayed events
  • poll - Process delayed events (advance time)
  • vars - Show datamodel variables
  • state - Show active states

Code Generation Examples

Java with Automatic Timing

bash
scxml-gen traffic-light.scxml -t java -o TrafficLight.java --package com.traffic
java
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

bash
scxml-gen traffic-light.scxml -t javascript -o traffic-light.js
javascript
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

bash
scxml-gen traffic-light.scxml -t c -o traffic_light.c --amalgamate
c
#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:

  1. States represent distinct configurations of your system
  2. Transitions change state in response to events
  3. Compound states organize related states hierarchically
  4. Delayed events enable time-based behavior
  5. 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