Watchdogs
Noticing when something stops happening, and forcing a safe state with a strong abort.
Most failures are loud: a sensor reports an error, a limit switch trips. The dangerous ones are quiet. A sensor’s cable breaks and it stops reporting; a controller hangs and stops answering. To catch a silence, a system needs a watchdog: something that expects a regular sign of life, a heartbeat, and raises the alarm when the heartbeat has been missing for too long.
This page builds a watchdog, then uses it to supervise a controller: when the watchdog expires, a strong abort forces the controller into a safe state, whatever it was doing. It builds on counters and timeouts and aborts.
A watchdog with a count delay
Section titled “A watchdog with a count delay”The watchdog waits in Watching. A beat restarts the wait; three ticks without one, and it expires:
Diagram
Code
scchart Watchdog { input bool beat output bool alarm
initial state Watching if beat go to Watching if 3 true do alarm = true go to Expired
state Expired}| Tick | input bool beat | output bool alarm | Active state of the chart |
|---|
Guided tour, 8 ticks:
- beat = falseTick 1: watching. The entry tick does not count.
- beat = trueA beat: the self-loop
if beat go to Watchingre-enters the state, which restarts the count. - beat = falseQuiet: one.
- beat = trueA beat: restart.
- beat = falseQuiet: one.
- beat = falseTwo.
- beat = falseThree quiet ticks in a row: the watchdog expires and raises the alarm.
- beat = falseExpired.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly
- Warning, line 5, column 17KIELER says:
Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which count delays are extra regions and transitions, and warns whenever control could go round a circle there within one tick, without checking whether the triggers prevent it: here the circle runs throughWatching. The chart works as written.About this message
The trick is the self-loop. if beat go to Watching leaves Watching and enters it again, and every
entry restarts the count delay if 3 true. So if 3 true can only fire after three ticks in a row
without a beat. The beat transition is written first, so a beat in the very tick the count would
complete still wins. (KIELER warns Instantaneous loop detected! for this chart, and SCCharts Lab
Potential instantaneous loop.: a false alarm, as
for the stairwell light on the counters page.)
The same with a counter
Section titled “The same with a counter”A counter makes the time since the last beat visible, and lets the limit be anything you like:
Diagrams
Watchdog
WatchdogCounter
Code
Watchdog
scchart Watchdog { input bool beat output bool alarm
initial state Watching if beat go to Watching if 3 true do alarm = true go to Expired
state Expired}WatchdogCounter
scchart WatchdogCounter { input bool beat output bool alarm output int quiet = 0 // ticks since the last beat
initial state Watching if beat do quiet = 0 go to Watching if quiet == 2 do quiet++; alarm = true go to Expired do quiet++ go to Watching
state Expired}| Tick | input bool beat | Watchdog: output bool alarm | Watchdog: Active state of the chart | WatchdogCounter: output bool alarm | WatchdogCounter: output int quiet | WatchdogCounter: Active state of the chart |
|---|
Guided tour, 8 ticks, the same inputs for both charts:
- beat = falseTick 1: both watch. The count delay does not count the entry tick, and
quietis 0. - beat = trueA beat.
Watchdog’s self-loop re-entersWatching, which restarts its count;WatchdogCountersetsquietto 0. - beat = falseA tick without a beat:
quietis 1. - beat = trueA beat: both start again,
quietis 0. - beat = falseQuiet: 1.
- beat = falseQuiet: 2.
- beat = falseThe third quiet tick in a row: both expire and raise the alarm. (
quietshows 3.) - beat = falseExpired.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly (Watchdog)
- Warning in
Watchdog, line 5, column 17KIELER says:Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which count delays are extra regions and transitions, and warns whenever control could go round a circle there within one tick, without checking whether the triggers prevent it: here the circle runs throughWatching. The chart works as written.About this message
quiet shows how long the silence has lasted.quiet is reset by a beat and counted up otherwise; when it is 2 and the third quiet tick arrives,
the watchdog expires. The two versions agree tick for tick. A real watchdog’s limit comes from the sensor:
if it is specified to report every 50 ms and the chart ticks every 10 ms, a limit of a few more than 5
ticks tolerates a little jitter without waiting dangerously long.
Supervision: a strong abort into a safe state
Section titled “Supervision: a strong abort into a safe state”Now a controller to supervise. Running drives a motor on demand and counts its starts. A watchdog in
a concurrent region listens for the heartbeat of the sensor the controller depends on, and when it expires
it emits expired. Running has a strong abort on that signal into Safe, which raises the alarm.
restart brings both back.
Diagram
Code
scchart Supervisor { input bool beat, demand, restart output bool motor, alarm output int starts = 0 signal expired
region Watchdog { initial state Watching if beat go to Watching if 3 true do expired go to Expired
state Expired if restart go to Watching }
region Control { initial state Running { exit do motor = false
initial state Stopped if demand do motor = true; starts++ go to Started
state Started if !demand do motor = false go to Stopped } if expired abort to Safe
state Safe { entry do alarm = true exit do alarm = false } if restart go to Running }}| Tick | input bool beat | input bool demand | input bool restart | output bool motor | output bool alarm | output int starts | Active state of region Watchdog | Active state of region Control | Active state of the region of Running |
|---|
Guided tour, 12 ticks:
- beat = false, demand = false, restart = falseTick 1: watching and running, motor stopped.
- beat = trueA beat.
- beat = false, demand = trueDemand: the motor starts (
startsis 1). - beat = trueA beat.
- beat = false, demand = falseNo demand: the motor stops. From now on no more beats arrive.
- Second quiet tick.
- demand = trueThird quiet tick: the watchdog emits
expired. In the same tick demand rises again, but the strong abortif expired abort to Safeis decided beforeRunningreacts: the motor is not started,startsstays 1.Running’s exit action makes sure the motor is off, andSaferaises the alarm. - demand = falseSafe: the alarm stays on, whatever the inputs.
- restart = true, beat = true
restart, with the beats back: the watchdog watches again and the controller returns toRunning. - restart = falseRunning.
- beat = falseQuiet.
- Quiet.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly
- Warning, line 8, column 19KIELER says:
Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which count delays are extra regions and transitions, and warns whenever control could go round a circle there within one tick, without checking whether the triggers prevent it: here the circle runs throughWatching. The chart works as written.About this message
Safe.Look at tick 7. The watchdog expires, and in the same tick demand rises. Two things happen in that tick:
- The watchdog region emits
expired, andRunning’s abort, which readsexpiredafter the emission (every emission of a signal comes before every test of it in another region), fires. - Because the abort is strong, it is decided before anything inside
Runningreacts.Stoppednever sees the demand: no start,startsstays 1.
Running’s exit action, exit do motor = false, runs although Running is left by a strong abort: exit
actions run on every kind of exit, as mode switching
explains. Put “switch everything off” into the exit action of the supervised state, and every way out of
it, normal or forced, ends with the outputs safe.
Strong, not weak
Section titled “Strong, not weak”With a weak abort, if expired go to Safe, the inside of Running would finish its tick first:
Diagrams
Supervisor
SupervisorWeak
Code
Supervisor
scchart Supervisor { input bool beat, demand, restart output bool motor, alarm output int starts = 0 signal expired
region Watchdog { initial state Watching if beat go to Watching if 3 true do expired go to Expired
state Expired if restart go to Watching }
region Control { initial state Running { exit do motor = false
initial state Stopped if demand do motor = true; starts++ go to Started
state Started if !demand do motor = false go to Stopped } if expired abort to Safe
state Safe { entry do alarm = true exit do alarm = false } if restart go to Running }}SupervisorWeak
scchart SupervisorWeak { input bool beat, demand, restart output bool motor, alarm output int starts = 0 signal expired
region Watchdog { initial state Watching if beat go to Watching if 3 true do expired go to Expired
state Expired if restart go to Watching }
region Control { initial state Running { exit do motor = false
initial state Stopped if demand do motor = true; starts++ go to Started
state Started if !demand do motor = false go to Stopped } if expired go to Safe
state Safe { entry do alarm = true exit do alarm = false } if restart go to Running }}| Tick | input bool beat | input bool demand | input bool restart | Supervisor: output bool motor | Supervisor: output bool alarm | Supervisor: output int starts | Supervisor: Active state of region Watchdog | Supervisor: Active state of region Control | Supervisor: Active state of the region of Running | SupervisorWeak: output bool motor | SupervisorWeak: output bool alarm | SupervisorWeak: output int starts | SupervisorWeak: Active state of region Watchdog | SupervisorWeak: Active state of region Control | SupervisorWeak: Active state of the region of Running |
|---|
Guided tour, 12 ticks, the same inputs for both charts:
- beat = false, demand = false, restart = falseTick 1: both charts watch and run; the motors are stopped.
- beat = trueA beat.
- beat = false, demand = trueDemand: both motors start, and
startsis 1. - beat = trueA beat.
- beat = false, demand = falseNo demand: the motors stop. From now on no more beats arrive.
- The second tick without a beat.
- demand = trueThe third quiet tick: both watchdogs emit
expired, in the very tick in which demand rises again.Supervisor’s strong abort is decided beforeRunningreacts: no start,startsstays 1.SupervisorWeak’s weak abort letsRunningfinish its tick first: the motor is started and counted (startsis 2), and only then isRunningleft, its exit action switching the motor off again. Both end the tick inSafe, alarm on. - demand = false
Safe: both alarms stay on. - restart = true, beat = true
restart, with the beats back: both watch again and return toRunning. - restart = falseRunning.
- beat = falseQuiet.
- Quiet.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly (Supervisor and SupervisorWeak)
- Warning in
SupervisorandSupervisorWeak, line 8, column 19KIELER says:Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which count delays are extra regions and transitions, and warns whenever control could go round a circle there within one tick, without checking whether the triggers prevent it: here the circle runs throughWatching. The chart works as written.About this message
starts becomes 2) before the controller goes to Safe.Strong and Weak are starts in Supervisor and in SupervisorWeak. Both raise the alarm in
tick 7:
| Tick | Inputs | Strong | Weak |
|---|---|---|---|
| 3 | demand |
1 | 1 |
| 6 | none | 1 | 1 |
| 7 | demand |
1 | 2 |
| 8 | none | 1 | 2 |
In SupervisorWeak, the motor is started in tick 7 and switched off again by Running’s exit action in
the same tick. motor ends the tick off in both charts, but in SupervisorWeak a start command was
issued and counted (starts is 2), and in a real system perhaps sent to hardware, by a controller that
was already known to be blind. For supervision, use a strong abort: once the watchdog has spoken, nothing
inside gets another move.
Keep the watchdog outside
Section titled “Keep the watchdog outside”It is tempting to put the watchdog inside the state it supervises, as one more region of Running.
KIELER rejects this version:
Diagram
Code
scchart WatchdogInside { input bool beat, demand output bool motor, alarm output int starts = 0 signal expired
initial state Running { exit do motor = false
region Watchdog { initial state Watching if beat go to Watching if 3 true do expired go to Expired
state Expired }
region Work { initial state Stopped if demand do motor = true; starts++ go to Started
state Started if !demand do motor = false go to Stopped } } if expired abort to Safe
state Safe { entry do alarm = true }}KIELER rejects this chart
- Line 26, column 6The strong abort from
RunningtoSafereadsexpired, which is emitted insideRunningin the same tick, and KIELER cannot put the two in an orderKIELER says:The SCG is NOT asc-schedulable!
In each tick KIELER has to check this abort and run the inside ofRunningin some order, and here each waits for the other. (KIELER accepts some charts whose transitions out of a superstate read a value written inside it, but not this one.) What always works: emitexpiredin a region next toRunning, outside it, or read last tick's value withpre(expired).About this message
Running itself. KIELER rejects the chart.The messages are Can't schedule from … and The SCG is NOT asc-schedulable!; SCCharts Lab adds:
Circular dependency prevents scheduling this tick.The abort out of Running reads expired, and now the watchdog emits expired inside Running, on the
transition with the count delay. KIELER has to check the abort and run the inside of Running in some
order, and with that count delay it cannot find one. It is not a matter of the abort being strong:
KIELER rejects the weak version, if expired go to Safe, in the same way.
Without a count delay, KIELER does accept a watchdog inside the state it aborts. Here is the counter
watchdog as a region of Running, with beats in ticks 2 and 4 as for Supervisor:
Diagram
Code
scchart WatchdogInsideCounter { input bool beat, demand output bool motor, alarm output int starts = 0, quiet = 0 signal expired
initial state Running { exit do motor = false
region Watchdog { initial state Watching if beat do quiet = 0 go to Watching if quiet == 2 do quiet++; expired go to Expired do quiet++ go to Watching
state Expired }
region Work { initial state Stopped if demand do motor = true; starts++ go to Started
state Started if !demand do motor = false go to Stopped } } if expired abort to Safe
state Safe { entry do alarm = true }}| Tick | input bool beat | input bool demand | output bool motor | output bool alarm | output int starts | output int quiet | Active state of the chart | Active state of region Watchdog (in Running) | Active state of region Work (in Running) |
|---|
Guided tour, 10 ticks:
- beat = false, demand = falseTick 1: watching and running, motor stopped.
- beat = trueA beat.
- beat = false, demand = trueDemand: the motor starts (
startsis 1).quietcounts the first tick without a beat. - beat = trueA beat:
quietis 0 again. From now on no more beats arrive. - beat = falseFirst quiet tick.
- Second quiet tick.
- Third quiet tick: the watchdog emits
expired.Workchecks the abort after that emission and stops at once, inStarted, with the motor on.Watchdogchecked it before its own emission, so it carries on intoExpired, andRunningis not left. expiredwas a signal, and it is gone.Watchdogsits inExpired, which never stops, soRunningis never left: noSafe, no alarm.- demand = falseThe demand ends, but
Workhas stopped inStarted, so nothing switches the motor off.Running’s exit action would, butRunningis never left. - beat = trueA beat changes nothing: the motor runs, and the alarm never comes.
Note: This strong abort reads expired, which the inside of Running writes: in a tick in which expired changes, some inner regions can be aborted while others still react
- Note, line 27, column 3KIELER checks a strong abort separately in each inner region, before that region reacts, so a region that writes
expiredhas already read the old value. Use a weak abort (go to) or a termination (join to) for conditions computed insideRunning.
Work stops, with the motor on. Running is never left: no Safe, no alarm, and the motor runs on.That is worse than a rejection. KIELER checks a strong abort separately in each region inside the
state, as that region runs. The Work region checks it after the watchdog has emitted expired, and
stops at once, in Started. The Watchdog region checks it before its own emission, sees nothing, and
moves on to Expired. Running is left only once every region inside it has stopped, and from tick 8
on expired, a signal, is absent again. So Running is never left, its exit action never runs, and
nothing switches the motor off.
The rule is simple: the watchdog and the thing it supervises are siblings, never parent and child. As a
sibling region, the watchdog emits expired before Running’s abort reads it, and the abort stops
Running in one piece.
Try it
Section titled “Try it”An early warning
Extend the counter watchdog with an output late that is true in the second tick in a row without a
beat, one tick before the alarm, and false otherwise (also once the alarm is up). To test it: a beat in
tick 2 and none in ticks 3 and 4 make late true in tick 4; a beat in tick 5 clears it; after that,
silence makes it true in tick 7, and in tick 8 the alarm goes up and late is false again.
Show the starter chart
scchart EarlyWarning { input bool beat output bool alarm, late output int quiet = 0 // ticks since the last beat
initial state Watching if beat do quiet = 0 go to Watching if quiet == 2 do quiet++; alarm = true go to Expired do quiet++ go to Watching
state Expired}Show a solution
scchart EarlyWarning { input bool beat output bool alarm, late output int quiet = 0 // ticks since the last beat
initial state Watching if beat do quiet = 0; late = false go to Watching if quiet == 2 do quiet++; late = false; alarm = true go to Expired do quiet++; late = quiet == 2 go to Watching
state Expired}Two sensors
The starter watches sensor A. Add a second watchdog for sensor B (beatB), so that the alarm goes up when
either sensor falls silent. Both watchdogs may emit the same signal. To test it: both sensors beat in
tick 2, then only A (ticks 4 and 6). The starter never raises the alarm; yours should raise it in tick 5,
the third tick without a beat from B, and restart with both beating (tick 8) should clear it.
Show the starter chart
scchart TwoSensors { input bool beatA, beatB, restart output bool alarm signal expired
region WatchA { initial state Watching if beatA go to Watching if 3 true do expired go to Expired
state Expired if restart go to Watching }
region Alarm { initial state Normal if expired go to Raised
state Raised { entry do alarm = true exit do alarm = false } if restart go to Normal }}Show a solution
scchart TwoSensors { input bool beatA, beatB, restart output bool alarm signal expired
region WatchA { initial state Watching if beatA go to Watching if 3 true do expired go to Expired
state Expired if restart go to Watching }
region WatchB { initial state Watching if beatB go to Watching if 3 true do expired go to Expired
state Expired if restart go to Watching }
region Alarm { initial state Normal if expired go to Raised
state Raised { entry do alarm = true exit do alarm = false } if restart go to Normal }}Check your understanding
Section titled “Check your understanding”Diagram
Code
scchart Watchdog { input bool beat output bool alarm
initial state Watching if beat go to Watching if 3 true do alarm = true go to Expired
state Expired}The count-delay watchdog, with beats in ticks 2, 5 and 9. What is alarm after ticks 5, 7, 8 and 9?
Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.
| Tick | beat |
|---|---|
| 1 | false |
| 2 | true |
| 3 | false |
| 4 | false |
| 5 | true |
| 6 | false |
| 7 | false |
| 8 | false |
| 9 | true |
Explanation
alarm is false after ticks 5 and 7 and true after ticks 8 and 9. The beat in tick 5 comes after two
quiet ticks, just in time, and restarts the count. Ticks 6, 7 and 8 are quiet, so the watchdog expires
in tick 8. The beat in tick 9 is too late: Expired has no transitions, so the alarm stays up.
Show the answer
Answer B: Watchdog checks the abort before emitting expired, and by tick 8 it is gone
KIELER checks a strong abort separately in each region inside the state. Work checks it after the
emission and stops, which also shows that a signal from inside can trigger the abort. Watchdog checks
it before, so it is not stopped, and from tick 8 on expired is absent. Running is left only once all
its regions have stopped, so it never is. With the watchdog as a sibling region, expired is emitted
before Running’s abort reads it, and the abort stops Running in one piece.
Show the answer
Answer C: if 3 !beat counts quiet ticks, not quiet ticks in a row
A count delay counts the ticks in which its trigger holds, consecutive or not. if 3 !beat would expire
after any three quiet ticks since Watching was entered, even if beats came in between: with a beat in
every other tick (ticks 2, 4 and 6), it expires in tick 7. KIELER accepts it without a word. The
self-loop re-enters Watching on every beat, and re-entering restarts the count.
Key ideas
- A watchdog expects a heartbeat and expires when it has been missing for N ticks: a count delay restarted by a self-loop, or a counter reset by each beat.
- Give beats as single ticks when you test: inputs keep their value in the simulator.
- Supervise with a strong abort on the watchdog’s signal: in the expiry tick nothing inside the supervised state moves.
- Exit actions run on strong aborts too; use them to switch outputs off.
- Keep the watchdog in a sibling region, never inside the state it aborts.
Next, Sharing a resource: two clients, one device, and an arbiter that decides who goes first.