Driver
Using state machines, it’s common to have a big switch
statement (or lots of if
s), altering behaviour depending on the the current state. These behaviours in turn might trigger a state change. Since this is such a common pattern, the StateMachine.driver is provided.
With it, you set up state handlers for different states and guiding the machine to subsequent states.
Each handler has an if
field, a single string or array of strings corresponding to the state(s) that handler applies to. While one handler can handle multiple different states, there can’t be multiple handlers per state.
The other part of the handler is then
field. At its simplest, it is an object that tells what state to transition to, for example:
The then
field can be an array of functions, all of which return the same kind of object. When the handler is run, it executes these functions to determine what to do. Functions defined under then
don’t have to return a value - they could just be things you want to run when the state machine is in that state.
Once we have the state machine and the handlers, the driver can be initialised with StateMachine.driver . This would likely happen once, when your sketch is initialised.
And then, perhaps in a timing-based loop, call run()
, which will execute a state handler for the current state.
Here’s a complete example:
Once you have the state machine and driver set up, you need to call .run() whenever you want the driver to do its thing. This might be called for example in a loop based on a timer.
If you use asynchronous event handlers, call await driver.run()
instead.
Some other things to do with the driver:
So far, handlers have returned an object describing what state to transition. Instead of hardcoding the state, you can use { next: true }
to transition to next available state. An alternative is { reset: true }
. When that is returned, the machine goes back to its initial state.
Each result can also have a score
field. This is only useful if you have several results under then
. By default, the highest scoring result determines what happens.
With this in mind, we can re-write the earlier example, assigning random scores for each possible next state:
In practice you might want to weight the random values so one choice is more or less likely than another.
Each handler also has an optional resultChoice
field, which can be ‘first’, ‘highest’, ‘lowest’ or ‘random’. By default, ‘highest’ is used, picking the highest scoring result. In our example, we might use resultChoice: 'random'
to evenly pick between choices. With that enabled, we no longer need scores.
When calling driver.run()
, a result is returned with some status information, if that’s needed:
Demo
In the demo below, the driver is used to autonomously change states based on an ‘energy’ level, also affected by current activity.