Time
Clocks that measure real time, timeouts, periodic behaviour, and what to do when your platform only gives you a timer input.
A tick is a step of logical time: it takes no time at all, and nothing in a chart says how far apart two ticks are. Real controllers still have to wait real time. A stairwell light stays on for five seconds, an LED blinks twice a second, a sensor is read every half second. SCCharts gives you two ways to do this. Timed SCCharts add clock variables that measure the time the environment reports to the chart. The other way is to count the pulses of a timer input that your hardware provides. This chapter covers both, starting with clocks.
Clocks and deltaT
Section titled “Clocks and deltaT”Diagram
Code
scchart EggTimer { output bool ring = false clock t
initial state Boiling if t >= 3000 do ring = true go to Done
final state Done}| Tick | output bool ring | local float t | Active state of the chart |
|---|
Guided tour, 5 ticks:
- deltaT = 1000Tick 1 enters
Boiling. A clock does not advance in the tick its region starts, sot= 0 althoughdeltaTsays 1000 ms passed. - deltaT = 1000One second later.
Boilingwas already active, so the clock advances first:t= 1000. Then the transition is tested: 1000 is not ≥ 3000. - deltaT = 1000
t= 2000. - deltaT = 1000
t= 3000, and the transition sees the new value in the same tick:ring= true, the chart moves toDoneand terminates. - deltaT = 1000The chart has terminated: nothing changes any more.
t stay at 0 in tick 1 and reach 3000 in tick 4.clock t declares a clock: a float variable that starts at 0 and measures time. Declaring a clock
adds two variables to the chart’s interface:
- the input
deltaT: how much time has passed since the previous tick. The environment supplies it. - the output
sleepT: how long the environment may wait before the next tick (more on this below).
Time passes only through deltaT. At the start of every tick, before any transition is tested,
KIELER adds deltaT to the clock. It does so in whatever state the clock’s region is in (the region
whose transitions test the clock), whether or not that state looks at the clock itself. The one tick in
which the clock does not move is the tick in which that region is entered: tick 1 for the egg timer.
| Tick | deltaT |
t |
State after | ring |
|---|---|---|---|---|
| 1 | 1000 | 0 | Boiling | false |
| 2 | 1000 | 1000 | Boiling | false |
| 3 | 1000 | 2000 | Boiling | false |
| 4 | 1000 | 3000 | Done | true |
So the timeout if t >= 3000 fires in the tick in which the clock reaches 3000, three ticks after
Boiling was entered. Clocks have no built-in unit: t counts in whatever unit the environment uses
for deltaT. This chapter uses milliseconds.
In a simulator you play the environment, so you type in how much time passed. If deltaT stays 0, no
clock ever moves and nothing times out.
Use >=, never ==
Section titled “Use >=, never ==”Time arrives in steps of deltaT, and a clock seldom lands exactly on the number you wrote. Here is the
same egg timer ticked every 1300 ms, next to a copy that tests t == 3000:
Diagrams
EggTimer
EggTimerEqual
Code
EggTimer
scchart EggTimer { output bool ring = false clock t
initial state Boiling if t >= 3000 do ring = true go to Done
final state Done}EggTimerEqual
scchart EggTimerEqual { output bool ring = false clock t
initial state Boiling if t == 3000 do ring = true go to Done
final state Done}| Tick | input float deltaT | EggTimer: output bool ring | EggTimer: output float sleepT | EggTimer: Active state of the chart | EggTimerEqual: output bool ring | EggTimerEqual: output float sleepT | EggTimerEqual: Active state of the chart |
|---|
Guided tour, 4 ticks, the same inputs for both charts:
- deltaT = 1300Both egg timers are ticked every 1300 ms (
deltaT= 1300). Tick 1 entersBoiling:t= 0. - deltaT = 1300
t= 1300 in both. - deltaT = 1300
t= 2600. (sleepTdiffers too:EggTimerasks to be woken in 400 ms, exactly at its timeout, while==givesEggTimerEqualno bound to aim for. More onsleepTbelow.) - deltaT = 1300
t= 3900: the clock has jumped over 3000.EggTimer:t >= 3000holds, soringcomes on, 900 ms late.EggTimerEqual:t == 3000was never true and never will be, soringstays false.
>= notices, 900 ms late; == never fires.t >= 3000 fires in the first tick at or after 3000, so at most one step of deltaT late. t == 3000
is true only if a tick happens to fall on 3000 exactly, and here none does. t > 3000 works, but it is
false at exactly 3000: with steps of 1000 it fires a tick later than >= does. Write every timeout as
clock >= value.
Resetting a clock
Section titled “Resetting a clock”A clock starts at 0 when the state that declares it is entered. For a clock declared at the top of the chart that is tick 1, and from then on it never stops: it keeps counting in states that never look at it. To time an interval, set the clock back to 0 on the transition that starts the interval:
Diagram
Code
scchart StairLight { input bool button output bool light = false clock t
initial state Off if button do light = true; t = 0 go to On
state On if button do t = 0 go to On if t >= 5000 do light = false go to Off}| Tick | input bool button | output bool light | local float t | Active state of the chart |
|---|
Guided tour, 11 ticks:
- button = false, deltaT = 1000Each tick is one second later than the last (
deltaT = 1000). Tick 1 entersOff. - button = truePressed. The root’s clock has been running since tick 1 (it says 1000 here before the transition), so the transition resets it:
t= 0,lighton. - button = falseThe clock advances in
Onjust as it did inOff:t= 1000. t= 2000.- button = truePressed again. The clock first advances to 3000, then the self-loop (priority 1) resets it:
t= 0. The five seconds start again. - button = false
t= 1000. t= 2000.t= 3000.t= 4000.t= 5000: the timeout fires in this tick andlightgoes off, five seconds after the second press.- The clock is never stopped: it keeps counting in
Off(t= 6000). That is why the transition intoOnresets it.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly
- Warning, line 6, column 17KIELER says:
Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which clocks (their timeouts andsleepT) 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 throughOnandOff. The chart works as written.About this message
The transition into On resets the clock, and so does the self-loop that a second press takes. In
tick 5 the clock first advances to 3000, then the self-loop sets it back to 0. (The
counters pattern builds the same light without a
clock, by counting ticks.)
Forgetting the reset is the classic bug of timed charts. Without it the clock keeps the time since tick 1, and once it has passed a bound, every later test against that bound is already true:
Diagrams
Blink
BlinkForgetful
Code
Blink
scchart Blink { output bool led = false clock t
initial state Off if t >= 500 do led = true; t = 0 go to On
state On if t >= 500 do led = false; t = 0 go to Off}BlinkForgetful
scchart BlinkForgetful { output bool led = false clock t
initial state Off if t >= 500 do led = true go to On
state On if t >= 500 do led = false go to Off}| Tick | input float deltaT | Blink: output bool led | Blink: output float sleepT | Blink: Active state of the chart | BlinkForgetful: output bool led | BlinkForgetful: output float sleepT | BlinkForgetful: Active state of the chart |
|---|
Guided tour, 8 ticks, the same inputs for both charts:
- deltaT = 250Both blinkers are ticked every 250 ms. Tick 1:
t= 0, the LED is off. - deltaT = 250
t= 250 in both. - deltaT = 250
t= 500: both LEDs come on.Blinkresets the clock on the way (t= 0);BlinkForgetfuldoes not (t= 500). - deltaT = 250
Blink:t= 250.BlinkForgetful:t= 750, already past 500, so its LED goes off again after only 250 ms. - deltaT = 250
Blink:t= 500, so the LED goes off after 500 ms on, andt= 0 again.BlinkForgetful:t= 1000, and its LED is on again: from now on, every timeout test it makes is already true. - deltaT = 250
Blinkstays off (t= 250);BlinkForgetfulflips off. - deltaT = 250
Blinkcomes on after 500 ms off;BlinkForgetfulflips on. - deltaT = 250
Blinkkeeps its rhythm, 500 ms on and 500 ms off.BlinkForgetfulflips in every tick.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly (Blink and BlinkForgetful)
- Warning in
BlinkandBlinkForgetful, line 5, column 17KIELER says:Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which clocks (their timeouts andsleepT) 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 throughOffandOn. The chart works as written.About this message
Blink: 500 ms on, 500 ms off. BlinkForgetful: the clock is never reset, so after the first timeout the LED flips in every tick.Local clocks
Section titled “Local clocks”A clock declared inside a superstate belongs to that state. Like any local variable it is set to its initial value, 0, every time the state is entered, so restarting the timer is the same as entering the state again:
Diagram
Code
scchart StairLightLocal { input bool button output bool light = false
initial state Off if button go to On
state On { clock t entry do light = true exit do light = false
initial state Lit if t >= 5000 go to Expired
final state Expired } if button go to On join to Off}| Tick | input bool button | input float deltaT | output bool light | output float sleepT | Active state of the chart | Active state of the region of On |
|---|
Guided tour, 11 ticks:
- button = false, deltaT = 1000One second per tick. Tick 1 enters
Off. - button = truePressed:
Onis entered, its entry action switches thelighton, and its own clock starts at 0. - button = falseThe clock of
Onis at 1000. It is declared insideOn, so the trace does not show it. - 2000.
- button = truePressed again: the self-loop leaves
On(the exit action switches the light off) and enters it afresh (the entry action switches it on again). EnteringOnre-initialises its clock to 0, with no reset written anywhere. - button = false1000.
- 2000.
- 3000.
- 4000.
- 5000:
Lit→Expiredreaches the final state,join to Offfires in the same tick and the exit action switches thelightoff. - In
Off. There is no clock running now: it belongs toOn.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly
- Warning, line 13, column 19KIELER says:
Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which entry and exit actions and clocks (their timeouts andsleepT) 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 throughOn,LitandExpired. The chart works as written.About this message
On. A second press re-enters On, which restarts its clock.There is no t = 0 anywhere in this version. The clock of On is a local of a nested state, not part
of the chart’s interface, so the trace does not show it.
One clock per region
Section titled “One clock per region”KIELER handles a clock only if the transitions that test it all belong to the states of one region:
Diagram
Code
scchart TwoTimers { output bool tea = false, egg = false clock t
region { initial state Steeping if t >= 3000 do tea = true go to Ready state Ready }
region { initial state Boiling if t >= 5000 do egg = true go to Ready state Ready }}KIELER rejects this chart
- Line 13, column 10Clock
tis read by several concurrent regions ofTwoTimers.KIELER says:Cannot handle concurrent timed automata using the same clock. Add @ClocksUseSD annotation for experimental support based on schduling directives.
Declare one clock per region and reset it on entry. KIELER's timed automata give every clock to one region, which advances it.About this message
t.KIELER rejects two arrangements:
-
One clock tested in two concurrent regions, as above. Official KIELER says (the misspelling is KIELER’s)
Cannot handle concurrent timed automata using the same clock. Add @ClocksUseSD annotation for experimental support based on schduling directives.and SCCharts Lab says
Clock t is read by several concurrent regions of TwoTimers. -
One clock tested by a superstate’s own transitions and inside it. Official KIELER says
Cannot handle hierarchical timed automata. Add @ClocksUseSD annotation for experimental support based on SDs.and SCCharts Lab says
Clock t is read both by Outer and inside it.
Charts in which a timeout lies on a cycle of states, such as the blinker and the stair light, compile
with a warning: Instantaneous loop detected! in official KIELER, and in SCCharts Lab
Potential instantaneous loop through the timed transitions on t.The loop analysis does not know that a clock needs time to reach a timeout. The warning is advisory: the chart is accepted and runs as shown here, as long as no timeout is 0 (more about this warning).
sleepT: when to wake the chart
Section titled “sleepT: when to wake the chart”On a real device a chart does not have to be ticked all the time. With dynamic ticks the
environment runs a tick when an input changes or when the chart asks for one, measures how long it
waited, and passes that time in as the next deltaT. The chart asks through sleepT: at the end of
every tick it is the time until the nearest timeout of the active states, or 1000 if that is further
away or there is no timeout. Take the egg timer ticked every 1300 ms again. Predict sleepT after
ticks 2 and 3, check, then open the explanation:
Diagram
Code
scchart EggTimer { output bool ring = false clock t
initial state Boiling if t >= 3000 do ring = true go to Done
final state Done}deltaT is 1300 in every tick, and the timeout is t >= 3000.
Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.
| Tick | deltaT |
|---|---|
| 1 | 1300 |
| 2 | 1300 |
| 3 | 1300 |
Explanation
After tick 2 the clock is at 1300, so the timeout is 1700 away. That is more than 1000, so sleepT is
1000. After tick 3 the clock is at 2600, 400 short of the timeout: sleepT is 400. Tick by tick:
| Tick | deltaT |
t |
sleepT |
Why |
|---|---|---|---|---|
| 1 | 1300 | 0 | 1000 | 3000 to go; at most 1000 |
| 2 | 1300 | 1300 | 1000 | 1700 to go |
| 3 | 1300 | 2600 | 400 | 400 to go |
| 4 | 1300 | 3900 | 1000 | fired; no timeout left |
If the environment had woken the chart 400 ms after tick 3, ring would have come on exactly at 3000.
You can play such an environment on this site: next to the deltaT field of a timed chart, Advance by
sleepT sets deltaT to the sleepT of the tick shown and steps.
A chart running on a fixed tick instead (every 10 ms, say) ignores sleepT and passes deltaT = 10 in
every tick. From chart to C shows both kinds of main loop.
Annotations on the chart change the defaults. They go before scchart:
@SimulateSleep: in every tick after the first,deltaTis set to the previoussleepT, so the chart pretends that exactly the requested time passed.@DefaultSleep 5000: the largest (and the default)sleepTis 5000 instead of 1000.@IntegerClockType: clocks,deltaTandsleepTareintinstead offloat.@NoSleep: nosleepToutput at all.
@SimulateSleep is the easiest way to watch a timed chart in a simulator, because you never type a
deltaT. The chart jumps from timeout to timeout:
Diagram
Code
@SimulateSleepscchart BlinkSleep { output bool led = false clock t
initial state Off if t >= 500 do led = true; t = 0 go to On
state On if t >= 300 do led = false; t = 0 go to Off}| Tick | output bool led | local float t | Active state of the chart |
|---|
Guided tour, 5 ticks:
- deltaT = 0Tick 1 enters
Off. The next timeout is 500 ms away:sleepT= 500. - With
@SimulateSleepthe chart assumes that exactly the requested time passed:deltaTis set to the lastsleepT(500), so the timeout fires at once and the LED comes on. On wants 300 ms:sleepT= 300. deltaT= 300: off again.deltaT= 500: on. Every tick is a timeout; nothing happens in between.deltaT= 300: off.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly
- Warning, line 6, column 17KIELER says:
Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which clocks (their timeouts andsleepT) 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 throughOffandOn. The chart works as written.About this message
deltaT is filled in from sleepT.Doing something every so often: period
Section titled “Doing something every so often: period”period 500 in a state makes the inside of that state run only in ticks in which another 500 time units
have passed. KIELER gives the state a hidden clock and guards every action and transition inside with
“has the period elapsed?”. The default soft reset keeps any excess time; @HardReset before the
period throws it away:
Diagrams
Sampler
SamplerHard
Code
Sampler
scchart Sampler { output int samples = 0
initial state Run { period 500
initial state Measure do samples++ go to Measure }}SamplerHard
scchart SamplerHard { output int samples = 0
initial state Run { @HardReset period 500
initial state Measure do samples++ go to Measure }}| Tick | input float deltaT | Sampler: output int samples | Sampler: output float sleepT | Sampler: Active state of the chart | Sampler: Active state of the region of Run | SamplerHard: output int samples | SamplerHard: output float sleepT | SamplerHard: Active state of the chart | SamplerHard: Active state of the region of Run |
|---|
Guided tour, 12 ticks, the same inputs for both charts:
- deltaT = 200Tick 1 enters
Runin both charts, and both hidden period clocks start at 0. A plainperiodwaits for the first period, so nothing inside runs:samples= 0. - deltaT = 200Both period clocks are at 200. Nothing runs.
- deltaT = 200400. Still nothing.
- deltaT = 200600: the period has elapsed, so the inside runs once in both:
samples= 1.Sampler’s soft reset subtracts 500 and keeps the 100 ms excess;SamplerHard’s hard reset sets its clock back to 0. - deltaT = 200Soft: 100 + 200 = 300. Hard: 200.
- deltaT = 200Soft: 500, so
Samplertakes its second sample, exactly one period after the first. Hard: 400, nothing yet. - deltaT = 200Soft: 200. Hard: 600, so
SamplerHardtakes its second sample, 600 ms after its first. - deltaT = 200Soft: 400. Hard: 200.
- deltaT = 200Soft: 600, the third sample, with 100 kept. Hard: 400.
- deltaT = 400From now on the ticks come 400 ms apart. Soft: 100 + 400 = 500, the fourth sample. Hard: 800, its third.
- deltaT = 400400 on both clocks: no sample in either.
- deltaT = 400800 on both clocks, and both take a sample. After the same 2800 ms the soft reset has 5 samples and the hard reset 4: with the hard reset, every late tick shifts all the following ones.
Warning: KIELER warns about a potential instantaneous loop here, but compiles and runs the chart correctly (Sampler and SamplerHard)
- Warning in
SamplerandSamplerHardKIELER says:Instantaneous loop detected!
KIELER's loop check works on the compiled chart, in which periods 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. The chart works as written.About this message
The hidden period clocks are not part of the interface, so the simulation cannot show them. Here they
are at the end of each tick (deltaT is 200 up to tick 9, then 400):
| Tick | Soft clock | samples |
Hard clock | samples |
|---|---|---|---|---|
| 1 | 0 | 0 | 0 | 0 |
| 2 | 200 | 0 | 200 | 0 |
| 3 | 400 | 0 | 400 | 0 |
| 4 | 100 | 1 | 0 | 1 |
| 5 | 300 | 1 | 200 | 1 |
| 6 | 0 | 2 | 400 | 1 |
| 7 | 200 | 2 | 0 | 2 |
| 8 | 400 | 2 | 200 | 2 |
| 9 | 100 | 3 | 400 | 2 |
| 10 | 0 | 4 | 0 | 3 |
| 11 | 400 | 4 | 400 | 3 |
| 12 | 300 | 5 | 0 | 4 |
With the soft reset a late tick lets the next period come sooner, so in the long run the inside runs once per period. With the hard reset every late tick shifts all the following ones.
Two more details. Like a delayed transition, a plain period waits for the first period: nothing inside
runs in the tick the state is entered, not even the entry action of its initial state.
immediate period 500 lets the inside run in the entry tick as well. And the guard also covers the
transitions leaving the state that declares the period.
Without clocks: counting a timer input
Section titled “Without clocks: counting a timer input”Not every platform has dynamic ticks, or reports elapsed time at all. A common alternative is a
hardware timer that turns an input on once per period, say second, true in one tick each second. The
chart then counts pulses, for instance with a count delay (see chapter 11):
Diagram
Code
scchart PulseTimer { input bool start, second output bool done = false
initial state Idle if start do done = false go to Waiting
state Waiting if 2 second do done = true go to Idle}| Tick | input bool start | input bool second | output bool done | Active state of the chart |
|---|
Guided tour, 12 ticks:
- start = false, second = falseThe environment ticks the chart every 500 ms, and the hardware timer makes
secondtrue in every other tick (ticks 2, 4, 6, ...): one pulse per second. - second = true, start = falseA pulse, but nobody is waiting yet.
- second = false, start = true
start:Idle→Waiting. This happens just before a pulse: the next one comes in the very next tick. - second = true, start = falseThe first counted pulse, only 500 ms after the start.
- second = false, start = falseNo pulse.
- second = true, start = falseThe second pulse:
if 2 secondfires anddoneis true, 1.5 s after the start. - second = false, start = falseBack in
Idle. - second = true, start = true
startagain, this time in a tick that has a pulse. A delayed transition does not count the tick its state was entered in, so this pulse does not count. - second = false, start = falseNo pulse.
- second = true, start = falseFirst counted pulse.
- second = false, start = falseNo pulse.
- second = true, start = falseSecond pulse:
done, now 2 s after the start. The same chart waited 1.5 s once and 2 s once.
if 2 second waits 1.5 s the first time and 2 s the second.The count starts when Waiting is entered, and that can be anywhere between two pulses. Started just
before a pulse, the first pulse counts almost at once; started in a tick that has a pulse, that pulse is
not counted, because a delayed transition ignores the tick its state was entered in. So “wait for 2
pulses” lasts somewhere between one and two periods. In general, counting n pulses of a timer with
period P waits between (n - 1) * P and n * P. This is the phase problem: your interval is not
aligned with the timer’s.
How much this matters depends on the job. A faster timer shrinks the error: counting 20 pulses of
100 ms is never more than 100 ms short of two seconds. If the wait must be exact, begin it on a pulse:
let the transition into the waiting state wait for second as well (through an intermediate state, if
the request can come between pulses). The pulse in the entry tick is not counted, so the wait is then
exactly n periods long, at the price of up to one period before it begins. And if the platform can
report elapsed time, clocks avoid the problem altogether.
Counting the chart’s own ticks measures time only if the environment ticks the chart at a fixed rate. Under dynamic ticks it measures nothing useful.
Try it
Section titled “Try it”Hand dryer
The starter runs the fan only while hands is true. Change it so that the fan keeps running for 2000 ms
after the hands are taken away, and keeps running (with a fresh 2000 ms afterwards) if they come back in
the meantime. Use a clock. Once the chart has a clock, it gets a deltaT input: test it with
deltaT = 500.
Show the starter chart
scchart HandDryer { input bool hands output bool fan = false
initial state Idle if hands do fan = true go to Drying
state Drying if !hands do fan = false go to Idle}Show a solution
scchart HandDryer { input bool hands output bool fan = false clock t
initial state Idle if hands do fan = true go to Drying
state Drying if !hands do t = 0 go to Afterrun
state Afterrun if hands go to Drying if t >= 2000 do fan = false go to Idle}Check your understanding
Section titled “Check your understanding”Show the answer
Answer B: Tick 8
The effect sets the clock to 0 in tick 5, after any advance in that tick. The clock then reaches 100 in tick 6, 200 in tick 7 and 300 in tick 8. It advances before the transition is tested, so the timeout fires in tick 8.
Show the answer
Answer A: 300
sleepT asks for the nearest timeout of the active states: 300 time units. Neither bound is more than
1000 away, so the default maximum does not come into it.
Key ideas
clock tmeasures time. The environment reports elapsed time in the inputdeltaT; the chart asks to be woken through the outputsleepT.- A clock advances by
deltaTat the start of every tick, before any transition is tested, whatever state its region is in; only in the tick its region is entered does it stay put. Write timeouts ast >= value. - Reset a clock (
t = 0) when a waiting period starts, or declare it in the superstate whose entry should restart it. One clock per region. period vruns a state’s inside once per period. The default is the soft reset, which keeps excess time;@HardResetdrops it.- Counting a timer input waits between
(n - 1)andnperiods, depending on the phase.
Next, Methods and code effects: a few lines of code in a transition, with if,
loops and local variables.