Skip to content

← Computing Further 6–8

How Does a Traffic Light Know What Comes Next?

Topic 20 · Grades 6–8 · State machines

The blocks from Grade 7 (if/else); the Python parts from Grade 8 (functions and the micro:bit board).

What this builds on from class
In this unit 8 sections
  1. Try it
  2. How it works
  3. Program it
  4. What your program shows
  5. Going further still
  6. Challenges
  7. Questions
  8. Where to go next

Stand at a busy junction and watch the lights: green, yellow, red, green again, all day and all night. The light doesn’t know how many cars went past this morning, or what day it is. Yet it never gets mixed up: after yellow always comes red, and the other road always gets its turn. How does a small box on a pole always know what comes next?

Here are the lights where a main road meets a side road. One box, the controller, runs both. Press Run the clock and watch. (The page’s lights change faster than real ones.)

Try it

Two roads, one controller

Run the clock and watch both lights. Under them is the controller’s whole list of instructions: the row it is on is marked.

Main roadSide road
State
main green
Time left
8.0 s
Changes
0

It starts in “main green”. Main road green, side road red. Press Run the clock.

The controller’s table: the next state
StateLastsTime’s up
main green8 smain yellow
main yellow3 sside green
side green6 sside yellow
side yellow3 smain green

To always know what comes next, what does the controller need to remember?

Pick an answer to see why.

You need: for the blocks, if/else from Grade 7. For the Python, functions from Grade 8, and for the board, the micro:bit from Grade 8’s Physical Computing.

Try it

Two roads, one controller

Watch the junction for a whole round, then answer these.

  • a.A driver on the main road sees three colors. How many different steps does the controller go through in one round?
  • b.While the main road’s light is red, what is the side road’s light doing?

A button for people on foot

On a road with no junction, people who want to cross press a button on the pole. This crossing is a first try. The cars keep green for as long as nobody presses. Press the button, and the cars get yellow, then red, and walkers get their green figure. Then the red figure flashes (don’t start to cross now), and the cars get green again.

Try it

A crossing with a button, first try

Run the clock. Press the button and watch a whole round. Then try to make the cars’ green as short as you can. Watch Cars’ last green.

CarsWalkersWAITButton box
State
go
Time left
–
Cars’ last green
–

It starts in “go”. Cars green, walkers wait. Press Run the clock.

The controller’s table: the next state
StateLastsTime’s upButton
gono limitstaystopping
stopping3 swalkstay
walk6 shurrystay
hurry4 sgostay

Walkers’ figures: RealMetzor, CC0, via Wikimedia Commons

How it works

Each step the controller can be in is called a state. The junction has four states. Things that happen to it, such as “the time is up” or “someone pressed the button”, are events. An event moves the controller from one state to another: a transition. A machine with a fixed number of states that moves between them on events is a finite state machine (FSM). (Grade 12’s The Limits of Computing studies them; you need none of it here.)

An FSM can be drawn as a state diagram: a circle for each state, an arrow for each transition, labeled with its event. Or it can be written as a transition table: a row for each state, a column for each event, and in each cell the next state. Both say exactly the same thing.

Try it

The junction as a state diagram

Press Time’s up four times. Follow the red arrow round the diagram, and the cell it uses in the table.

starttimetimetimetimemain greenmain yellowside greenside yellow
Transition table: the next state
StateTime’s up
main greenmain yellow
main yellowside green
side greenside yellow
side yellowmain green
State
main green
Inputs so far
0

It starts in main green. Press a button to give it an input.

That is the whole junction: four states and four arrows. Each state also has its seconds: the page uses 8 for main green, 6 for side green and 3 for each yellow. Real lights hold green much longer.

What the light remembers

Whatever an FSM needs to remember must be one of its states: it has no other memory. That tells us how to fix the crossing.

The cars must get at least 8 seconds of green. So “cars green” becomes two states: fresh (less than 8 seconds of green) and go (8 seconds or more). A press in go turns the cars yellow at once. A press in fresh must not, but it mustn’t be forgotten either, so it moves to a third green state, waiting, which lights the WAIT lamp on the button box. When the 8 seconds are up, waiting goes to yellow.

Try it

The fixed crossing

Press Button at once, then Time’s up until the cars get green again. Then try Time’s up first and Button after. Find each arrow you used.

starttimebuttonbuttontimetimetimetimefreshgowaitingstoppingwalkhurry

A press with no button arrow, or time in go, changes nothing.

Transition table: the next state
StateTime’s upButton
freshgowaiting
gogostopping
waitingstoppingwaiting
stoppingwalkstopping
walkhurrywalk
hurryfreshhurry
State
fresh
Inputs so far
0

It starts in fresh. Press a button to give it an input.

In the fixed crossing, the state is fresh and someone presses the button. What happens?

Pick an answer to see why.

Here is the fixed crossing’s table. A dash means the event changes nothing.

State Cars Walkers Lasts Time’s up → Button →
fresh green wait 8 s go waiting
go green wait no limit – stopping
waiting green, WAIT lit wait 8 s in all stopping –
stopping yellow wait 3 s walk –
walk red cross 6 s hurry –
hurry red red flashing 4 s fresh –

Program it

With blocks

The junction’s controller is one variable, state, and a choice: which state comes after this one? This program says each state and then moves to the next, six times.

Try it

The junction’s controller

Press Run and watch state in the trace table. Then make it go round three whole times. Which one number do you change?

What the program says and asks appears here.

state – no value
StepBlockstate

Press Run to start the program, or Step to do one block at a time.

Loading the blocks…

In Python (from Grade 8)

The same controller in Python is a function that answers one question: given a state, what comes next? A second function gives each state’s seconds, so the program can print a timetable.

def next_light(state):    if state == 'main green':        return 'main yellow'    elif state == 'main yellow':        return 'side green'    elif state == 'side green':        return 'side yellow'    else:        return 'main green' def seconds(state):    if state == 'main green':        return 8    elif state == 'side green':        return 6    else:        return 3 state = 'main green'clock = 0for change in range(9):    print('at', clock, 's:', state)    clock = clock + seconds(state)    state = next_light(state)

Output

at 0 s: main greenat 8 s: main yellowat 11 s: side greenat 17 s: side yellowat 20 s: main greenat 28 s: main yellowat 31 s: side greenat 37 s: side yellowat 40 s: main green

Every 20 seconds the same four states come round again. That repeat is called the light’s cycle.

The crossing’s table as a function

The crossing has two events, so its function takes the state and what happened. Each if is one cell of the table; anything else falls through to the last line and stays. The loop at the bottom prints the function as a table.

def next_state(state, event):    if state == 'fresh' and event == 'time':        return 'go'    if state == 'fresh' and event == 'button':        return 'waiting'    if state == 'go' and event == 'button':        return 'stopping'    if state == 'waiting' and event == 'time':        return 'stopping'    if state == 'stopping' and event == 'time':        return 'walk'    if state == 'walk' and event == 'time':        return 'hurry'    if state == 'hurry' and event == 'time':        return 'fresh'    return state       # anything else: stay states = ['fresh', 'go', 'waiting', 'stopping', 'walk', 'hurry']for state in states:    print(state, ': time →', next_state(state, 'time'),          ', button →', next_state(state, 'button'))

Output

fresh : time → go , button → waitinggo : time → go , button → stoppingwaiting : time → stopping , button → waitingstopping : time → walk , button → stoppingwalk : time → hurry , button → walkhurry : time → fresh , button → hurry

Now let your function run the lights. The widget asks your next_state about every state and event (twelve questions), makes its table from the answers, and runs the crossing on it.

Try it

Your next_state runs the crossing

Press Use your next_state(), then Run the clock and press the button at different moments. Then change the program above (Edit): make hurry go to go instead of fresh, and see what a press just after the walkers finish does.

CarsWalkersWAITButton box
State
fresh
Time left
8.0 s
Cars’ last green
–

Press Use your next_state() to run the light on your function’s table.

The table your next_state() gives: the next state
StateLastsTime’s upButton
fresh8 s??
gono limit??
waiting8 s in all??
stopping3 s??
walk6 s??
hurry4 s??

What your next_state() prints, and any error, appears here.

Walkers’ figures: RealMetzor, CC0, via Wikimedia Commons

On the micro:bit board

A real crossing’s controller is a small computer like the board in Grade 8. The same next_state runs it here. The display is the lights: on the left, the cars’ three lamps (red at the top, then yellow, then green); on the right, the walkers’ red figure at the top and green figure at the bottom; the dot in the middle is WAIT. A lit lamp is bright, a lamp that is off glows dimly. Button A is the walkers’ button.

Try it

The crossing on the board

Run it. Press A within the first 8 seconds: the middle dot lights up and the cars stay green. Then wait without pressing, and press A later: the cars get yellow at once.

AB0123VGND

Press Run to send the program to the board.

from microbit import * def next_state(state, event):    if state == 'fresh' and event == 'time':        return 'go'    if state == 'fresh' and event == 'button':        return 'waiting'    if state == 'go' and event == 'button':        return 'stopping'    if state == 'waiting' and event == 'time':        return 'stopping'    if state == 'stopping' and event == 'time':        return 'walk'    if state == 'walk' and event == 'time':        return 'hurry'    if state == 'hurry' and event == 'time':        return 'fresh'    return state def seconds(state):          # 0: no time limit    if state == 'fresh' or state == 'waiting':        return 8    if state == 'stopping':        return 3    if state == 'walk':        return 6    if state == 'hurry':        return 4    return 0 def picture(state):    if state == 'stopping':        return Image("11099:00099:99000:00011:11011")    if state == 'walk':        return Image("99011:00011:11000:00099:11099")    if state == 'hurry':        return Image("99099:00099:11000:00011:11011")    if state == 'waiting':        return Image("11099:00099:11900:00011:99011")    return Image("11099:00099:11000:00011:99011")   # fresh and go state = 'fresh'began = running_time()display.show(picture(state))while True:    waited = running_time() - began    event = ''    if button_a.was_pressed():        event = 'button'    elif seconds(state) > 0 and waited >= seconds(state) * 1000:        event = 'time'    if event != '':        new = next_state(state, event)        if new != state:            if new != 'waiting':    # keep counting the green                began = running_time()            state = new            display.show(picture(state))    sleep(50)

Serial output

The loop finds out whether an event has happened, asks next_state where to go, and shows that state’s picture. waited is how many milliseconds the light has been in this state. (On the way to waiting, began is left alone: the 8 seconds count from when the cars got green.)

What your program shows

In the widget you found the first try’s bug by luck. A program can find it for sure, by trying everything that could happen. This one makes every list of 10 events, time or button in each place: 2 × 2 × … × 2 = 1,024 lists. It runs each through a machine and keeps the shortest green the cars ever got. (A time event in go is skipped: no time runs out there.)

def first_try(state, event):    if state == 'go' and event == 'button':        return 'stopping'    if state == 'stopping' and event == 'time':        return 'walk'    if state == 'walk' and event == 'time':        return 'hurry'    if state == 'hurry' and event == 'time':        return 'go'    return state def next_state(state, event):    if state == 'fresh' and event == 'time':        return 'go'    if state == 'fresh' and event == 'button':        return 'waiting'    if state == 'go' and event == 'button':        return 'stopping'    if state == 'waiting' and event == 'time':        return 'stopping'    if state == 'stopping' and event == 'time':        return 'walk'    if state == 'walk' and event == 'time':        return 'hurry'    if state == 'hurry' and event == 'time':        return 'fresh'    return state def seconds(state):          # 0: no time limit    if state == 'fresh' or state == 'waiting':        return 8    if state == 'stopping':        return 3    if state == 'walk':        return 6    if state == 'hurry':        return 4    return 0 def cars_green(state):    return state == 'fresh' or state == 'go' or state == 'waiting' def check(machine, start):    shortest = 1000    used = []    for n in range(2 ** 10):           # every list of 10 events        state = start        green = 0        for k in range(10):            if n // 2 ** k % 2 == 0:                event = 'time'            else:                event = 'button'            if event == 'time' and seconds(state) == 0:                continue               # no time runs out here            if [state, event] not in used:                used.append([state, event])            new = machine(state, event)            if event == 'time' and cars_green(state):                green = green + seconds(state)            if cars_green(state) and not cars_green(new):                shortest = min(shortest, green)            if cars_green(new) and not cars_green(state):                green = 0            state = new    print(2 ** 10, 'lists of events,', len(used), 'different situations')    print('shortest green for the cars:', shortest, 's') check(first_try, 'go')check(next_state, 'fresh')

Output

1024 lists of events, 7 different situationsshortest green for the cars: 0 s1024 lists of events, 11 different situationsshortest green for the cars: 8 s

(n // 2 ** k % 2 is digit number k of n in binary, counted from the right and from 0, as in Grade 6’s Zeros and Ones: as n counts from 0 to 1,023, its ten binary digits run through every mix of time and button.)

  • The first try failed: in some list of events the cars got 0 seconds of green. Nobody had to think of “press just as it turns green”.
  • The fix passed: in all 1,024 lists, the cars always got at least 8 seconds.
  • And look at the situations. 1,024 different histories, but the light only ever met 11 different situations: a state and an event. That is the fixed crossing’s whole table: 6 states × 2 events, less the one that can’t happen (time running out in go). However long the light runs, for years, it will never meet a twelfth.

That is the power of a state machine. A light that remembered everything since it was switched on would face a new situation every moment, and nobody could test them all. A state machine faces only the cells of its table, so engineers can check every one and be sure: the cars always get their green, and walkers never get the green figure while cars have green. That is why traffic lights, lifts, railway signals and washing machines are run by state machines.

Going further still

An all-red state. Add a state called all red (2 seconds, both roads red) between main yellow and side green, and another between side yellow and main green. How many states does the junction have now? Change next_light and seconds and check the timetable.

A beep for people who can’t see the lights. Many crossings beep quickly while people may cross. On the board, add import music and, while the state is walk, music.pitch(1500, 40) each time round the loop.

Remember a press during hurry. In the fixed crossing, a press while the red figure flashes is forgotten: the person must press again. Which new state would remember it, and which arrows does it need?

Challenges

★ The crossing in blocks

Finish the first-try crossing. It asks for four events, typed as time or button, and must say the state it ends in. One rule is there already (go + button → stopping). Add the other three, then press Test.

Try it

★ The crossing in blocks

What the program says and asks appears here.

state – no value
event – no value
TestShould sayIt said
A whole roundgo
Press in hurryhurry
A late presswalk
Three presseswalk
No pressgo

Press Run to start the program, or Step to do one block at a time.

Loading the blocks…

Hint
Another hint
Answer

★★ Any machine from a table (Python, from Grade 8)

A table can be data instead of ifs: a list of rows, each [state, event, next state]. A pair with no row means stay. Write run_table(table, start, events) that starts in start, takes the events one by one, and gives back the state it ends in.

def run_table(table, start, events):    # table: a list of rows [state, event, next state]    return start gate = [['locked', 'ticket', 'unlocked'], ['unlocked', 'push', 'locked']]print(run_table(gate, 'locked', ['push', 'ticket']))

Try it

★★ Any machine from a table

  1. The crossing: someone presses early run_table([['fresh', 'time', 'go'], ['fresh', 'button', 'waiting'], ['go', 'button', 'stopping'], ['waiting', 'time', 'stopping'], ['stopping', 'time', 'walk'], ['walk', 'time', 'hurry'], ['hurry', 'time', 'fresh']], 'fresh', ['button', 'time', 'time']) Should give 'walk' Yours —
  2. The junction, five changes run_table([['main green', 'time', 'main yellow'], ['main yellow', 'time', 'side green'], ['side green', 'time', 'side yellow'], ['side yellow', 'time', 'main green']], 'main green', ['time', 'time', 'time', 'time', 'time']) Should give 'main yellow' Yours —
  3. A ticket gate run_table([['locked', 'ticket', 'unlocked'], ['unlocked', 'push', 'locked']], 'locked', ['push', 'ticket', 'ticket', 'push']) Should give 'locked' Yours —
Tests passed
— / 7
Challenge
★★ solved

Write your run_table in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Answer

★★★ The forgotten states (Python, from Grade 8)

When a table is changed by hand, a state can end up with no arrow leading to it, so the machine can never be in it. Write never_reached(table, start) that gives back the names in the table (in either place in a row) that the machine can never reach from start, in alphabetical order (sorted() does that). The rows are as in ★★.

def never_reached(table, start):    return [] night = [['main green', 'time', 'main yellow'], ['main yellow', 'time', 'side green'],         ['side green', 'time', 'side yellow'], ['side yellow', 'time', 'main green'],         ['night', 'time', 'night']]print(never_reached(night, 'main green'))

Try it

★★★ The forgotten states

  1. The crossing never_reached([['fresh', 'time', 'go'], ['fresh', 'button', 'waiting'], ['go', 'button', 'stopping'], ['waiting', 'time', 'stopping'], ['stopping', 'time', 'walk'], ['walk', 'time', 'hurry'], ['hurry', 'time', 'fresh']], 'fresh') Should give [] Yours —
  2. A night state nobody leads to never_reached([['main green', 'time', 'main yellow'], ['main yellow', 'time', 'side green'], ['side green', 'time', 'side yellow'], ['side yellow', 'time', 'main green'], ['night', 'time', 'night']], 'main green') Should give ['night'] Yours —
  3. A space typed after a name never_reached([['fresh', 'time', 'go'], ['fresh', 'button', 'waiting'], ['go', 'button', 'stopping'], ['waiting', 'time', 'stopping'], ['stopping', 'time', 'walk'], ['walk', 'time', 'hurry'], ['hurry', 'time', 'fresh ']], 'go') Should give ['fresh', 'waiting'] Yours —
Tests passed
— / 7
Challenge
★★★ solved

Write your never_reached in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Another hint
Answer

Questions

What is a state, in a state machine?

Pick an answer to see why.

A driver on the main road sees green, yellow and red. Why does the junction’s controller need four states?

Pick an answer to see why.

a.

Write the transition table for a ticket gate at a museum: it is locked until a ticket is scanned, and locks again after someone pushes through. The events are ticket and push.

Answer
b.

In the crossing’s first try, why could the cars get no green at all? How did two new states fix it?

Answer
c.

The check program tried 1,024 lists of events but found only 11 different situations. Explain why, and why this helps the people who build traffic lights.

Answer

Where to go next