Table of Contents
Motivation
Wer mit Robotern arbeitet, wird früher oder später eine Zustandsmaschine programmieren müssen, da Roboter typischerweise verschiedene Betriebszustände einnehmen können.
Was sind State Machines?
Eine State Machine (deutsch: Zustandsautomat) ist ein Modell, das beschreibt, wie sich ein System abhängig von seinem aktuellen Zustand und einem Ereignis verhält. Statt viele verschachtelte if-Anweisungen zu verwenden, definiert man klar:
- Zustände – z. B. „Aus”, „Ein”, „Pause”
- Ereignisse – z. B. Tastendruck oder Timer
- Übergänge – wann von einem Zustand in einen anderen gewechselt wird
- Aktionen – was beim Wechsel oder innerhalb eines Zustands passiert
Boost SML
Boost State Machine Language wurde 2016 von Kris Jusiak entwickelt.
- Erste öffentliche Version auf GitHub: 2016
- Anzahl der Contributors: ~50
- Ein bis zwei Releases pro Jahr
- Kein offizieller Teil von Boost
Projektseite: https://github.com/boost-ext/sml
Installation
Variante 1 – Download Header
Flache Kopie ins Projekt
cd ~/robot_sml_demo
mkdir boost
wget https://raw.githubusercontent.com/boost-ext/sml/master/include/boost/sml.hpp
Einbinden mit
#include "boost/sml.hpp"
Variante 2 – Globale Verfügbarkeit
cd ~
git clone https://github.com/boost-ext/sml
Erstellt den Ordner sml im Home, dann kann man über CMake das Verzeichnis inkludieren
target_include_directories(robot_sml_tutorial PRIVATE
/home/jboegeholz/sml/include
)
Einbinden mit
#include <boost/sml.hpp>
Variante 3 – git submodules
Eine weitere Variante ist die Einbindung über submodules.
mkdir external
git submodule add https://github.com/boost-ext/sml external/boost
git submodule update --init --recursive
#include "../external/boost/include/boost/sml.hpp"
Einfache State Machine
Im folgenden Implementieren wir eine einfache State Machine für einen Roboter:
| State | Event | New state |
|---|---|---|
| Idle | Start | Driving |
| Idle | Error | Fault |
| Driving | Stop | Idle |
| Driving | Error | Fault |
| Fault | Reset | Idle |
Erste Implementierung
Eine State Machine in SML benötigt drei Dinge:
- Zustände + Events
- Transition Table
- Transition durch die Zustände mit
process_event()
Modellierung der Zustände und Events
In Boost.SML können Zustände und Events durch structs implementiert werden:
// States
struct Idle {};
struct Driving {};
struct Fault{};
// Events
struct Start{};
struct Stop{};
struct Error{};
struct Reset{};
Transition Table
Die Transition Table ist das Herzstück der State Machine. Der Startzustand wird über * definiert, dann folgt die Modellierung der Zustandsübergänge nach dem Muster:
src_state + event = dst_state
struct Robot
{
auto operator()() const
{
using namespace sml;
return make_transition_table(
*state<Idle>+ event<Start> = state<Driving>,
state<Driving> + event<Error> = state<Fault>,
state<Driving> + event<Stop> = state<Idle>,
state<Fault> + event<Reset> = state<Idle>
);
}
};
TEST(SimpleStateMachine, TestStartState) {
const sml::sm robot{};
robot.visit_current_states([](auto state) {
EXPECT_STREQ(state.c_str(), "Idle");
});
}
Im Grunde ist Robot hier keine State Machine, sondern eine Konfigurationsklasse. Der operator()()-Ausdruck bedeutet, dass der Funktionsaufruf-Operator überladen wird. Klassen, die diesen Operator implementieren, erzeugen Objekte, die wie ganz normale Funktionen aufgerufen werden können. Solche Objekte nennt man auch Funktoren.
Transition durch die Zustände
Eine Transition von einem Zustand in einen anderen wird durch process_event ausgelöst:
TEST(SimpleStateMachine, TestTransition) {
sml::sm robot{};
robot.process_event(Start{});
robot.visit_current_states([](auto state) {
EXPECT_STREQ(state.c_str(), "Driving");
});
}
Actions
Jedem Zustandübergang kann eine Aktion zugeordnet werden, hier im Beispiel mit Lambdas
src_state + event / action = dst_state
struct Robot
{
auto operator()() const
{
using namespace sml;
return make_transition_table(
*state<Idle> + event<Start> / [] { std::cout << "Received <<Start>> Event";} = state<Driving>,
state<Driving> + event<Error> / [] { std::cout << "Received <<Error>> Event";} = state<Fault>,
state<Driving> + event<Stop> / [] { std::cout << "Received <<Stop>> Event";} = state<Idle>,
state<Fault> + event<Reset> / [] { std::cout << "Received <<Reset>> Event";} = state<Idle>
);
}
};
Actions als Function Objects / Funktoren
Eleganter hingegen ist es, Actions ebenfalls als Funktor zu definieren, da das die Transition Table deutlich übersichtlicher macht:
struct start_action {
void operator()() const {
std::cout << "Start\n";
}
};
struct stop_action {
void operator()() const {
std::cout << "Stop\n";
}
};
struct error_action {
void operator()() const {
std::cout << "Error\n";
}
};
struct reset_action {
void operator()() const {
std::cout << "Reset\n";
}
};
...
return make_transition_table(
*state<Idle>+ event<Start> / start_action{} = state<Driving>,
state<Driving> + event<Error> / error_action{} = state<Fault>,
state<Driving> + event<Stop> / stop_action{} = state<Idle>,
state<Fault> + event<Reset> / reset_action{} = state<Idle>
);
Guards
Ein Guard ist eine Bedingung, die entscheiden darf, ob ein Übergang stattfinden soll.
state + event [ guard ] = next_state;
Beispiel
auto battery_ok = [] { return true; };
...
*state<Idle>+ event<Start>[battery_ok] = state<Driving>,
Mehrere Guards verknüpfen
Das ist ein besonderes Feature von SML: Guards können mit &&, || und ! kombiniert werden.
state + event [ guard1 || guard2 ] / action = next_state;
Dependency Injection
Eine weitere große Stärke von SML ist die Verwendung von Dependency Injection.
Hier injiziert SML das RobotContext-Objekt in den Guard:
struct RobotContext {
bool battery_ok = true;
};
...
*state<Idle>+ (StartEvent)[([](const RobotContext& ctx) {return ctx.battery_ok;})] = state<Driving>,
...
RobotContext ctx;
sml::sm robot{ctx};
robot.process_event(StartEvent());
User-Defined Literals (UDLs)
Zu Deutsch benutzerdefinierte Literale.
States
Für Zustände definiert SML das Literal _s, dadurch ist "Idle"_s ein gültiger Zustand.
Events
Für Events existiert das Literal _e:
auto start_event = "Start"_e;
Die Transitiontabelle wird dadurch etwas besser lesbar:
return make_transition_table(
*"Idle"_s + start_event = "Driving"_s,
"Driving"_s + stop_event = "Idle"_s,
"Driving"_s + error_event = "Fault"_s,
"Fault"_s + reset_event = "Idle"_s
);
Im process_event muss es dann so aufgerufen werden:
robot.process_event(start_event());
Fazit & Ausblick
SML ist eine kleine, schlanke Library für State Machines in C++. Sie überzeugt durch ihre ausdrucksstarke Syntax, Dependency Injection und die Möglichkeit, Guards flexibel zu kombinieren.
Links
Die Codebeispiele findest Du in diesem Repo:
https://github.com/jboegeholz/boost_sml_tutorial







