tsm  0.1.0
tsm Documentation

For a primer on UML state machines, look here.

Using tsm

Create the state machine by first creating a context. The context is a structure that holds the states, events, actions and guards of the state machine. The context is a template parameter to the Hsm class. The Hsm class is a Mixin class that adds state machine like functionality to the context. In essence, it takes a context and converts it into a state machine.

Wrap the state machine around an execution policy. Currently, there are several options.

a. A state machine that executes in the context of the parent thread. This means that the client sending events to the state machine drives the event processing as well. Typically, this will be the 'main' thread of your application driving event processing. Note that several other threads can hold references to the state machine instance and queue up events (see 'd' below).

SingleThreadedHsm<SwitchContext> sm;

b. A state machine that processes incomming events in its own thread. i.e. Events will be immediately consumed as long as the state machine is not busy. Clients 'fire and forget' an event and desire no control over event processing.

ThreadedExecutionPolicy<GarageDoorHsm> sm;

c. Send events to the state machine by using sendEvent method provided by the policy.

sm.sendEvent(sm.doorOpen);

d. If the state machine is running in parent thread context, invoke the step method to process the first event in the event queue. As opposed to the SingleThreadedExecutionPolicy, the ThreadedExecutionPolicy will immediately process an event on completion of prior event processing.

Architecture

Policy based design

Classes have been partitioned across policies so they can be mixed and matched for code reuse (in an ideal world)! The current architecture supports state machines with the following characterestics.

a. Hierarchical
b. Asynchronous
c. Parallel/Orthogonal

Other Policy classes can be implemented for distributed event processing. The existing mechanism can also be extended to incorporate custom behavior such as writing state transitions to disk. For e.g. see struct ThreadedExecWithObserver in ThreadedExecutionPolicy.h

Clients need to include tsm.h file.

Hsm

Place holder for your own application specific sate machine definition.

Hierarchical StateMachine

The make_hsm_t meta-function is used to recursively examine a state transition table and convert all sub-contexts into state machines. Every state machine is a Hsm class that is a mixin of the context.

Policy Classes

Policy classes like ThreadedExecutionPolicy and SingleThreadedExecutionPolicy are mixins that operate on wrapped Hsms. Clients will typically interact with a Policy class at the bottom of the inheritance hierarcy. By convention, these Policy classes also provide a send_event method as a public interface to the state machine. The SingleThreadedExecutionPolicy class also provides a step method for clients to initiate event processing. If the state machine is a ClockedHsm, a tick method is provided which will provide a reference to a ClockTickEvent.

The ThreadedExecutionPolicy processes events in its own thread. The processing of events is single threaded within all Hsms. So when a Hsm is started using a call to start(), the StateMachine will block waiting for next_event to return an event. The main advantage is that the only external interface to the Hsm can be the set of Events. Any "client" can asynchronously send an event to the state machine long as they have a reference to it. As soon as the Hsm is done with its processing, it will pick up the next event in the queue and process it. This can be seen in the test/*.cpp files.

Advantages

  • Integration - A lot of effort went into integration using CMake. This should make it easier for modern C++ development.
  • Unit Tests - Althought the framework could use a lot more unit tests, most of the code (98%) has been tested for 'happy path' cases.
  • Architecture - Hopefully, the ability to add your own policy classes makes it adaptable to your applications.

Putting it all together

Create your own context definitions. The context hierarchy, its states (and sub-contexts if any), events, actions guards and transitions are all specified and defined in the definition. The relationships between Hsms.

Then choose a policy class for your state machine. Complete your state machine type by wrapping the policy class around it. The unit test provided below is illustrative.

namespace TrafficLight {
struct LightContext {
struct G1 {
bool handle(LightContext&, ClockTickEvent& t) {
if (t.ticks_ >= 30) {
// exit action
t.ticks_ = 0;
return true;
}
return false;
}
};
struct Y1 {
bool handle(LightContext&, ClockTickEvent& t) {
if (t.ticks_ >= 5) {
// exit action
t.ticks_ = 0;
return true;
}
return false;
}
void entry(LightContext& t) { t.walk_pressed_ = false; }
};
struct G2 {
bool handle(LightContext& l, ClockTickEvent& t) {
if (t.ticks_ >= 60 || (l.walk_pressed_ && t.ticks_ >= 30)) {
// exit action
t.ticks_ = 0;
return true;
}
return false;
}
};
struct Y2 {
bool handle(LightContext&, ClockTickEvent& t) {
if (t.ticks_ >= 5) {
// exit action
t.ticks_ = 0;
return true;
}
return false;
}
};
bool walk_pressed_{};
using transitions = std::tuple<ClockedTransition<G1, Y1>,
ClockedTransition<Y1, G2>,
ClockedTransition<G2, Y2>,
ClockedTransition<Y2, G1>>;
};
// Emergency override trait. G1 and G2 will transition every five ticks. If
// walk_pressed_ is true, the light will transition to Y2 after 30 ticks. This
// will be part of a hierarchical state machine
struct EmergencyOverrideContext {
struct BaseHandle {
bool handle(EmergencyOverrideContext&, ClockTickEvent& t) {
if (t.ticks_ >= 5) {
// exit action
t.ticks_ = 0;
return true;
}
return false;
}
};
struct G1 : BaseHandle {};
struct Y1 : BaseHandle {};
struct G2 : BaseHandle {};
struct Y2 : BaseHandle {};
bool walk_pressed_{};
using transitions = std::tuple<ClockedTransition<G1, Y1>,
ClockedTransition<Y1, G2>,
ClockedTransition<G2, Y2>,
ClockedTransition<Y2, G1>>;
};
// Parent HSM - TrafficLight
struct TrafficLightHsmContext {
// Events
struct EmergencySwitchOn {};
struct EmergencySwitchOff {};
using transitions = std::tuple<
Transition<LightContext, EmergencySwitchOn, EmergencyOverrideContext>,
Transition<EmergencyOverrideContext, EmergencySwitchOff, LightContext>>;
};
}
// Test StateMachine ThreadedExecutionPolicy
TEST_CASE("Test ThreadedExecutionPolicy") {
// apply policy
using TrafficLightHsm =
ThreadedExecutionPolicy<TrafficLight::TrafficLightHsmContext>;
using LightHsm = make_hsm_t<TrafficLight::LightContext>;
using EmergencyOverrideHsm =
make_hsm_t<TrafficLight::EmergencyOverrideContext>;
TrafficLightHsm hsm;
hsm.start();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
REQUIRE(std::holds_alternative<EmergencyOverrideHsm*>(hsm.current_state_));
auto current_hsm = std::get<EmergencyOverrideHsm*>(hsm.current_state_);
REQUIRE(std::holds_alternative<TrafficLight::EmergencyOverrideContext::G1*>(
current_hsm->current_state_));
}
TEST_CASE("TestEventQueue - testSingleEvent")
Definition: EventQueue.cpp:10
Definition: test_hsm.cpp:33
bool handle(EmergencyOverrideContext &, ClockTickEvent &t)
Definition: test_hsm.cpp:94
std::tuple< ClockedTransition< G1, Y1 >, ClockedTransition< Y1, G2 >, ClockedTransition< G2, Y2 >, ClockedTransition< Y2, G1 > > transitions
Definition: test_hsm.cpp:113
bool walk_pressed_
Definition: test_hsm.cpp:109
bool handle(LightContext &, ClockTickEvent &t)
Definition: test_hsm.cpp:37
bool handle(LightContext &l, ClockTickEvent &t)
Definition: test_hsm.cpp:60
bool handle(LightContext &, ClockTickEvent &t)
Definition: test_hsm.cpp:48
void entry(LightContext &t)
Definition: test_hsm.cpp:56
bool handle(LightContext &, ClockTickEvent &t)
Definition: test_hsm.cpp:71
bool walk_pressed_
Definition: test_hsm.cpp:81
std::tuple< ClockedTransition< G1, Y1 >, ClockedTransition< Y1, G2 >, ClockedTransition< G2, Y2 >, ClockedTransition< Y2, G1 > > transitions
Definition: test_hsm.cpp:86
std::tuple< Transition< LightContext, EmergencySwitchOn, EmergencyOverrideContext >, Transition< EmergencyOverrideContext, EmergencySwitchOff, LightContext > > transitions
Definition: test_hsm.cpp:124

Here, the TrafficLightHsm is a hierarchical state machine. The LightHsm and EmergencyOverrideHsm are the leaf state machines. The TrafficLightHsm is a parent state machine that can switch between the LightHsm and EmergencyOverrideHsm state machines. The EmergencyOverrideHsm is a state machine that can override the LightHsm state machine. The EmergencyOverrideHsm is a "leaf" state machine that can switch between the G1, Y1, G2 and Y2 states. LightHsm is another "leaf" state machine that can switch between the same G1, Y1, G2 and Y2 states. If the main LightHsm needs to switch over to emergency mode, an EmergencySwitchOn event is sent to the TrafficLightHsm. The TrafficLightHsm will then switch over to the EmergencyOverrideHsm state machine.

The ThreadedExecutionPolicy is used to process events in a separate thread. If the ticks map to seconds, a PeriodicExecutionPolicy can be used to generate ticks every second. See tsm.h for an example.