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.
What the controller does
Section titled “What the controller does”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:
- When a train approaches, the lamps start flashing and the bell rings. After a warning time of three ticks, the barrier is lowered.
- 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.
- If another train comes while the barrier is rising, the barrier goes down again at once.
- 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
restartends it. - 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.
Step 1: the barrier sequence
Section titled “Step 1: the barrier sequence”Start with the heart of it, the sequence of the barrier, as one state per phase:
Diagram
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 | input bool occupied | input bool isDown | input bool isUp | output bool lower | Active state of the chart |
|---|
Guided tour, 12 ticks:
- occupied = false, isDown = false, isUp = trueTick 1: no train, the barrier is up (
isUp), the crossing isOpen. - occupied = trueA train enters the track circuit:
occupied. The crossing starts its warning time. - Warning, one tick counted.
- Two.
- Three: the warning time is over,
lowerstarts the barrier motor. - isUp = falseThe barrier leaves its upper limit switch.
- isDown = trueIt reaches the lower one:
isDown. The crossing isClosed. - The train passes.
- occupied = falseThe track circuit is clear: the barrier is raised (
lowerfalse). - isDown = falseThe barrier leaves the lower limit switch.
- isUp = trueIt is up:
Openagain. - Open.
Each state waits for one thing:
Openwaits for a train.Warningwaits for the warning time:if 3 truefires three ticks afterWarningwas entered (tick 2), so in tick 5. The transition’s effect starts the motor.Loweringwaits for the lower limit switch,Closedfor the track to clear,Raisingfor 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:
Diagram
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 | input bool occupied | input bool isDown | input bool isUp | output bool lower | output bool lampL | output bool lampR | output signal bell | Active state of region Lights | Active state of region Control |
|---|
Guided tour, 12 ticks:
- occupied = false, isDown = false, isUp = trueTick 1: open, lamps dark.
- occupied = trueA train:
Controlsetsflashingand goes toWarning. In the same tickLightsreadsflashing(afterControlhas written it) and lights the left lamp; the bell strikes. - Right lamp.
- Left lamp, bell.
- Right lamp; the barrier starts down.
- isUp = falseLeft, bell.
- isDown = trueRight; the barrier is down.
- Left, bell.
- occupied = falseRight; the train has gone and the barrier rises. The lamps keep flashing while it moves.
- isDown = falseLeft, bell.
- isUp = trueThe barrier is up:
Controlclearsflashing, andLightsgoes dark in the same tick. - Dark.
A few things make this work:
- One writer per variable.
Controlalone writesflashingandlower;Lightsalone writeslampL,lampRand emitsbell. 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,
ControlsetsflashingandLightsreacts 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.
LeftandRightswitch their lamp on in an entry action and off in an exit action, as the one-shot on the edges page does. Whatever makesLightsleave a state, the lamp goes off. - The flasher is a frequency divider.
LeftandRighthand over to each other in every tick, like the blinker on the counters page, only faster.if !flashing go to Darkis written first, so stopping wins over flashing on. - A variable for the condition, a signal for the event.
flashingis a condition that lasts many ticks, so it is a variable. A bell stroke is an event of one tick, sobellis a signal: present in the tickLeftis entered, absent otherwise, with nothing to reset.
Step 3: timeouts and a fault mode
Section titled “Step 3: timeouts and a fault mode”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:
Diagram
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 | input bool occupied | input bool isDown | input bool isUp | input bool restart | output bool lower | output bool lampL | output bool lampR | output signal bell | output bool fault | Active state of region Lights | Active state of region Control | Active state of the region of Working |
|---|
Guided tour, 13 ticks:
- occupied = false, isDown = false, isUp = true, restart = falseTick 1: open.
- occupied = trueA train: warning.
- Warning.
- Warning.
- The barrier is commanded down.
- isUp = falseIt leaves the upper limit switch, and then it sticks:
isDownnever comes. - Lowering: two ticks without
isDown. - Three.
- Four:
if 4 true go to Stuckfires.Stuckis final, soWorkingterminates andjoin to Faultfires in the same tick.Faultlights the fault lamp, keeps the lamps flashing and keeps commanding the barrier down. - occupied = falseThe train has passed, but
Faultdoes not care: only the maintainer can end it. - 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, andWorkingstarts again inOpen. - restart = falseOpen.
- Open.
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:
Diagram
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 | input bool occupied | input bool isDown | input bool isUp | input bool restart | output bool lower | output bool lampL | output bool lampR | output signal bell | output bool fault | output int trains | Active state of region Lights | Active state of region Control | Active state of the region of Working |
|---|
Guided tour, 26 ticks:
- occupied = false, isDown = false, isUp = true, restart = falseTick 1: open, dark, no trains so far.
- occupied = trueA train: the lamps start flashing, the bell strikes, and the warning time begins.
- Warning.
- Warning.
- The warning time is over: the barrier is commanded down.
- isUp = falseThe barrier leaves its upper limit switch.
- isDown = trueDown:
Closed. - The train passes.
- occupied = falseThe track is clear: raise the barrier.
occupiedhas just fallen, so the chart-level during action counts the train:trainsis 1. - isDown = falseThe barrier is moving up.
- isUp = trueUp:
Open, the lamps go dark. - Open.
- occupied = trueA second train.
- Warning.
- Warning.
- Lower.
- isUp = falseThe barrier leaves the upper switch, and sticks.
- Lowering: two ticks.
- Three.
- Four ticks without
isDown:Stuck, soWorkingterminates andjoin to Faultfires: the fault lamp is on, the lamps flash, the barrier is commanded down. - occupied = falseThe train passes (
trainsis 2); the fault stays. - restart = true, isUp = trueThe maintainer has freed the barrier, it is up, and the key is turned: back to
Open. - restart = falseOpen.
- isDown = trueBoth limit switches report at once, which cannot happen with a healthy barrier.
if isUp && isDown abort to Faultis a strong abort: in this tick nothing insideWorkingreacts, and the crossing goes straight toFault. - isDown = falseThe fault stays until the next
restart. - Fault.
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 |
What each part of the language does here
Section titled “What each part of the language does here”- 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 ofoccupied.- Priority (
Raising,Left,Right): the safety-relevant transitions are written first.
Testing it
Section titled “Testing it”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.
Variations
Section titled “Variations”- 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
occupiedinto 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 combinedoccupied. - Supervising the sensors. If the track circuit sends a regular sign of life (many do), a
watchdog on it can send the crossing into
Faultwhen it falls silent.
Try it
Section titled “Try it”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 }}Check your understanding
Section titled “Check your understanding”Diagram
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.
| Tick | occupied | isDown | isUp | restart |
|---|---|---|---|---|
| 1 | false | false | true | false |
| 2 | true | false | true | false |
| 3 | true | false | true | false |
| 4 | true | false | true | false |
| 5 | true | false | true | false |
| 6 | true | false | false | false |
| 7 | true | true | false | false |
| 8 | false | true | false | false |
| 9 | false | false | false | false |
| 10 | true | false | false | false |
| 11 | true | true | false | false |
| 12 | false | true | false | false |
| 13 | false | false | false | false |
| 14 | false | false | true | false |
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.
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.
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.