Skip to content

Level crossing

A capstone: a railway level crossing controller with train detection, a barrier, flashing lamps, timeouts and a fault mode, built up step by step.

Where a road crosses a railway line at the same level, a controller has to stop the road traffic before every train: flash the red lamps, ring the bell, lower the barrier, wait for the train to pass, raise the barrier again. It must also notice when something goes wrong, and then fail safe: a barrier that does not move must not be mistaken for one that has.

This page builds such a controller in four steps. Each step is a complete chart you can run, and the last one uses most of what the Learn chapters teach: concurrent regions, superstates, variables and signals, entry and exit actions, count delays, termination, a strong abort and pre. It also reuses several patterns from this section: edges, counters and timeouts, mode switching and watchdogs.

The chart is meant to tick once a second, so a count of 3 ticks is 3 seconds.

Name Kind Meaning
occupied input bool the track circuit: a train is near or on the crossing
isDown, isUp input bool the barrier’s limit switches: fully down, fully up
restart input bool the maintainer’s key, to leave the fault mode
lower output bool the barrier motor: true lowers the barrier, false raises it
lampL, lampR output bool the two red lamps, which flash alternately
bell output signal one stroke of the bell
fault output bool the fault lamp for the maintainer
trains output int how many trains have passed

The behaviour, in words:

  1. When a train approaches, the lamps start flashing and the bell rings. After a warning time of three ticks, the barrier is lowered.
  2. When the barrier is down, the crossing is closed until the track is clear. Then the barrier is raised; the lamps keep flashing until it is up.
  3. If another train comes while the barrier is rising, the barrier goes down again at once.
  4. If the barrier does not reach its end position within four ticks, the crossing goes into a fault mode: fault lamp on, lamps flashing, barrier commanded down. Only the maintainer’s restart ends it.
  5. Both limit switches reporting at the same time is impossible for a healthy barrier: that is a fault too, and it must act at once.

Start with the heart of it, the sequence of the barrier, as one state per phase:

CrossingSequenceSimulation

Diagram

Diagram of CrossingSequenceStates: Open (initial), Warning, Lowering, Closed, Raising. Transitions: Open to Warning: occupied; Warning to Lowering: 3 true / lower = true; Lowering to Closed: isDown; Closed to Raising: !occupied / lower = false; Raising to Lowering: 1: occupied / lower = true; Raising to Open: 2: isUp.CrossingSequenceinput booloccupiedinput boolisDown, isUpoutput boollowerOpenWarningLoweringClosedRaisingoccupied3 true/ lower = trueisDown!occupied/ lower = false1: occupied/ lower = true2: isUp

Code

scchart CrossingSequence {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
output bool lower // barrier motor: true lowers, false raises
initial state Open
if occupied go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown go to Closed
state Closed
if !occupied do lower = false go to Raising
state Raising
if occupied do lower = true go to Lowering
if isUp go to Open
}
Tick 0 of 12
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it.
Timing diagram of CrossingSequence
Tickinput bool occupiedinput bool isDowninput bool isUpoutput bool lowerActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 12 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 12 ticks:

  1. occupied = false, isDown = false, isUp = trueTick 1: no train, the barrier is up (isUp), the crossing is Open.
  2. occupied = trueA train enters the track circuit: occupied. The crossing starts its warning time.
  3. Warning, one tick counted.
  4. Two.
  5. Three: the warning time is over, lower starts the barrier motor.
  6. isUp = falseThe barrier leaves its upper limit switch.
  7. isDown = trueIt reaches the lower one: isDown. The crossing is Closed.
  8. The train passes.
  9. occupied = falseThe track circuit is clear: the barrier is raised (lower false).
  10. isDown = falseThe barrier leaves the lower limit switch.
  11. isUp = trueIt is up: Open again.
  12. Open.
A train from tick 2 to tick 8. The barrier is commanded down in tick 5 and up in tick 9; the crossing is open again in tick 11.

Each state waits for one thing:

  • Open waits for a train.
  • Warning waits for the warning time: if 3 true fires three ticks after Warning was entered (tick 2), so in tick 5. The transition’s effect starts the motor.
  • Lowering waits for the lower limit switch, Closed for the track to clear, Raising for the upper limit switch.

Raising has two ways out, and their order matters: if occupied is written first, so a train that arrives in the very tick the barrier reaches the top still closes the crossing. Safety first, in priority order.

lower is a variable, not a signal: the motor command must hold from one tick to the next, and a variable keeps its value until something assigns it. Each transition that changes the command sets it.

Step 2: flashing lamps in a concurrent region

Section titled “Step 2: flashing lamps in a concurrent region”

The lamps flash all the time the crossing is not open, independently of what the barrier does. That is a job for a second region. Control decides whether to flash and says so in a variable, flashing; Lights does the flashing:

CrossingLightsSimulation

Diagram

Diagram of CrossingLightsStates: Dark (initial), Left (superstate), Right (superstate), Open (initial), Warning, Lowering, Closed, Raising. Transitions: Dark to Left: flashing; Left to Dark: 1: !flashing; Left to Right: 2:; Right to Dark: 1: !flashing; Right to Left: 2:; Open to Warning: occupied / flashing = true; Warning to Lowering: 3 true / lower = true; Lowering to Closed: isDown; Closed to Raising: !occupied / lower = false; Raising to Lowering: 1: occupied / lower = true; Raising to Open: 2: isUp / flashing = false.CrossingLightsinput booloccupiedinput boolisDown, isUpoutput boolloweroutput boollampL, lampRoutput signalbellboolflashing = falseLightsDarkLeftentry/ lampL = true; bellexit/ lampL = falseRightentry/ lampR = trueexit/ lampR = falseflashing1: !flashing2:1: !flashing2:ControlOpenWarningLoweringClosedRaisingoccupied/ flashing = true3 true/ lower = trueisDown!occupied/ lower = false1: occupied/ lower = true2: isUp/ flashing = false

Code

scchart CrossingLights {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
output bool lower // barrier motor: true lowers, false raises
output bool lampL, lampR // the two red lamps
output signal bell // one stroke of the bell
bool flashing = false // Control asks Lights to flash
region Lights {
initial state Dark
if flashing go to Left
state Left {
entry do lampL = true; bell
exit do lampL = false
}
if !flashing go to Dark
go to Right
state Right {
entry do lampR = true
exit do lampR = false
}
if !flashing go to Dark
go to Left
}
region Control {
initial state Open
if occupied do flashing = true go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown go to Closed
state Closed
if !occupied do lower = false go to Raising
state Raising
if occupied do lower = true go to Lowering
if isUp do flashing = false go to Open
}
}
Tick 0 of 12
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it.
Timing diagram of CrossingLights
Tickinput bool occupiedinput bool isDowninput bool isUpoutput bool loweroutput bool lampLoutput bool lampRoutput signal bellActive state of region LightsActive state of region Control
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 12 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 12 ticks:

  1. occupied = false, isDown = false, isUp = trueTick 1: open, lamps dark.
  2. occupied = trueA train: Control sets flashing and goes to Warning. In the same tick Lights reads flashing (after Control has written it) and lights the left lamp; the bell strikes.
  3. Right lamp.
  4. Left lamp, bell.
  5. Right lamp; the barrier starts down.
  6. isUp = falseLeft, bell.
  7. isDown = trueRight; the barrier is down.
  8. Left, bell.
  9. occupied = falseRight; the train has gone and the barrier rises. The lamps keep flashing while it moves.
  10. isDown = falseLeft, bell.
  11. isUp = trueThe barrier is up: Control clears flashing, and Lights goes dark in the same tick.
  12. Dark.
The lamps alternate from tick 2 to tick 10, the bell strikes with every left lamp, and both go dark in tick 11 when the barrier is up.

A few things make this work:

  • One writer per variable. Control alone writes flashing and lower; Lights alone writes lampL, lampR and emits bell. Neither writes what the other writes, so there is nothing for KIELER to object to (see latches for what happens otherwise).
  • Within a tick, writes come before reads. In tick 2, Control sets flashing and Lights reacts to it in the same tick: KIELER schedules the write before the read in the other region. In tick 11 the same happens in reverse, and the lamps go dark in the very tick the barrier is up.
  • A lamp is on exactly while its state is active. Left and Right switch their lamp on in an entry action and off in an exit action, as the one-shot on the edges page does. Whatever makes Lights leave a state, the lamp goes off.
  • The flasher is a frequency divider. Left and Right hand over to each other in every tick, like the blinker on the counters page, only faster. if !flashing go to Dark is written first, so stopping wins over flashing on.
  • A variable for the condition, a signal for the event. flashing is a condition that lasts many ticks, so it is a variable. A bell stroke is an event of one tick, so bell is a signal: present in the tick Left is entered, absent otherwise, with nothing to reset.

Now the failures. A barrier that is commanded down but never reports isDown (a broken motor, a stuck arm, a cut wire) must not leave the crossing waiting for ever with the road half open. Each moving phase gets a timeout, and a timeout leads to a fault mode:

CrossingFaultsSimulation

Diagram

Diagram of CrossingFaultsStates: Working (initial, superstate), Open (initial), Warning, Lowering, Closed, Raising, Stuck (final), Fault (superstate). Transitions: Open to Warning: occupied / flashing = true; Warning to Lowering: 3 true / lower = true; Lowering to Closed: 1: isDown; Lowering to Stuck: 2: 4 true; Closed to Raising: !occupied / lower = false; Raising to Lowering: 1: occupied / lower = true; Raising to Open: 2: isUp / flashing = false; Raising to Stuck: 3: 4 true; Working to Fault; Fault to Working: restart.CrossingFaultsinput booloccupiedinput boolisDown, isUpinput boolrestartoutput boolloweroutput boollampL, lampRoutput signalbelloutput boolfaultboolflashing = falseLightsControlWorkingOpenWarningLoweringClosedRaisingStuckoccupied/ flashing = true3 true/ lower = true1: isDown2: 4 true!occupied/ lower = false1: occupied/ lower = true2: isUp/ flashing = false3: 4 trueFaultentry/ fault = true; flashing = true; lower = trueexit/ fault = false; flashing = false; lower = falserestart

Code

scchart CrossingFaults {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
input bool restart // the maintainer's key
output bool lower // barrier motor: true lowers, false raises
output bool lampL, lampR // the two red lamps
output signal bell // one stroke of the bell
output bool fault // fault lamp for the maintainer
bool flashing = false // Control asks Lights to flash
@collapse
region Lights {
initial state Dark
if flashing go to Left
state Left {
entry do lampL = true; bell
exit do lampL = false
}
if !flashing go to Dark
go to Right
state Right {
entry do lampR = true
exit do lampR = false
}
if !flashing go to Dark
go to Left
}
region Control {
initial state Working {
initial state Open
if occupied do flashing = true go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown go to Closed
if 4 true go to Stuck
state Closed
if !occupied do lower = false go to Raising
state Raising
if occupied do lower = true go to Lowering
if isUp do flashing = false go to Open
if 4 true go to Stuck
final state Stuck
}
join to Fault
state Fault {
entry do fault = true; flashing = true; lower = true
exit do fault = false; flashing = false; lower = false
}
if restart go to Working
}
}
Tick 0 of 13
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it.
Timing diagram of CrossingFaults
Tickinput bool occupiedinput bool isDowninput bool isUpinput bool restartoutput bool loweroutput bool lampLoutput bool lampRoutput signal belloutput bool faultActive state of region LightsActive state of region ControlActive state of the region of Working
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 13 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 13 ticks:

  1. occupied = false, isDown = false, isUp = true, restart = falseTick 1: open.
  2. occupied = trueA train: warning.
  3. Warning.
  4. Warning.
  5. The barrier is commanded down.
  6. isUp = falseIt leaves the upper limit switch, and then it sticks: isDown never comes.
  7. Lowering: two ticks without isDown.
  8. Three.
  9. Four: if 4 true go to Stuck fires. Stuck is final, so Working terminates and join to Fault fires in the same tick. Fault lights the fault lamp, keeps the lamps flashing and keeps commanding the barrier down.
  10. occupied = falseThe train has passed, but Fault does not care: only the maintainer can end it.
  11. restart = true, isUp = trueThe maintainer has freed the barrier (it is up again) and turns the key: restart. Fault’s exit action clears the fault lamp, the lamps and the motor command, and Working starts again in Open.
  12. restart = falseOpen.
  13. Open.
The barrier sticks on its way down. Four ticks after the command (tick 9), the crossing goes into Fault; the maintainer’s restart in tick 11 ends it.

Lights has not changed since step 2, so this chart draws it collapsed: the annotation @collapse in front of the region does that (Comments and annotations), and the Code view and the trace still show it in full.

The normal sequence now lives in a superstate, Working, and both moving states have a count delay: if 4 true go to Stuck. Stuck is a final state, and the only one in Working. So reaching it makes Working terminate, and join to Fault fires in the same tick. Termination is the inside’s way of saying “I am done here”, which is exactly the message: the normal sequence has given up.

Fault is a mode with a safe state built into its entry action: fault lamp on, lamps flashing, barrier commanded down. Its exit action undoes all three when the maintainer turns the key. As on the mode switching page, the actions belong to the mode, so no path into or out of it can forget them.

In tick 10 the train has gone, and nothing happens: the fault mode ignores the track. Once the controller no longer trusts its barrier, only a person who has looked at it should decide that the crossing works again.

Step 4: a sensor check and a train counter

Section titled “Step 4: a sensor check and a train counter”

Two additions complete the controller:

LevelCrossingSimulation

Diagram

Diagram of LevelCrossingStates: Dark (initial), Left (superstate), Right (superstate), Working (initial, superstate), Open (initial), Warning, Lowering, Closed, Raising, Stuck (final), Fault (superstate). Transitions: Dark to Left: flashing; Left to Dark: 1: !flashing; Left to Right: 2:; Right to Dark: 1: !flashing; Right to Left: 2:; Open to Warning: occupied / flashing = true; Warning to Lowering: 3 true / lower = true; Lowering to Closed: 1: isDown; Lowering to Stuck: 2: 4 true; Closed to Raising: !occupied / lower = false; Raising to Lowering: 1: occupied / lower = true; Raising to Open: 2: isUp / flashing = false; Raising to Stuck: 3: 4 true; Working to Fault: 1: isUp && isDown; Working to Fault: 2:; Fault to Working: restart.LevelCrossinginput booloccupiedinput boolisDown, isUpinput boolrestartoutput boolloweroutput boollampL, lampRoutput signalbelloutput boolfaultoutput inttrains = 0boolflashing = falseduringpre(occupied) && !occupied/ trains++LightsDarkLeftentry/ lampL = true; bellexit/ lampL = falseRightentry/ lampR = trueexit/ lampR = falseflashing1: !flashing2:1: !flashing2:ControlWorkingOpenWarningLoweringClosedRaisingStuckoccupied/ flashing = true3 true/ lower = true1: isDown2: 4 true!occupied/ lower = false1: occupied/ lower = true2: isUp/ flashing = false3: 4 trueFaultentry/ fault = true; flashing = true; lower = trueexit/ fault = false; flashing = false; lower = false1: isUp && isDown2:restart

Code

scchart LevelCrossing {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
input bool restart // the maintainer's key
output bool lower // barrier motor: true lowers, false raises
output bool lampL, lampR // the two red lamps
output signal bell // one stroke of the bell
output bool fault // fault lamp for the maintainer
output int trains = 0 // trains that have passed
bool flashing = false // Control asks Lights to flash
during if pre(occupied) && !occupied do trains++
region Lights {
initial state Dark
if flashing go to Left
state Left {
entry do lampL = true; bell
exit do lampL = false
}
if !flashing go to Dark
go to Right
state Right {
entry do lampR = true
exit do lampR = false
}
if !flashing go to Dark
go to Left
}
region Control {
initial state Working {
initial state Open
if occupied do flashing = true go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown go to Closed
if 4 true go to Stuck
state Closed
if !occupied do lower = false go to Raising
state Raising
if occupied do lower = true go to Lowering
if isUp do flashing = false go to Open
if 4 true go to Stuck
final state Stuck
}
if isUp && isDown abort to Fault
join to Fault
state Fault {
entry do fault = true; flashing = true; lower = true
exit do fault = false; flashing = false; lower = false
}
if restart go to Working
}
}
Tick 0 of 26
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it.
Timing diagram of LevelCrossing
Tickinput bool occupiedinput bool isDowninput bool isUpinput bool restartoutput bool loweroutput bool lampLoutput bool lampRoutput signal belloutput bool faultoutput int trainsActive state of region LightsActive state of region ControlActive state of the region of Working
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 26 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 26 ticks:

  1. occupied = false, isDown = false, isUp = true, restart = falseTick 1: open, dark, no trains so far.
  2. occupied = trueA train: the lamps start flashing, the bell strikes, and the warning time begins.
  3. Warning.
  4. Warning.
  5. The warning time is over: the barrier is commanded down.
  6. isUp = falseThe barrier leaves its upper limit switch.
  7. isDown = trueDown: Closed.
  8. The train passes.
  9. occupied = falseThe track is clear: raise the barrier. occupied has just fallen, so the chart-level during action counts the train: trains is 1.
  10. isDown = falseThe barrier is moving up.
  11. isUp = trueUp: Open, the lamps go dark.
  12. Open.
  13. occupied = trueA second train.
  14. Warning.
  15. Warning.
  16. Lower.
  17. isUp = falseThe barrier leaves the upper switch, and sticks.
  18. Lowering: two ticks.
  19. Three.
  20. Four ticks without isDown: Stuck, so Working terminates and join to Fault fires: the fault lamp is on, the lamps flash, the barrier is commanded down.
  21. occupied = falseThe train passes (trains is 2); the fault stays.
  22. restart = true, isUp = trueThe maintainer has freed the barrier, it is up, and the key is turned: back to Open.
  23. restart = falseOpen.
  24. isDown = trueBoth limit switches report at once, which cannot happen with a healthy barrier. if isUp && isDown abort to Fault is a strong abort: in this tick nothing inside Working reacts, and the crossing goes straight to Fault.
  25. isDown = falseThe fault stays until the next restart.
  26. Fault.
The whole controller: a normal passage (ticks 2 to 11), a barrier that sticks (from tick 16, fault in tick 20), a restart (tick 22), and two limit switches reporting at once (tick 24). Actual size shows the diagram at full scale.

A sensor check with a strong abort. isUp && isDown means a broken switch or wiring. Whatever Working is doing, it must stop: if isUp && isDown abort to Fault is a strong abort, decided before anything inside Working reacts in that tick (tick 24), so no inner transition can act on the impossible readings. It is written before the join to: KIELER requires strong aborts to come first, and otherwise rejects the chart with Causality problem! and:

Strong abort transitions must have a higher priority than weak abort or termination transitions.

Note that the trigger reads only inputs. A strong abort whose trigger is written inside the aborted state causes trouble, as the watchdog page explains.

A train counter with pre. during if pre(occupied) && !occupied do trains++, written directly in the chart, counts the falling edges of occupied: the ticks in which a train has just cleared the crossing (ticks 9 and 21). It runs alongside both regions and writes only trains, so it disturbs nobody.

Here are the key ticks of the whole run, from KIELER’s simulation. Control is the state of the Control region after the tick; the inputs and outputs not listed are false (or, for bell, absent), and “(held)” marks an input that keeps the value it was given in an earlier tick. trains goes up to 1 in tick 9 and to 2 in tick 21:

Tick Inputs Control Outputs
1 isUp Open none
2 occupied, isUp (held) Warning lampL, bell
5 occupied (held), isUp (held) Lowering lower, lampR
7 occupied (held), isDown Closed lower, lampR
9 isDown (held) Raising lampR
11 isUp Open none
13 occupied, isUp (held) Warning lampL, bell
16 occupied (held), isUp (held) Lowering lower, lampR
20 occupied (held) Fault lower, lampR, fault
21 none Fault lower, lampL, bell, fault
22 isUp, restart Open none
24 isUp (held), isDown Fault lower, lampL, bell, fault
  • Concurrent regions (Lights, Control): the lamps flash independently of the barrier sequence.
  • A superstate with a final state and join to (Working, Stuck): the normal sequence gives up by terminating.
  • Count delays (Warning, Lowering, Raising): the warning time and the timeouts, counted in ticks.
  • Entry and exit actions (Left, Right, Fault): outputs that are on exactly while a state is active.
  • Variables (lower, flashing, fault): conditions that last.
  • A signal (bell): an event of one tick.
  • A strong abort (if isUp && isDown abort to Fault): a check that must win over everything inside.
  • pre (the train counter): the falling edge of occupied.
  • Priority (Raising, Left, Right): the safety-relevant transitions are written first.

A controller like this is only as good as the scenarios it has been run with. When you test your own:

  • Give every input in tick 1, and remember that inputs keep their value in KIELER’s simulator and in SCCharts Lab until you change them. A limit switch you set stays set.
  • Run the coincidences. A train arriving while the barrier rises; a train arriving in the tick the barrier reaches the top; a limit switch and a timeout in the same tick. Each is one line of priority in the chart, and one tick in a scenario.
  • Run the failures, not only the happy path: a barrier that never arrives, a switch that never releases, both switches at once.
  • Check the end of every phase, not only its start: do the lamps really go dark in the tick the barrier is up, or one tick later?

Troubleshooting has the same checklist for any chart, and the tools SCCharts Lab gives you for it: saved input traces, breakpoints and watches.

The debouncing page is worth a look too: real limit switches bounce, and this controller trusts them on the first reading.

  • Real time. Counting ticks is only time if the ticks are regular. KIELER also has clocks, which measure the time that the environment reports between ticks, so that a warning time can be written in seconds instead of ticks; Time covers them.
  • More tracks. With two tracks, each with its own track circuit, the crossing must close while either is occupied. Turn occupied into a local, compute it in one line, immediate during do occupied = occupied1 || occupied2, and the rest of the controller stays as it is. Count the trains per track, though: two trains that overlap make only one falling edge of the combined occupied.
  • Supervising the sensors. If the track circuit sends a regular sign of life (many do), a watchdog on it can send the crossing into Fault when it falls silent.

A safe restart

After restart, the starter goes straight to Open: the barrier is raised with the lamps already dark, so for a few seconds a moving barrier gives no warning. Change the chart so that leaving the fault mode keeps the lamps flashing while the barrier is raised, and the crossing is only Open once isUp confirms it. The same should happen when the controller starts up. To test it: start with every input false. The starter is Open with dark lamps; yours should flash until you set isUp (say in tick 3). Then set isDown as well (an impossible reading, so Fault at once), and in the next tick switch isUp off and turn the key (restart): the barrier is down and starts to rise. The starter’s lamps go dark; yours should keep flashing until isUp comes back.

Show the starter chart
scchart LevelCrossing {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
input bool restart // the maintainer's key
output bool lower // barrier motor: true lowers, false raises
output bool lampL, lampR // the two red lamps
output signal bell // one stroke of the bell
output bool fault // fault lamp for the maintainer
output int trains = 0 // trains that have passed
bool flashing = false // Control asks Lights to flash
during if pre(occupied) && !occupied do trains++
region Lights {
initial state Dark
if flashing go to Left
state Left {
entry do lampL = true; bell
exit do lampL = false
}
if !flashing go to Dark
go to Right
state Right {
entry do lampR = true
exit do lampR = false
}
if !flashing go to Dark
go to Left
}
region Control {
initial state Working {
initial state Open
if occupied do flashing = true go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown go to Closed
if 4 true go to Stuck
state Closed
if !occupied do lower = false go to Raising
state Raising
if occupied do lower = true go to Lowering
if isUp do flashing = false go to Open
if 4 true go to Stuck
final state Stuck
}
if isUp && isDown abort to Fault
join to Fault
state Fault {
entry do fault = true; flashing = true; lower = true
exit do fault = false; flashing = false; lower = false
}
if restart go to Working
}
}
Show a hint

There is already a state that raises the barrier with the lamps flashing. What must hold on every way into it, and could the controller start there?

Show a solution
scchart LevelCrossing {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
input bool restart // the maintainer's key
output bool lower // barrier motor: true lowers, false raises
output bool lampL, lampR // the two red lamps
output signal bell // one stroke of the bell
output bool fault // fault lamp for the maintainer
output int trains = 0 // trains that have passed
bool flashing = false // Control asks Lights to flash
during if pre(occupied) && !occupied do trains++
region Lights {
initial state Dark
if flashing go to Left
state Left {
entry do lampL = true; bell
exit do lampL = false
}
if !flashing go to Dark
go to Right
state Right {
entry do lampR = true
exit do lampR = false
}
if !flashing go to Dark
go to Left
}
region Control {
initial state Working {
state Open
if occupied do flashing = true go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown go to Closed
if 4 true go to Stuck
state Closed
if !occupied do lower = false go to Raising
initial state Raising {
entry do flashing = true; lower = false
}
if occupied do lower = true go to Lowering
if isUp do flashing = false go to Open
if 4 true go to Stuck
final state Stuck
}
if isUp && isDown abort to Fault
join to Fault
state Fault {
entry do fault = true; flashing = true; lower = true
exit do fault = false; flashing = false; lower = false
}
if restart go to Working
}
}

A quieter bell

Some crossings stop ringing once the barrier is down. Change the controller so that the bell rings in Warning and Lowering only, while the lamps keep flashing as before. Keep one writer per variable: Control knows the phase, Lights rings the bell. To test it: a train from tick 2 to tick 9, the barrier leaving the top in tick 6 and down from tick 7. The bell should strike in ticks 2, 4 and 6 only (the starter rings on in ticks 8, 10 and 12), and the lamps should flash until the barrier is up again.

Show the starter chart
scchart LevelCrossing {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
input bool restart // the maintainer's key
output bool lower // barrier motor: true lowers, false raises
output bool lampL, lampR // the two red lamps
output signal bell // one stroke of the bell
output bool fault // fault lamp for the maintainer
output int trains = 0 // trains that have passed
bool flashing = false // Control asks Lights to flash
during if pre(occupied) && !occupied do trains++
region Lights {
initial state Dark
if flashing go to Left
state Left {
entry do lampL = true; bell
exit do lampL = false
}
if !flashing go to Dark
go to Right
state Right {
entry do lampR = true
exit do lampR = false
}
if !flashing go to Dark
go to Left
}
region Control {
initial state Working {
initial state Open
if occupied do flashing = true go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown go to Closed
if 4 true go to Stuck
state Closed
if !occupied do lower = false go to Raising
state Raising
if occupied do lower = true go to Lowering
if isUp do flashing = false go to Open
if 4 true go to Stuck
final state Stuck
}
if isUp && isDown abort to Fault
join to Fault
state Fault {
entry do fault = true; flashing = true; lower = true
exit do fault = false; flashing = false; lower = false
}
if restart go to Working
}
}
Show a hint

Lights cannot test Control’s states, so let Control say when the bell may ring, in a variable it alone writes, as it already does with flashing.

Show a solution
scchart LevelCrossing {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
input bool restart // the maintainer's key
output bool lower // barrier motor: true lowers, false raises
output bool lampL, lampR // the two red lamps
output signal bell // one stroke of the bell
output bool fault // fault lamp for the maintainer
output int trains = 0 // trains that have passed
bool flashing = false // Control asks Lights to flash
bool ringing = false // Control asks Lights to ring the bell
during if pre(occupied) && !occupied do trains++
region Lights {
initial state Dark
if flashing go to Left
state Left {
entry do lampL = true
entry if ringing do bell
exit do lampL = false
}
if !flashing go to Dark
go to Right
state Right {
entry do lampR = true
exit do lampR = false
}
if !flashing go to Dark
go to Left
}
region Control {
initial state Working {
initial state Open
if occupied do flashing = true; ringing = true go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown do ringing = false go to Closed
if 4 true go to Stuck
state Closed
if !occupied do lower = false go to Raising
state Raising
if occupied do lower = true; ringing = true go to Lowering
if isUp do flashing = false go to Open
if 4 true go to Stuck
final state Stuck
}
if isUp && isDown abort to Fault
join to Fault
state Fault {
entry do fault = true; flashing = true; lower = true; ringing = false
exit do fault = false; flashing = false; lower = false
}
if restart go to Working
}
}
LevelCrossingPrediction

Diagram

Diagram of LevelCrossingStates: Dark (initial), Left (superstate), Right (superstate), Working (initial, superstate), Open (initial), Warning, Lowering, Closed, Raising, Stuck (final), Fault (superstate). Transitions: Dark to Left: flashing; Left to Dark: 1: !flashing; Left to Right: 2:; Right to Dark: 1: !flashing; Right to Left: 2:; Open to Warning: occupied / flashing = true; Warning to Lowering: 3 true / lower = true; Lowering to Closed: 1: isDown; Lowering to Stuck: 2: 4 true; Closed to Raising: !occupied / lower = false; Raising to Lowering: 1: occupied / lower = true; Raising to Open: 2: isUp / flashing = false; Raising to Stuck: 3: 4 true; Working to Fault: 1: isUp && isDown; Working to Fault: 2:; Fault to Working: restart.LevelCrossinginput booloccupiedinput boolisDown, isUpinput boolrestartoutput boolloweroutput boollampL, lampRoutput signalbelloutput boolfaultoutput inttrains = 0boolflashing = falseduringpre(occupied) && !occupied/ trains++LightsDarkLeftentry/ lampL = true; bellexit/ lampL = falseRightentry/ lampR = trueexit/ lampR = falseflashing1: !flashing2:1: !flashing2:ControlWorkingOpenWarningLoweringClosedRaisingStuckoccupied/ flashing = true3 true/ lower = true1: isDown2: 4 true!occupied/ lower = false1: occupied/ lower = true2: isUp/ flashing = false3: 4 trueFaultentry/ fault = true; flashing = true; lower = trueexit/ fault = false; flashing = false; lower = false1: isUp && isDown2:restart

Code

scchart LevelCrossing {
input bool occupied // track circuit: a train is near or on the crossing
input bool isDown, isUp // the barrier's limit switches
input bool restart // the maintainer's key
output bool lower // barrier motor: true lowers, false raises
output bool lampL, lampR // the two red lamps
output signal bell // one stroke of the bell
output bool fault // fault lamp for the maintainer
output int trains = 0 // trains that have passed
bool flashing = false // Control asks Lights to flash
during if pre(occupied) && !occupied do trains++
region Lights {
initial state Dark
if flashing go to Left
state Left {
entry do lampL = true; bell
exit do lampL = false
}
if !flashing go to Dark
go to Right
state Right {
entry do lampR = true
exit do lampR = false
}
if !flashing go to Dark
go to Left
}
region Control {
initial state Working {
initial state Open
if occupied do flashing = true go to Warning
state Warning
if 3 true do lower = true go to Lowering
state Lowering
if isDown go to Closed
if 4 true go to Stuck
state Closed
if !occupied do lower = false go to Raising
state Raising
if occupied do lower = true go to Lowering
if isUp do flashing = false go to Open
if 4 true go to Stuck
final state Stuck
}
if isUp && isDown abort to Fault
join to Fault
state Fault {
entry do fault = true; flashing = true; lower = true
exit do fault = false; flashing = false; lower = false
}
if restart go to Working
}
}

The final controller, with a second train arriving while the barrier rises (tick 10). What are these outputs after the ticks shown?

Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.

TickoccupiedisDownisUprestart
1falsefalsetruefalse
2truefalsetruefalse
3truefalsetruefalse
4truefalsetruefalse
5truefalsetruefalse
6truefalsefalsefalse
7truetruefalsefalse
8falsetruefalsefalse
9falsefalsefalsefalse
10truefalsefalsefalse
11truetruefalsefalse
12falsetruefalsefalse
13falsefalsefalsefalse
14falsefalsetruefalse
Plays the scenario on the diagram, then marks each answer.
Explanation

lower is true after tick 10 and false after tick 12; after tick 14 lampL is false and trains is 2. In tick 10 a second train arrives while the barrier rises: Raising tries if occupied first, so the barrier is lowered again at once. The track clears in tick 12, which counts the second train (the first cleared in tick 8) and raises the barrier. In tick 14 the barrier is up: Control goes to Open, and Lights switches the lamps off in the same tick.

While the barrier is lowering, both limit switches report at once. Which of the following happens in that tick?
Show the answer

Answer A: The strong abort fires before Lowering reacts, and the crossing enters Fault

A strong abort is checked before the inner behaviour of the tick, so Lowering’s if isDown never gets its turn. The abort is delayed only in the sense that it is not checked in the tick Working is entered; Working was entered long before.

Why does Control communicate with Lights through a variable, flashing, instead of letting Lights read Control’s states?
Show the answer

Answer B: Transitions and actions cannot test a state directly, and a variable written by Control is the clear way to pass the phase on

A trigger or an action cannot ask whether another region is in a given state. KIELER does have an expression for that, state(…), but it allows it only inside a method body, a rarely used escape hatch; in a trigger it says:

State access expressions are only permissible in methods bodies.

The clear way is a variable written by the region that owns the phase. Control owns flashing and writes it on the transitions where the warning starts and ends; Lights reads it. One writer, any number of readers.

Key ideas

  • Build a controller in steps: the main sequence first, then concurrent parts, then failures, then checks. Run every step.
  • One region per independent concern; one writer per variable; variables for conditions, signals for events.
  • Timeouts are count delays (or counters) on the states that wait for the outside world.
  • A superstate that terminates into a fault mode is a clean way for a sequence to give up; a strong abort is the way for a check to override it.
  • Put safe states into entry and exit actions, and safety-relevant transitions first.
  • Test the coincidences and the failures, not only the happy path.

That is the end of Patterns. The cheat sheet has all the syntax on one page, and the playground is the place for charts of your own.

Report a mistake on this page