Maki
Loading...
Searching...
No Matches
Guard

Definition

A guard is associated with a transition. It is a condition for the transition to occur.

On a UML state diagram, it is represented between square brackets in the transition label:

A guard

In the above example, the state machine transitions from state0 to state1 and executes action provided guard is true. If guard is false, nothing happens.

Guards can be based on system-wide data (e.g. current temperature) or event data (e.g. button press duration).

When to use a guard

You need guards to express the equivalent of if/else, switch, for or while statements in your state machine.

For example, let's modelize the state machine of a 3-speed fan that remembers its last speed and automatically sets it at startup. Its components include:

  • an internal memory (to save last set speed);
  • a minus button, to lower the speed;
  • a plus button, to raise the speed.

The state machine defines:

  • 4 states:
    • reading memory, the initial state, in which the fan reads the last saved speed and waits for the memory read event;
    • spinning low;
    • spinning med;
    • spinning high;
  • 3 events:
    • memory read, emitted when the memory is read, which carries the speed value;
    • minus button press;
    • plus button press;
  • 3 actions:
    • set speed low;
    • set speed med;
    • set speed high.

What about transitions? Transitions between spinning * states should be obvious: we just use the button press events. But how do we get from the reading memory state to these spinning * states?

This is where we need guards. Starting from reading memory, at memory read:

  • if the guard memory speed = low, transition to the spinning low state;
  • if the guard memory speed = med, transition to the spinning med state;
  • if the guard memory speed = high, transition to the spinning high state.

Here is the state machine diagram of our fan:

Fan

How to define guards within Maki

The guard is the fifth (optional) parameter of maki::transition_table::operator()(). In this context, Maki expects an instance of maki::guard or maki::null.

constexpr auto transition_table = maki::transition_table{}
(source_state_type, target_state_type, maki::event<event_type>, action, guard)
//...
;
Represents a transition table.
Definition transition_table.hpp:157

Example

Let's implement the state machine of our 3-speed fan.

Our event types are the following. Notice that memory_read carries the fan speed that is read from the fan internal memory:

//Data types
enum class speed
{
low,
med,
high
};
//Events
struct memory_read
{
speed spd = speed::low;
};
struct minus_button_press{};
struct plus_button_press{};

Our guards need to check the value of the speed carried by the memory_read event. Thus, we must use one of the signatures that take the event. As there's no signature that only takes the event, let's use the second one and just ignore the context_type parameter. These are our guards:

constexpr auto is_speed_low = maki::guard_e([](const memory_read& event)
{
return event.spd == speed::low;
});
constexpr auto is_speed_med = maki::guard_e([](const memory_read& event)
{
return event.spd == speed::med;
});
constexpr auto is_speed_high = maki::guard_e([](const memory_read& event)
{
return event.spd == speed::high;
});

Finally, we pass each guard to the transition they're associated with. Once again, our transition table is a direct representation of the state diagram:

constexpr auto transition_table = maki::transition_table{}
//source, target, event, action, guard
(maki::init, reading_memory)
(reading_memory, spinning_low, maki::event<memory_read>, maki::null, is_speed_low)
(reading_memory, spinning_med, maki::event<memory_read>, maki::null, is_speed_med)
(reading_memory, spinning_high, maki::event<memory_read>, maki::null, is_speed_high)
(spinning_low, spinning_med, maki::event<plus_button_press>, maki::null)
(spinning_med, spinning_high, maki::event<plus_button_press>, maki::null)
(spinning_med, spinning_low, maki::event<minus_button_press>, maki::null)
(spinning_high, spinning_med, maki::event<minus_button_press>, maki::null)
;

Here is a test program for our state machine:

#include <maki.hpp>
#include <functional>
#include <iostream>
struct context{};
//Data types
enum class speed
{
low,
med,
high
};
//Events
struct memory_read
{
speed spd = speed::low;
};
struct minus_button_press{};
struct plus_button_press{};
//States
constexpr auto reading_memory = maki::state_builder{};
constexpr auto spinning_low = maki::state_builder{}
{
std::cout << "Speed is low\n";
//Set fan speed and save speed in memory
//[Implementation detail...]
})
;
constexpr auto spinning_med = maki::state_builder{}
{
std::cout << "Speed is med\n";
//Set fan speed and save speed in memory
//[Implementation detail...]
})
;
constexpr auto spinning_high = maki::state_builder{}
{
std::cout << "Speed is high\n";
//Set fan speed and save speed in memory
//[Implementation detail...]
})
;
//Guards
constexpr auto is_speed_low = maki::guard_e([](const memory_read& event)
{
return event.spd == speed::low;
});
constexpr auto is_speed_med = maki::guard_e([](const memory_read& event)
{
return event.spd == speed::med;
});
constexpr auto is_speed_high = maki::guard_e([](const memory_read& event)
{
return event.spd == speed::high;
});
//Transition table
constexpr auto transition_table = maki::transition_table{}
//source, target, event, action, guard
(maki::init, reading_memory)
(reading_memory, spinning_low, maki::event<memory_read>, maki::null, is_speed_low)
(reading_memory, spinning_med, maki::event<memory_read>, maki::null, is_speed_med)
(reading_memory, spinning_high, maki::event<memory_read>, maki::null, is_speed_high)
(spinning_low, spinning_med, maki::event<plus_button_press>, maki::null)
(spinning_med, spinning_high, maki::event<plus_button_press>, maki::null)
(spinning_med, spinning_low, maki::event<minus_button_press>, maki::null)
(spinning_high, spinning_med, maki::event<minus_button_press>, maki::null)
;
//State machine configuration
constexpr auto machine_conf = maki::machine_conf{}
.transition_tables(transition_table)
.context_a<context>()
;
//State machine
using machine_t = maki::machine<machine_conf>;
int main()
{
auto machine = machine_t{};
//Simulate a memory read that returns a "med" speed.
//This eventually sets the fan speed to "med".
machine.process_event(memory_read{speed::med});
//Simulate button presses
{
//Set fan speed to "high"
machine.process_event(plus_button_press{});
//Doesn't do anything, as the highest possible speed is already reached
machine.process_event(plus_button_press{});
machine.process_event(plus_button_press{});
//Set fan speed to "med"
machine.process_event(minus_button_press{});
//Set fan speed to "low"
machine.process_event(minus_button_press{});
//Doesn't do anything, as the lowest possible speed is already reached
machine.process_event(minus_button_press{});
}
return 0;
}

The output of this program is the following:

Speed is med
Speed is high
Speed is med
Speed is low