A simple on-off statechart

Abstract

This page uses a simple on-off switch, and defines its behaviour using a statechart. The switch is a simple device that has two states: “on” and “off”. When you “flick” the switch, it goes from on to off, or off to on. This is the base behaviour that will be described in a statechart. We will change this behaviour by modifing the statechart. It is a contrived example, but shows some ways to model behaviour using techniques specific to statecharts.

Introduction

Here’s the simple on-off switch, the “simple state machine” that we’ll extend in various ways. In a way it will function as the “zoomed out” view of our statechart too:

Simple on/off state machine

We’re going to specialize these two states by adding “substates” to alter the behaviour of the system, but before we continue, let’s recap the behaviour of the state machine above:

In our refinmenent, this will generally hold true, but with a few exceptions. The exceptions will be described by introducing substates. We’ll start off by introducing a couple of new states within the off state. These substates specialize the behaviour of the off state: It causes it to change behaviour.

The behaviour changes that will be introduced all have to deal with the “misuse” of the light switch, in that spamming the button with the flick event will cause the light to go on and off very, very, quickly, possibly causing the light to break because the light is switched on and off too quickly.

The new behaviour we’re going for is to require a two second period where the “flick event” should not go to the “on” state, and if the flick event happens a bit too early, that the switch now has to wait another two seconds for it to want to go to the “on” state.

Here’s the statechart:

On/off state machine with some "debouncing"

The off state now has it’s own little state machine diagram, complete with

If the whole statechart is a bit overwhelming, you can try to block out the rest of the statechart, leaving only the OFF state:

The off state machine

Since A is the initial state, then whenever the off state is entered, it also automatically enters the A state. When a state machine is in two states like this at the same time, it is the “deepest” one that gets to handle an event first. So when the machine is in the A state, the “flick” event will be handled by A by transitioning to itself: The A state points back to A. Such an event is consumed by the deepest state.

The delayed transition going from A to B (after 2 seconds) causes the machine enter B if and only if it has been in A, uninterrupted, for 2 seconds.

When the machine is in the B state, and the flick event happens, the B state doesn’t care, and so the parent Off state handles it, by transitioning to the On state.

This is a simple way of defining precise debouncing behaviour. Note how in this state machine, the debouncing only happens for the Off state, and that should the flick event happen in rapid succession, the On state would be exited on the first instance of the event.

Refining the On state

For the On state, we want to do something a bit more special: We want to delay the on action a bit, but allow the flick event to transition us to the Off state at any time. To do this we specialize the on state and move the actions to another substate, so that the entry/exit actions only happen a short time after it’s been in the On state.

Here’s the statechart:

On state expanded with a few substates

The new states (named C and D here to keep things simple) cause the side effects of entering the On state to be delayed by half a second. However, since none of these substates handle the flick event, then if that event happens at any time in the On state, the state machine immediately goes back to the Off state.

It’s interesting to note that the “turn off” action will only be called if the turn on action was ever called, since it’s only possible to exit D if it ever entered D in the first place.

Refining the On state even further

Let’s say the business side says that they don’t like the behaviour in that if you happened to turn the light on and then off again at just the right moment (0.6 seconds), the light might be on for a tiny fraction of a second (0.1 seconds), and for whatever reason they want the light to never be turned on for less than 0.5 seconds, i.e. that the “turn lights off action should never be called immediately after a turn lights on action, but be powered on for at least 0.5 seconds”

Well that’s simple to accommodate in a Statechart; just specialize the D state by adding a few new substates. These new states would be tasked to handle the flick event for the first 0.5 seconds and essentially “do nothing” if it happens.

Difference between how flick is handled in the On and Off states

The Off and On states have similar behaviour, although not quite the same. Both react to the “flick” event, but slightly differently.

In the Off state, when the button is “flick”ed repeatedly (even for many seconds), the stream of “flick” events will be ignored, and the light will stay Off. It is only when the “flick” event hasn’t happened for 2 seconds, that the next “flick” will turn it on. In other words, the “flick” event must “cool down” for 2 seconds before it has an effect.

In the On state, when the button is “flicked” repeatedly, the flick events in the first 0.5 seconds of the On state are ignored. Only after 0.5 seconds will the “flick” event cause the light turn off. The “flick” event is essentially ignored for half a second.

Light on for (at least) 1/2 second

When in the D state, the flick event should be ignored for 0.5 seconds. This requires a few new states:

It should stay in E for 0.5 seconds and then transition to F

In order to ignore the event, we introduce a substate of E, called G which simply is there to ignore the event.

Here’s the new D state, with substates:

The _D_ state with substates

Here’s the full statechart:

The full statechart with full _D_ state

[Concepts explained] [SCXML version] [XState version]

Conclusion

In all of these examples above, the business logic (the turn light on and turn light off) has been left untouched. All of this new behaviour has been designed in a Statechart. This page has shown a few of the techniques that can be used to solve various problems.

The problem being solved is a bit contrived, and albeit the problem domain is relatively simple (turn a light on or off at the flick of a switch) it does a lot more than a simple boolean:

Additionally, no weird bugs such as rogue timers cause events to fire out-of-order. In a normal, imperative implementation of this, it is common to forget to cancel any timers that have been started, causing the effects of the timer to be executed even though they were not supposed to. This whole class of bugs no longer exist.

You will never need an on/off switch that functions exactly like this, but if you consider the field of micro-interactions, things can easily get pretty complex. Consider showing a pop-up notification that the user can dismiss by clicking or tapping on it, what if the user was about to tap on something exactly where the pop-up appeared, and instead taps the notification, thus dismissing it without having read the notification. The notification should ignore taps for a short while, perhaps half a second, which is exactly the problem we’ve solved in statecharts.

Finally, the diagram itself is pretty explanatory. One can focus on the top level On and Off states to get a general understanding of what the machine does, and then look into each state in isolation to understand how each one functions.