Skip to content

Triggers, effects and priority

When a transition fires, what it does on the way, and which one wins when several could fire at once.

Every transition answers three questions. When may the chart leave this state? That is the trigger. What does it do on the way? Those are the effects. Where does it go? That is the target. In the previous chapter each state had a single transition with a simple trigger and one effect. Real controllers need more: conditions on numbers, several effects at once, and states with more than one way out. This chapter covers all of that.

A thermostat keeps a room warm without switching the heater on and off every few seconds. It turns the heater on when the room drops below 18 degrees, and off again only once it reaches 21. Between the two, it keeps doing whatever it was doing:

ThermostatSimulation

Diagram

Diagram of ThermostatStates: Idle (initial), Heating. Transitions: Idle to Heating: temperature < 18 / heating = true; Heating to Idle: temperature >= 21 / heating = false.Thermostatinput inttemperatureoutput boolheating = falseIdleHeatingtemperature < 18/ heating = truetemperature >= 21/ heating = false

Code

scchart Thermostat {
input int temperature
output bool heating = false
initial state Idle
if temperature < 18 do heating = true go to Heating
state Heating
if temperature >= 21 do heating = false go to Idle
}
Tick 0 of 5
Inputs for tick 1
Timing diagram of Thermostat
Tickinput int temperatureoutput bool heatingActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 5 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 5 ticks:

  1. temperature = 20Tick 1 enters Idle. It is 20 degrees and the heater is off.
  2. temperature = 1717 is below 18, so the trigger temperature < 18 holds: the transition fires, heating becomes true and the chart is in Heating.
  3. temperature = 1919 degrees. Heating waits for temperature >= 21, which is false, so the heater stays on. Idle’s trigger is not checked at all: only the active state’s transitions count.
  4. temperature = 2121 is at least 21: the heater goes off and the chart is back in Idle.
  5. temperature = 1919 degrees again, as in tick 3, but now the chart is in Idle, and 19 is not below 18. Same input, different reaction: the state remembers which way the temperature was going.
Compare ticks 3 and 5: the same temperature, 19 degrees, with the heater on in one and off in the other.
Tick temperature State after heating after
1 20 Idle false
2 17 Heating true
3 19 Heating true
4 21 Idle false
5 19 Idle false

A trigger is any condition you can write as a boolean expression over the chart’s variables: inputs, outputs and, later in the guide, local variables. temperature < 18 compares an int input with a number. You can combine conditions with && (and), || (or) and ! (not), and compare with ==, !=, <, <=, > and >=. Arithmetic works too: if a + b > 10. The operators behave as they do in C; Operators lists them all, with their precedence.

In each tick, the chart checks the triggers of the active state’s transitions, with that tick’s values. It never looks at the transitions of other states. That is why ticks 3 and 5 differ: in tick 3 the chart is in Heating and waits for temperature >= 21, in tick 5 it is in Idle and waits for temperature < 18, and 19 satisfies neither. The state is the chart’s memory of what has happened so far.

An effect is an assignment, written after do: do heating = true. It runs only when its transition fires, and it can use any expression on the right-hand side: do average = (a + b) / 2 reads two variables and writes a third. A variable keeps its value until an effect writes it, which is why heating stays true for the whole time the thermostat is in Heating.

A transition can also have no effect at all (if fault go to Blocked), or several.

Separate several effects with semicolons. They run one after another, left to right, and each one sees the values written by the effects before it.

This chart records two readings: when sample is true it shifts the latest reading into previous and stores the new one in latest. The second chart is identical except for the order of its two effects. The guided tour feeds both the same inputs and samples twice, first a 5 and then an 8:

Sampler and SamplerSwappedComparison

Diagrams

Sampler

Diagram of SamplerStates: Waiting (initial). Transitions: Waiting to Waiting: sample / previous = latest; latest = reading.Samplerinput boolsampleinput intreadingoutput intlatest = 0output intprevious = 0Waitingsample/ previous = latest;latest = reading

SamplerSwapped

Diagram of SamplerSwappedStates: Waiting (initial). Transitions: Waiting to Waiting: sample / latest = reading; previous = latest.SamplerSwappedinput boolsampleinput intreadingoutput intlatest = 0output intprevious = 0Waitingsample/ latest = reading;previous = latest

Code

Sampler

scchart Sampler {
input bool sample
input int reading
output int latest = 0
output int previous = 0
initial state Waiting
if sample do previous = latest; latest = reading go to Waiting
}

SamplerSwapped

scchart SamplerSwapped {
input bool sample
input int reading
output int latest = 0
output int previous = 0
initial state Waiting
if sample do latest = reading; previous = latest go to Waiting
}
Tick 0 of 5
Inputs for tick 1, for both charts Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Sampler and SamplerSwapped, no ticks yet
Tickinput bool sampleinput int readingSampler: output int latestSampler: output int previousSampler: Active state of the chartSamplerSwapped: output int latestSamplerSwapped: output int previousSamplerSwapped: Active state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 5 ticks Both charts get the same inputs. Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 5 ticks, the same inputs for both charts:

  1. sample = false, reading = 0Tick 1 enters Waiting in both charts; all four outputs start at 0.
  2. sample = true, reading = 5The first sample, 5. Sampler runs previous = latest first, which copies the old 0, and then stores 5 in latest. SamplerSwapped stores 5 in latest first, so previous = latest copies that 5 as well.
  3. sample = false, reading = 8sample is false, so neither transition fires, although reading is now 8.
  4. sample = trueThe second sample, 8. Sampler moves 5 into previous before it stores 8 in latest. SamplerSwapped stores 8 and then copies it: both of its outputs are 8.
  5. sample = falseNothing fires. Sampler remembers the older reading, 5; SamplerSwapped never holds anything but the newest.
The same two effects in a different order: only Sampler ever remembers an older reading.

reading is 5 in tick 2 and 8 from tick 3 on. After each tick, latest is the same in both charts, but previous is not (Swapped is SamplerSwapped):

Tick sample latest Sampler previous Swapped previous
1 false 0 0 0
2 true 5 0 5
3 false 5 0 5
4 true 8 5 8
5 false 8 5 8

In Sampler, previous = latest runs first and copies the old value; then latest = reading overwrites latest. In SamplerSwapped, latest = reading runs first, so previous = latest copies the value that was written a moment earlier. There is no “all at once” assignment in SCCharts: the effects of a transition are a small sequential program, run top to bottom, all within the tick. (So do a = b; b = a does not swap a and b; both end up with b’s old value.) In the diagram, each effect of a transition gets its own line of the label.

Both charts also have something you haven’t seen yet: their transition leads from Waiting back to Waiting. That is a self-loop; it gets its own section below.

Several transitions out of one state: priority

Section titled “Several transitions out of one state: priority”

A pump starts when start is true, and must never run while a sensor reports a fault. So Idle has two transitions: one for the fault, one for starting. What should happen in a tick in which both inputs are true?

SCCharts answers with priority. A state’s outgoing transitions are tried in the order in which they are written: the first one whose trigger holds fires, and the others are ignored for this tick. Their effects do not run. In the diagram the order appears as a number in front of each label, 1:, 2: and so on. (KIELER only shows the numbers when a state has more than one outgoing transition.)

Here are two pumps that differ only in the order of Idle’s two transitions. In tick 2 of the guided tour, start and fault both become true:

Pump and PumpStartFirstComparison

Diagrams

Pump

Diagram of PumpStates: Idle (initial), Running, Blocked. Transitions: Idle to Blocked: 1: fault; Idle to Running: 2: start / pumping = true; Running to Blocked: fault / pumping = false.Pumpinput boolstartinput boolfaultoutput boolpumping = falseIdleRunningBlocked1: fault2: start/ pumping = truefault/ pumping = false

PumpStartFirst

Diagram of PumpStartFirstStates: Idle (initial), Running, Blocked. Transitions: Idle to Running: 1: start / pumping = true; Idle to Blocked: 2: fault; Running to Blocked: fault / pumping = false.PumpStartFirstinput boolstartinput boolfaultoutput boolpumping = falseIdleRunningBlocked1: start/ pumping = true2: faultfault/ pumping = false

Code

Pump

scchart Pump {
input bool start
input bool fault
output bool pumping = false
initial state Idle
if fault go to Blocked
if start do pumping = true go to Running
state Running
if fault do pumping = false go to Blocked
state Blocked
}

PumpStartFirst

scchart PumpStartFirst {
input bool start
input bool fault
output bool pumping = false
initial state Idle
if start do pumping = true go to Running
if fault go to Blocked
state Running
if fault do pumping = false go to Blocked
state Blocked
}
Tick 0 of 4
Inputs for tick 1, for both charts Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Pump and PumpStartFirst, no ticks yet
Tickinput bool startinput bool faultPump: output bool pumpingPump: Active state of the chartPumpStartFirst: output bool pumpingPumpStartFirst: Active state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 4 ticks Both charts get the same inputs. Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 4 ticks, the same inputs for both charts:

  1. start = false, fault = falseTick 1 enters Idle in both charts. Their transitions are delayed, so nothing else happens.
  2. start = true, fault = truestart and fault are both true, so both of Idle’s transitions could fire. Pump tries fault first: it goes to Blocked and never starts. PumpStartFirst tries start first: it starts pumping, despite the fault.
  3. The inputs are unchanged. PumpStartFirst entered Running in tick 2, so only now does it check fault: the pump stops and goes to Blocked. Pump has been in Blocked since tick 2, and Blocked has no transitions.
  4. start = false, fault = falseBoth inputs are back to false. Both charts are in Blocked, which has no way out.
Both inputs true in tick 2: Pump goes straight to Blocked, PumpStartFirst runs for one tick first.

Each chart’s state after the tick (StartFirst is PumpStartFirst). In both, pumping is true exactly while the pump is in Running:

Tick start fault Pump StartFirst
1 false false Idle Idle
2 true true Blocked Running
3 true (held) true (held) Blocked Blocked

In Pump the fault check is transition 1, so in tick 2 it wins and the pump never starts. In PumpStartFirst the start is transition 1: the pump starts in tick 2 despite the fault, and it takes until tick 3 for Running’s own fault check to stop it.

Why not in tick 2 itself? Because Running was only entered in tick 2, and a state’s transitions are checked from the tick after it was entered (you saw this for the initial state in Thinking in ticks). So with the transitions you know so far, a chart moves at most one step per tick: one transition fires out of the active state, and the state it enters waits for the next tick. Chapter 4 shows how immediate transitions change that.

The rule, precisely:

  • The transitions of a state have priorities 1, 2, 3, … in the order they are written. There is no other way to set a priority: to change it, reorder the lines.
  • In a tick, the first transition (in priority order) whose trigger holds is taken. At most one transition leaves a state per tick, and only the taken transition’s effects run.
  • If no trigger holds, nothing happens and the state stays active.

When several conditions can hold at once, put the one that must win first. Safety checks, like fault, go at the top.

A transition may lead back to the state it leaves: the samplers’ go to Waiting does that. A self-loop is an ordinary transition whose target happens to be its source. When it fires, the chart leaves the state, runs the effects, and enters the state again.

With what you know so far, a re-entered state looks just like a state that stayed active. The difference matters once states have more inside them: a re-entered state runs its entry actions again (chapter 10), and it resets the variables declared inside it and restarts the states nested inside it (chapter 7). So use a self-loop when you mean “do this, and start this state over”, and not merely to stay put.

You never need a transition in order to stay in a state: a state with no enabled transition stays active. A transition like this one

initial state Off
if !button go to Off
if button do light = true go to On

is not harmless decoration. From tick 2 on it fires in every tick in which button is false, and each time the chart leaves Off and enters it again. Leave it out.

A transition without a trigger, such as go to Next or do x = 1 go to Next, fires as soon as it is allowed to: in the first tick after its state was entered, every time. You saw this in the blinker of Thinking in ticks, which switched state in every tick.

Because it is always enabled, a transition without a trigger is a good last transition of a state: it says “otherwise”. In this stopwatch, Running first checks whether stop was pressed, and otherwise counts one tick and loops back. Its effect elapsed++ adds one to elapsed; it is one of several assignment shorthands (+=, -= and friends) that chapter 5 introduces properly.

StopwatchSimulation

Diagram

Diagram of StopwatchStates: Stopped (initial), Running. Transitions: Stopped to Running: start; Running to Stopped: 1: stop; Running to Running: 2: / elapsed++.Stopwatchinput boolstart, stopoutput intelapsed = 0StoppedRunningstart1: stop2: / elapsed++

Code

scchart Stopwatch {
input bool start, stop
output int elapsed = 0
initial state Stopped
if start go to Running
state Running
if stop go to Stopped
do elapsed++ go to Running
}
Tick 0 of 9
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Stopwatch
Tickinput bool startinput bool stopoutput int elapsedActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 9 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 9 ticks:

  1. start = false, stop = falseTick 1 enters Stopped; elapsed is 0.
  2. start = truestart is pressed: Stopped to Running. Running was entered in this tick, so its own transitions wait until the next one, and elapsed is still 0.
  3. start = falseIn Running, transition 1 (stop) is false. Transition 2 has no trigger, so it fires: elapsed++ adds one, and the chart goes from Running back to Running.
  4. The same again: elapsed is 2.
  5. And again: 3. One tick, one count.
  6. stop = truestop is pressed. Transition 1 is checked first and fires, so the chart goes to Stopped. Only one transition leaves a state per tick, so transition 2 does not run and elapsed stays 3.
  7. stop = falseStopped waits for start; elapsed keeps its value.
  8. start = trueStarted again: Running, still showing 3.
  9. start = falseCounting carries on from where it stopped: 4.
Watch which of Running’s two transitions fires: the self-loop counts, until stop takes priority in tick 6.
Tick start stop State after elapsed after
1 false false Stopped 0
2 true false (held) Running 0
3 false false (held) Running 1
4 false (held) false (held) Running 2
5 false (held) false (held) Running 3
6 false (held) true Stopped 3
7 false (held) false Stopped 3
8 true false (held) Running 3
9 false false (held) Running 4

Three things to notice:

  • In tick 2 the stopwatch starts but does not count: Running was entered in that tick, so its transitions wait until tick 3.
  • From tick 3 on, the self-loop fires in every tick, and elapsed++ counts the ticks.
  • In tick 6 both of Running’s transitions could fire: stop is true, and the self-loop needs no trigger at all. Transition 1 wins, so the chart stops, and the count does not run.

Chapter 10 shows a tidier way to do something in every tick while staying in a state.

Swap the two transitions of Running and the stopwatch can no longer be stopped:

state Running
do elapsed++ go to Running
if stop go to Stopped

The untriggered self-loop is now transition 1, and it is enabled in every tick, so transition 2 never gets a chance. KIELER notices and warns on the if stop line: The transition is not reachable. Try it: press stop in the simulation below and the count carries on.

StopwatchSimulation

Diagram

Diagram of StopwatchStates: Stopped (initial), Running. Transitions: Stopped to Running: start; Running to Running: 1: / elapsed++; Running to Stopped: 2: stop.Stopwatchinput boolstart, stopoutput intelapsed = 0StoppedRunningstart1: / elapsed++2: stop

Code

scchart Stopwatch {
input bool start, stop
output int elapsed = 0
initial state Stopped
if start go to Running
state Running
do elapsed++ go to Running
if stop go to Stopped
}
Tick 0 of 5
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it. Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Stopwatch
Tickinput bool startinput bool stopoutput int elapsedActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 5 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 5 ticks:

  1. start = false, stop = falseTick 1 enters Stopped.
  2. start = trueStarted: Running.
  3. start = falseThe untriggered transition is first, so it fires: 1.
  4. stop = truestop is pressed, but transition 1 has no trigger and always wins. Transition 2 is never even checked: elapsed is 2 and the stopwatch keeps running.
  5. Still running: 3. There is no way to stop it.
Warning: This transition can never fire
  • Warning, line 10, column 5KIELER says: The transition is not reachable.A transition without a trigger above it always fires first (it has a higher priority). Move this one up, or give the other one a trigger.About this message
stop is true from tick 4, and the stopwatch keeps counting.

KIELER’s warning covers only this case, where an earlier transition has no trigger at all. It does not analyse your conditions. In

initial state Start
if a || b do choice = 1 go to Done
if a do choice = 2 go to Done

the second transition can never fire either, since whenever a is true a || b is true as well, but the chart compiles without a word. When you order transitions, put the more specific condition first.

Close the window first

Heating a room with the window open wastes energy. Add an input bool windowOpen to the thermostat. While the window is open, the heater must be off: it must not switch on, and if it is on when the window opens, it must switch off. Once the window is shut again, the thermostat works as before. Test it with the temperature at 17 while you open and shut the window.

Show the starter chart
scchart Thermostat {
input int temperature
output bool heating = false
initial state Idle
if temperature < 18 do heating = true go to Heating
state Heating
if temperature >= 21 do heating = false go to Idle
}
Show a solution
scchart Thermostat {
input int temperature
input bool windowOpen
output bool heating = false
initial state Idle
if temperature < 18 && !windowOpen do heating = true go to Heating
state Heating
if temperature >= 21 || windowOpen do heating = false go to Idle
}

A clear button

Add an input clear to the stopwatch. Pressing clear sets elapsed back to 0 and leaves the stopwatch in Stopped, whether it was running or not. If clear and stop are pressed in the same tick, clear should win. Think about where each new transition goes in its state’s list.

Show the starter chart
scchart Stopwatch {
input bool start, stop
output int elapsed = 0
initial state Stopped
if start go to Running
state Running
if stop go to Stopped
do elapsed++ go to Running
}
Show a solution
scchart Stopwatch {
input bool start, stop, clear
output int elapsed = 0
initial state Stopped
if clear do elapsed = 0 go to Stopped
if start go to Running
state Running
if clear do elapsed = 0 go to Stopped
if stop go to Stopped
do elapsed++ go to Running
}
StopwatchPrediction

Diagram

Diagram of StopwatchStates: Stopped (initial), Running. Transitions: Stopped to Running: start; Running to Stopped: 1: stop; Running to Running: 2: / elapsed++.Stopwatchinput boolstart, stopoutput intelapsed = 0StoppedRunningstart1: stop2: / elapsed++

Code

scchart Stopwatch {
input bool start, stop
output int elapsed = 0
initial state Stopped
if start go to Running
state Running
if stop go to Stopped
do elapsed++ go to Running
}

Inputs keep their values: look closely at stop in ticks 6 and 7.

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

Tickstartstop
1falsefalse
2truefalse
3truefalse
4truefalse
5falsetrue
6truetrue
7falsetrue
  1. After tick 6, is Running active?
  2. After tick 7, is Running active?
Plays the scenario on the diagram, then marks each answer.
Explanation

After tick 4, elapsed is 2: Running was entered in tick 2 and counted in ticks 3 and 4. In tick 5 stop stops it. In tick 6 start is true while the chart is in Stopped. Stopped only looks at start, so the stopwatch starts, although stop is still switched on from tick 5: Running is active. In tick 7 Running checks stop first, finds it still true, and stops again without counting.

A transition fires with the effects do x = 1; y = x + 1; x = 5. Before the tick, x and y are both 0. What are they after it?
Show the answer

Answer B: x is 5 and y is 2

The effects run left to right. x = 1 writes 1; y = x + 1 reads that 1 and writes 2; x = 5 overwrites x. y is not recomputed when x changes later: an effect is an assignment, not an equation.

Idle has two transitions: if fault go to Blocked, then if start do pumping = true go to Running. Both fault and start are true. What happens?
Show the answer

Answer C: Only the first transition fires, and pumping stays false

Transitions are tried in written order and the first enabled one wins. At most one transition leaves a state per tick, and the effects of the others do not run, so pumping = true never happens.

Key ideas

  • A trigger is a boolean expression over the chart’s variables. Only the active state’s triggers count, with the current tick’s values.
  • An effect is an assignment. Several effects are separated by ; and run left to right, each seeing the writes before it.
  • A state’s transitions are tried in written order; the first whose trigger holds fires, and only its effects run. The diagram numbers them when there are several.
  • A transition without a trigger fires in the first tick after its state was entered. Put it last.
  • A self-loop leaves its state and enters it again. To stay in a state, you need no transition.
  • The transition is not reachable. means an untriggered transition is hiding the ones after it.

Next, Delayed and immediate transitions: why a chart takes at most one step per tick so far, and how immediate lets it take several.

Report a mistake on this page