Mode switching
Switching between operating modes safely: request the switch, wait for a safe point, then commit.
Most devices have modes: a coffee machine serves coffee or cleans itself, a printer prints or runs maintenance, a robot works or charges. In SCCharts a mode is naturally a superstate with its own behaviour inside, and switching modes is a transition out of it. The hard part is not the transition. It is when to take it: a mode is often in the middle of something that must not be cut short, and whatever it switched on must be switched off.
This page develops a coffee machine with two modes, Serving and Cleaning, in three steps: a switch
that cuts in at any time, the same with exit actions that clean up, and a switch that waits for a safe
point. At the end it adds an emergency stop, which must not wait. It builds on
superstates, aborts, actions and the
latch pattern.
Two modes and an abrupt switch
Section titled “Two modes and an abrupt switch”Serving makes coffee: a brew request switches the heater on, the machine heats for a tick, then pours
for two ticks, then switches the heater off and counts the cup. Cleaning rinses for a tick, flushes
for a tick, and then hands back to Serving with a join to. The first attempt switches to cleaning
whenever clean is pressed:
initial state Serving { ...}if clean go to Cleaningif clean go to Cleaning leaves Serving from whatever inner state it is in. In CoffeeAbrupt, a press in
the middle of a brew (tick 3) abandons the cup and leaves the heater on:
Diagrams
CoffeeAbrupt
CoffeeExitAction
Code
CoffeeAbrupt
scchart CoffeeAbrupt { input bool brew, clean output bool heater, pump, rinsing output int cups = 0
initial state Serving { initial state Ready if brew do heater = true go to Heating
state Heating go to Pouring
state Pouring { entry do pump = true exit do pump = false } if 2 true do heater = false; cups++ go to Ready } if clean go to Cleaning
state Cleaning { entry do rinsing = true exit do rinsing = false
initial state Rinse go to Flush
state Flush go to Rinsed
final state Rinsed } join to Serving}CoffeeExitAction
scchart CoffeeExitAction { input bool brew, clean output bool heater, pump, rinsing output int cups = 0
initial state Serving { exit do heater = false
initial state Ready if brew do heater = true go to Heating
state Heating go to Pouring
state Pouring { entry do pump = true exit do pump = false } if 2 true do heater = false; cups++ go to Ready } if clean go to Cleaning
state Cleaning { entry do rinsing = true exit do rinsing = false
initial state Rinse go to Flush
state Flush go to Rinsed
final state Rinsed } join to Serving}| Tick | input bool brew | input bool clean | CoffeeAbrupt: output bool heater | CoffeeAbrupt: output bool pump | CoffeeAbrupt: output bool rinsing | CoffeeAbrupt: output int cups | CoffeeAbrupt: Active state of the chart | CoffeeAbrupt: Active state of the region of Serving | CoffeeAbrupt: Active state of the region of Cleaning | CoffeeExitAction: output bool heater | CoffeeExitAction: output bool pump | CoffeeExitAction: output bool rinsing | CoffeeExitAction: output int cups | CoffeeExitAction: Active state of the chart | CoffeeExitAction: Active state of the region of Serving | CoffeeExitAction: Active state of the region of Cleaning |
|---|
Guided tour, 15 ticks, the same inputs for both charts:
- brew = false, clean = falseTick 1: both machines enter
Servingand wait inReady. - brew = true
brew: in both, the heater goes on and the machine moves toHeating. - brew = false, clean = true
cleanin the middle of the brew.if clean go to Cleaningis a weak abort, so the inside ofServingfinishes its tick first:Heatingmoves on toPouring, whose entry action starts the pump. ThenServingis left, andPouring’s exit action stops the pump again. OnlyCoffeeExitActionhas an exit action onServingthat switches the heater off. InCoffeeAbruptthe heater stays on. - clean = falseBoth machines are cleaning, and the half-made cup has been abandoned.
CoffeeAbrupt’s heater is still on. - Cleaning finishes and
Servingstarts again.cupsis still 0 in both. - Back in
Ready.CoffeeAbrupt’s heater is still on, with nothing to heat. - brew = trueA proper brew in both.
- brew = falsePouring.
- Pouring.
- The cup is done in both:
cupsis 1.CoffeeAbrupt’s heater is off at last, switched off by the end of this brew. - Ready.
- brew = true, clean = true
brewandcleanin the same tick. The weak abort’s last wish letsReadystart the brew in both machines (heater on), and only then isServingleft forCleaning.CoffeeExitAction’s exit action switches the heater off again before the tick ends;CoffeeAbrupt’s stays on. - brew = false, clean = falseCleaning.
- Back to
Serving. - Ready: the heater is on in
CoffeeAbruptand off inCoffeeExitAction.
heater stays on in CoffeeAbrupt until the next brew happens to switch it off. CoffeeExitAction switches it off on the way out.Abrupt is heater in CoffeeAbrupt, Exit is heater in CoffeeExitAction, and cups is the
same in both:
| Tick | Inputs | Abrupt | Exit | cups |
|---|---|---|---|---|
| 2 | brew |
true | true | 0 |
| 3 | clean |
true | false | 0 |
| 6 | none | true | false | 0 |
| 10 | none | false | false | 1 |
| 12 | brew, clean |
true | false | 1 |
| 15 | none | true | false | 1 |
Two things went wrong in CoffeeAbrupt:
- Nothing switched the heater off. The heater was switched on by a transition inside
Serving, and only another transition insideServingswitches it off. WhenServingis left early, that transition never runs. The pump is fine: it is switched byPouring’s entry and exit actions, and exit actions run however a state is left. - The weak abort grants a last wish.
go tois a weak abort, so the inside ofServingfinishes its tick before the mode is left. In tick 12,brewandcleanarrive together:Readystarts the brew (heater on), and only then does the machine switch to cleaning.
Cleaning up on the way out
Section titled “Cleaning up on the way out”The fix for the first problem, in CoffeeExitAction, is one line: exit do heater = false on Serving. An exit
action is the mode’s “finally” block. Use it for every output the mode may have switched on, and the next
mode starts from a known state, however the old one ended.
That makes the switch safe for the hardware. It does not make it good: a press of clean still
throws away a half-made cup. To fix that, the switch must wait.
Request, wait for a safe point, commit
Section titled “Request, wait for a safe point, commit”A safe point is a moment at which the mode can be left without harm. For the coffee machine it is
Ready: no cup in progress. A safe switch has three parts:
- Request. Remember that a switch was asked for, whenever it was asked for. That is a latch.
- Wait. Let the current mode carry on until it reaches a safe point.
- Commit. At the safe point, leave the mode.
Diagram
Code
scchart CoffeeMachine { input bool brew, clean output bool heater, pump, rinsing output int cups = 0 bool cleanWanted = false
initial state Serving { during if clean do cleanWanted = true
initial state Ready if cleanWanted go to Stopped if brew do heater = true go to Heating
state Heating go to Pouring
state Pouring { entry do pump = true exit do pump = false } if 2 true do heater = false; cups++ go to Ready
final state Stopped } do cleanWanted = false join to Cleaning
state Cleaning { entry do rinsing = true exit do rinsing = false
initial state Rinse go to Flush
state Flush go to Rinsed
final state Rinsed } join to Serving}| Tick | input bool brew | input bool clean | output bool heater | output bool pump | output bool rinsing | output int cups | Active state of the chart | Active state of the region of Serving | Active state of the region of Cleaning |
|---|
Guided tour, 14 ticks:
- brew = false, clean = falseTick 1:
Serving,Ready. - brew = true
brew: the heater goes on. - brew = false, clean = true
cleanduring the brew.Serving’s during action records the request incleanWanted, and the brew carries on:Heatingmoves toPouring. - clean = falsePouring. The request is remembered although
cleanhas been released. - The cup is done (
cupsis 1) and the chart is back inReady, the safe point.Readywas entered in this tick, so its delayed transitions wait for the next one. - Commit:
if cleanWanted go to Stoppedfires,Stoppedis final, soServingterminates and its join clearscleanWantedand entersCleaning, all in this tick. - Rinse, flush ...
- ... done:
Cleaningterminates andServingstarts again. - Ready.
- brew = true, clean = true
brewandcleanin the same tick, inReady. The during action writescleanWantedbeforeReadyreads it, andReadytriesif cleanWantedfirst: cleaning wins, the brew request is ignored. - brew = false, clean = falseCleaning.
- Cleaning.
- Back to
Serving. - Ready.
clean in tick 3 is remembered; the cup is finished in tick 5, and cleaning starts in tick 6.Each part is one or two lines:
- Request:
during if clean do cleanWanted = trueonServingrecords a request in every tick in whichServingis active, except the tick in which it was entered. Aduringaction of a superstate runs alongside its inner states, so the brew is not disturbed. - Wait: only
Readylooks at the request.HeatingandPouringdo not, so a cup that has been started is finished. - Commit: in
Ready,if cleanWanted go to Stoppedenters a final state, soServingterminates, and itsjoin to Cleaningfires in the same tick. The join’s effect clears the request.
CoffeeMachine has no exit action for the heater, and needs none: it leaves Serving only from Ready,
where the heater is already off. Add one anyway as soon as a mode can be left in another way, as the
emergency stop below shows.
The request and the commit both write cleanWanted: the during action sets it, the join clears it.
KIELER accepts that, because the two are not concurrent. The join is a transition of the region around
Serving, and it is taken only after Serving has finished its tick. The during action, on the other
hand, is concurrent with Serving’s inner states: KIELER runs a superstate’s during action like an
extra region alongside them. So, as between any concurrent regions, its write of cleanWanted comes
before Ready’s read of it in the same tick. In tick 10, when brew and clean arrive together,
Ready already sees the request.
Here is the switch, tick by tick. State is the state after the tick, and Request is
cleanWanted:
| Tick | Inputs | State | Request |
|---|---|---|---|
| 2 | brew |
Serving, Heating | false |
| 3 | clean |
Serving, Pouring | true |
| 4 | none | Serving, Pouring | true |
| 5 | none | Serving, Ready (the cup is done) | true |
| 6 | none | Cleaning, Rinse | false |
| 8 | none | Serving, Ready | false |
| 10 | brew, clean |
Cleaning, Rinse | false |
Tick 5 to tick 6 is worth a second look. The cup is finished in tick 5, which enters Ready, and the
switch is committed one tick later: Ready’s transitions are delayed, so they are not checked in the
tick Ready is entered. If you want the commit in the very tick the safe point is reached, make the
transition immediate if cleanWanted go to Stopped.
Notice the policy decisions this chart makes, and that you should make on purpose in your own:
- Who wins at the safe point?
Readytriesif cleanWantedbeforeif brew, so a pending clean beats a new brew request, also when both arrive in the same tick (tick 10). - What happens to requests that arrive in the wrong mode? A brew request during cleaning is ignored. The exercise below remembers it instead.
Switches that must not wait
Section titled “Switches that must not wait”Some switches must happen at once, safe point or not: a fault, a lid opened, an emergency stop. That is a strong abort from a superstate around all the normal modes:
Diagram
Code
scchart CoffeeEmergency { input bool brew, clean, fault output bool heater, pump, rinsing output int cups = 0 bool cleanWanted = false
initial state Operating { initial state Serving { exit do heater = false during if clean do cleanWanted = true
initial state Ready if cleanWanted go to Stopped if brew do heater = true go to Heating
state Heating go to Pouring
state Pouring { entry do pump = true exit do pump = false } if 2 true do heater = false; cups++ go to Ready
final state Stopped } do cleanWanted = false join to Cleaning
state Cleaning { entry do rinsing = true exit do rinsing = false
initial state Rinse go to Flush
state Flush go to Rinsed
final state Rinsed } join to Serving } if fault abort to Halted
state Halted}| Tick | input bool brew | input bool clean | input bool fault | output bool heater | output bool pump | output bool rinsing | output int cups | Active state of the chart | Active state of the region of Operating | Active state of the region of Operating.Serving | Active state of the region of Operating.Cleaning |
|---|
Guided tour, 7 ticks:
- brew = false, clean = false, fault = falseTick 1:
Operating,Serving,Ready. - brew = true
brew: heater on. - brew = falsePouring: pump on.
- fault = trueA
faultin the middle of the cup. The strong abortif fault abort to Haltedstops everything insideOperatingbefore it reacts. But the exit actions still run, innermost first:Pouring’s stops the pump,Serving’s switches the heater off. - fault = false
Halted, everything off. - brew = trueNothing gets the machine out of
Halted: a brew request is ignored. - brew = falseHalted.
if fault abort to Halted is checked before anything inside Operating reacts, so in tick 4 nothing
inside it moves: no inner transition fires and no during action runs. Yet the heater and pump do go
off: exit actions run on strong aborts too, innermost first (Pouring’s, then Serving’s). This surprises people who have read
the KIELER wiki, which says that a strong abort suppresses exit actions. KIELER’s compiler does run
them, and that is what makes exit actions useful for safety: whatever ends a mode, its outputs are put
into a safe state.
If Operating also had weak aborts or a join to, the strong abort would have to be written first;
otherwise KIELER rejects the chart with Causality problem! and:
Strong abort transitions must have a higher priority than weak abort or termination transitions.For more on supervising a chart and forcing a safe state, see watchdogs.
Try it
Section titled “Try it”A brew request during cleaning
In the starter, a brew request during cleaning is lost: press clean in tick 2 and brew in tick
3, while the machine is cleaning, and no cup is ever made. Change it so that the request is remembered
and the cup is made as soon as Serving is back in Ready: with these inputs, the heater should go on in
tick 5 and cups should be 1 in tick 8. Use the same request-and-commit idea as for cleaning, in the
other direction, and clear the request when the brew starts. If KIELER warns
Instantaneous loop detected! about your chart (SCCharts Lab: Potential instantaneous loop, followed by
the variables involved), that is a false alarm here.
Show the starter chart
scchart CoffeeQueue { input bool brew, clean output bool heater, pump, rinsing output int cups = 0 bool cleanWanted = false
initial state Serving { during if clean do cleanWanted = true
initial state Ready if cleanWanted go to Stopped if brew do heater = true go to Heating
state Heating go to Pouring
state Pouring { entry do pump = true exit do pump = false } if 2 true do heater = false; cups++ go to Ready
final state Stopped } do cleanWanted = false join to Cleaning
state Cleaning { entry do rinsing = true exit do rinsing = false
initial state Rinse go to Flush
state Flush go to Rinsed
final state Rinsed } join to Serving}Show a solution
scchart CoffeeQueue { input bool brew, clean output bool heater, pump, rinsing output int cups = 0 bool cleanWanted = false bool brewWanted = false
initial state Serving { during if clean do cleanWanted = true
initial state Ready if cleanWanted go to Stopped if brew || brewWanted do brewWanted = false; heater = true go to Heating
state Heating go to Pouring
state Pouring { entry do pump = true exit do pump = false } if 2 true do heater = false; cups++ go to Ready
final state Stopped } do cleanWanted = false join to Cleaning
state Cleaning { entry do rinsing = true exit do rinsing = false during if brew do brewWanted = true
initial state Rinse go to Flush
state Flush go to Rinsed
final state Rinsed } join to Serving}Check your understanding
Section titled “Check your understanding”Diagram
Code
scchart CoffeeMachine { input bool brew, clean output bool heater, pump, rinsing output int cups = 0 bool cleanWanted = false
initial state Serving { during if clean do cleanWanted = true
initial state Ready if cleanWanted go to Stopped if brew do heater = true go to Heating
state Heating go to Pouring
state Pouring { entry do pump = true exit do pump = false } if 2 true do heater = false; cups++ go to Ready
final state Stopped } do cleanWanted = false join to Cleaning
state Cleaning { entry do rinsing = true exit do rinsing = false
initial state Rinse go to Flush
state Flush go to Rinsed
final state Rinsed } join to Serving}The safe coffee machine, CoffeeMachine, with new inputs. What are rinsing and heater after these ticks?
Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.
| Tick | brew | clean |
|---|---|---|
| 1 | false | false |
| 2 | false | true |
| 3 | true | false |
| 4 | false | false |
| 5 | true | false |
| 6 | false | true |
| 7 | false | false |
| 8 | false | false |
| 9 | false | false |
Explanation
rinsing is true after tick 3, heater is true after tick 5, and rinsing is false after tick 8 and
true after tick 9. The clean of tick 2 found the machine in Ready, so cleaning started at once, and
the brew of tick 3 is ignored: Cleaning has no transition for it. Back in Ready, the brew of
tick 5 switches the heater on. The clean of tick 6 comes during the brew: it is recorded, the cup is
finished in tick 8, and the switch is committed in tick 9, because Ready’s transitions are delayed.
Show the answer
Answer D: The exit actions of Pouring and Serving, innermost first
A strong abort prevents the inner behaviour of the tick: no transitions inside fire and no during actions run. But KIELER runs the exit actions of every state being left, the innermost first, also on a strong abort. That is why an exit action is the right place to switch outputs off.
Show the answer
Answer C: A transition would leave the state and cut the brew short
The point is to let the brew continue. A during action of a superstate runs alongside its inner states
in every tick after the entry tick, so it can note the request while Heating and Pouring carry on.
Only Ready, the safe point, acts on it.
Key ideas
- A mode is a superstate; switching modes is a transition out of it.
- Give every mode exit actions that switch off what it may have switched on. Exit actions run on every kind of exit, strong aborts included.
- A weak abort (
go to) lets the mode finish its tick first; that last wish can start things you did not want started. - Safe switching: latch the request, act on it only at a safe point, and commit by terminating the mode
(final state and
join to). - Switches that must not wait (faults) are strong aborts on a superstate around all normal modes, written before any weak abort.
Next, Handshakes: a request and an acknowledgement between two regions.