A UML state chart library.
This library provides APIs for creating a UML style state chart (see UML state machine). It provides basic building blocks for a state chart including:
- State
- Event
- Guard
- Action
It also has the following features:
- Automatically generated initial and final state for each chart
- Support for composite State (i.e. using a state chart (subchart) as a State inside another chart)
- Support observers on events
- Support for callbacks on state transitions (entry, do, exit)
- Support for both blocking and nonblocking state chart progression
cmake -S . -B build
cmake --build build
# (optionally) run unit test
cd build && ctest
You can find more usage examples in the examples
folder
We will demostrate a simple state chart here to illustrate the usages of this
library.
The state chart is a slightly simplified version of examples/simple.cpp
and
it look like following:
e1
┌───────────┐ ┌──────┐ ┌─────────┐
│ │ │ │ e2 │ │
│ Initial ├─────────────────────►│ s1 ├───────────────────►│ Final │
│ │ t1:g1 │ │ t2: /action() │ │
└───────────┘ └──────┘ └─────────┘
entry:fn_en()
do:fn_do()
exit:fn_ex()
- Two automatically generated states:
Inital
andFinal
- One user state
s1
, with following state actions:- entry:
fn_en()
- do:
fn_do()
- exit:
fn_ex()
- entry:
t1
: Transition fromInital
tos1
- Guarded by guard
g1
- Guarded by guard
t2
: Transition froms1
toFinal
- Guarded by event
e2
- Also performs action function
action()
upon transition
- Guarded by event
e1
: Subscribed by states1
e2
: Subscribed by transitiont2
We now demonstrate how to create the example state chart above
- create a containing
Chart
auto chart = Chart::createChart("chart");
This will automatically generate Initial
and Final
states
Note These two states can be retrieved by calling getInitialState()
and
getFinalState()
methods on the containing chart
- create user state
s1
auto s1 = chart->createState("s1");
Set up state actions respectively
s1->setCallbackEntry(fn_en);
s1->setCallbackDo(fn_do);
s1->setCallbackExit(fn_ex);
state action functions can take any things that's a functor or function pointer, such as
/* lambda */
s1->setCallbackEntry([](){std::cout << "<s1 Entry> called!" << std::endl;});
/* function object returned by std::bind */
s1->setCallbackDo(std::bind(func, arg));
/* function pointer */
void fn_ex(void) {}
s1->setCallbackExit(fn_ex);
- create transtions t1, without action
auto t1 = chart->getInitialState()->createTransition(s1);
- create transtions t2, with callback action
action()
auto t2 = s1->createTransition(chart->getFinalState(), action);
- create guards
g1
andg2
Guards takes the form of a functor that returnstrue
orfalse
, here we specifyg1
as a lambda function that always returnstrue
andg2
a lambda that checks a local flag
bool g2_flag;
t1->createGuard([](){return true});
t2->createGuard([&g2_flag](){return g2_flag;});
- Instantiate events
e1
ande2
Event e1{"e1"}, e2{"e2"};
- add event
e1
to states1
, with a trivial callback to print a message on std out
s1->createEventCallback(e1,
[](auto event){
std::cout << "Event:[in s1]:" << event.name() << std::endl;});
- add event
e2
to transitiont2
t2->addEvent(e2);
At this point our state chart is fully constructed according to our diagram.
You can also optionally add a state change callback through
createStateChangeCallback(*Functor*)
method providing a Funtor that will
be called each time a state transition occurs.
A state chart can run in either syncronously (in the calling thread) or asyncronously (in another (spawned) thread). A detailed description can be found in the Appendix Section
As a simple example, we choose to run the chart in a syncronous (blocking) manner as:
chart->spin();
An asyncronous (nonblocking) API
chart->spinAsync();
will spawn up another thread for the chart to progress. To stop this progress (and thus stop this spawned thread) one needs to call
chart->stop();
respectively.
Contiuing on the previous example, we can also trigger events, for example:
e1.trigger();
...
e2.trigger();
will notify event listeners on the corresponding events and call their callbacks
(e.g. Functor supplied with the call s1->createStateChangeCallback()
) or
grant a transition (e.g. transition t2
if current state is s1
)
We also provide a ros2 package under branch ros2_foxy
. As the name suggests it
supports foxy
distro. Other ROS2 distros are not tested but should generally work
To use this package, add mogi_statechart
as a dependency in your ROS2
project's package.xml
, as well as your CMakeLists.txt
The startchart can be invoked both syncronously and asyncronously
- Sync call
spin()
will run the state chart indefinitely in the calling thread, blocking the calling thread thereafter.spinOnce()
will take one step in the state chart logic and return. CallbackDo() of this state("A") will be called; additionally, if a transition can be taken it will be, and CallbackExit() of this state("A") will be called, followed by the CallbackEntry() of the transitted-to state("B"), at this point it's considered we arein the state of "B"
The nextspinOnce()
will repeat the above process for the new statespinToState(stateName)
will run the statechart untilstateName
state is reached, i.e. it's equivalent of a series calls tospinOnce()
until we arein the state of "stateName"
- Async call
spinAsync()
will spaw a new thread and run the state chart logic in that thread without blocking the calling thread. all the callbacks (statechange observer, state/transition action callbacks guard callbacks) will be called from the newly spawned thread thus locks must be provide by the callback functions if shared resources are contendedstop()
will stop the asyncronously running state chart
- This implementation does not support dynamic reconfiguration of the
chart, i.e. you will need to
stop()
the chart to change some configurations like add/remove states, change state callbacks and transitions etc. if the chart was (spinAsync()
ed) previously. Future implementation might dynamic configuration if there's a need - All the callbacks does not offer thread safty, there's two implication here:
- If any of the callback functions will access shared resource with other thread, proper locks should be used
- A
stop()
should be called before any of possible callback and their accessed varibles are going out of scope and/or being destructed.
This library throws runtime_error exceptions at one of the following scenarios
- create a chart with an empty name
createChart("")
. This is not allowed and a chart should always created with a meaningful name - create a state with an empty name
createState("")
. This is not allowed and a state should always created with a meaningful name - create a transition
srcState->addTransition(dstState ...)
where the destine statedstState
is not in the same chart as source statesrcState
. i.e. thedstState
should either be created by the same chartc
that createdsrcState
byc->createState()
, ordstState
is a subchart state added tosrcState
's containing chartc
byc->addSubchart()
.