@@ -1,69 +1,714 @@
\~english \page state_machine State machine
\~russian \page state_machine Машина состояний
\~\ english \page state_machine State machine
\~\ russian \page state_machine Машина состояний
\~english
\~\ english
The state machine module (\a PIStateMachine) provides hierarchical states, event-driven and timeout-driven transitions, and aligns with the [SCXML ](https://www.w3.org/TR/scxml/ ) idea of state charts.
# State Machine
# Concepts
## State Machine Description
* **State** — a named node with an optional entry handler. You add states with \a PIStateMachine::addState() (or equivalent on the template subclass), giving an enum or id, name, and optional handler .
* **Transition (rule)** — from one state to another, triggered by an event (and optionally guarded). Use \a addRule() to register a transition; you can attach conditions and actions .
* **Event** — an integer id posted with \a postEvent(); the machine delivers it to active states and runs the first matching transition guard.
* **Conditions** — named flags that can be required for a transition (e.g. \a addCondition() on a \a Rule). Call \a performCondition() to set them; \a resetConditions() to clear.
* **Timeout** — a transition can fire after a delay; combine with \a PITimer or internal timeout support in transition classes.
State machine is a behavioral design pattern that allows an object to change its behavior when its internal state changes .
Implementation in PIP is based on the **SCXML ** (State Chart XML) standard .
The machine is a \a PIObject subclass; you can connect timers or other objects to post events. Call \a setInitialState() and \a start() to run. Use \a switchToState() for direct state changes, \a performCondition() to satisfy named conditions.
### SCXML Concepts
# Minimal example
- **State** — a node in the state tree, can be atomic or compound
- **Transition** — a connection between states that triggers on an event
- **Event** — a trigger that causes a transition
- **Guard** — a condition that must be true for a transition to execute
- **Action** — an operation executed during a transition
- **Parallel** — a state where all substates are activated simultaneously
Define an enum for states, subclass \a PIStateMachine<YourEnum>, add states and rules in the constructor, set the initial state, then start. Post events from keyboard, timer, or other handlers.
### PIP State Machine Features
- **Cannot create state, event, transition on stack** — all objects are created via `new` and managed via pointers
- **State machine is the root state** — `PIStateMachine` inherits from `PIStateBase`
- **All transitions and states are automatically deleted** — when the parent object is destroyed
### State Types
- **Atomic state** — a state without nested substates
- **Compound state** — a state containing nested substates
- **Final state** — a terminating state indicating the end of machine execution
- **Parallel state** — a state where all substates are activated simultaneously
### Transition Types
- **Normal transition** — triggers on receiving a specific event
- **Timeout transition** — triggers automatically after a specified time interval
- **Guarded transition** — triggers only when a specific condition is met
## State Machine API
### Main Classes
#### PIStateMachine
Main state machine class.
**Key methods: **
- `bool start()` — starts state machine execution
- `bool isRunning()` — checks if state machine is running
- `void setOnFinish(std::function<void()> f)` — sets finish callback
- `bool postEvent(int event_id, Args... args)` — posts event to state machine
#### PIStateBase
Base class for all states.
**Key methods: **
- `virtual void onEnter()` — called when state is entered
- `virtual void onExit()` — called when state is exited
- `void addState(PIStateBase *s)` — adds child state
- `PITransitionBase *addTransition(PIStateBase *target, int event_id)` — adds transition
- `PITransitionTimeout *addTimeoutTransition(PIStateBase *target, PISystemTime timeout)` — adds timeout transition
- `void setParallel(bool yes)` — sets parallel mode
#### PIStateLambda
State with lambda callbacks.
\code{.cpp}
enum Mode { Start, Manual, Auto, Finish, End };
class Machine : public PIStateMachine<Mode> {
PIOBJECT_SUBCLASS(Machine, PIObject)
public:
Machine() {
addState(Start, "start", HANDLER(onStart));
addState(Manual, "manual", HANDLER(onManual));
addState(Auto, "auto", HANDLER(onAuto));
addRule(Start, Manual, "init_ok", HANDLER(beginManual));
addRule(Manual, Auto, HANDLER(toAuto));
addRule(Auto, Manual, HANDLER(toManual));
setInitialState(Start);
}
EVENT_HANDLER(void, onStart) { /* entry */ }
EVENT_HANDLER(void, onManual) { /* entry */ }
EVENT_HANDLER(void, onAuto) { /* entry */ }
EVENT_HANDLER(void, beginManual) { /* transition */ }
EVENT_HANDLER(void, toAuto) { }
EVENT_HANDLER(void, toManual) { }
};
Machine machine;
// In key handler: machine.performCondition("init_ok"); or machine.switchToState(Manual);
PIStateLambda(std::function<void()> on_enter, std::function<void()> on_exit = nullptr, const PIString &n = {})
\endcode
Full example: doc/examples/pistatemachine.cpp. API details: \a PIStateMachine, \a PIStateBase, \a pistatemachine_state.h, \a pistatemachine_transition.h.
#### PIStateFinal
\~russian
Final state of state machine.
Модуль машины состояний (\a PIStateMachine) предоставляет иерархические состояния, переходы по событиям и по таймауту и ориентирован на идеи [SCXML ](https://www.w3.org/TR/scxml/ ).
\code{.cpp}
PIStateFinal(std::function<void()> on_finish = nullptr, const PIString &n = {})
\endcode
# Концепции
#### PITransitionBase
* **Состояние** — именованный узел с опциональным обработчиком входа. Состояния добавляются через \a PIStateMachine::addState() (или аналог в шаблонном подклассе): enum/id, имя, при необходимости обработчик .
* **Переход (правило)** — из одного состояния в другое по событию (и при выполнении условий). \a addRule() регистрирует переход; можно задать условия и действия.
* **Событие** — целочисленный id, посылаемый через \a postEvent(); машина доставляет е г о активным состояниям и выполняет первый подходящий переход.
* **Условия** — именованные флаги, требуемые для перехода (например \a addCondition() на \a Rule). Установка через \a performCondition(), с б р о с — \a resetConditions().
* **Таймаут** — переход по истечении времени; используется вместе с \a PITimer или встроенной поддержкой таймаутов в классах переходов.
Base class for transitions .
Машина — подкласс \a PIObject; к ней можно подключать таймеры и другие объекты для посылки событий. Перед запуском задают \a setInitialState() и вызывают \a start(). \a switchToState() — прямая смена состояния, \a performCondition() — выполнение именованного условия.
**Key methods: **
# Минимальный пример
- `PITransitionBase *addGuard(std::function<R(Args...)> f)` — adds guard function
- `PITransitionBase *addAction(std::function<void()> a)` — adds action
- `void trigger()` — triggers transition
Определяют enum состояний, подкласс \a PIStateMachine<YourEnum>, в конструкторе добавляют состояния и правила, задают начальное состояние и запускают. События посылают из обработчика клавиш, таймера и т.д. Код минимального примера приведён выше в англоязычной секции.
#### PITransitionTimeout
Полный пример: doc/examples/pistatemachine.cpp. Детали API: \a PIStateMachine, \a PIStateBase, \a pistatemachine_state.h, \a pistatemachine_transition.h.
Transition that triggers after a specified timeout.
## Usage Examples
### Simple State Machine
\code{.cpp}
#include "pistatemachine.h"
#include "pisystemtime.h"
// Create state machine
PIStateMachine *machine = new PIStateMachine("Main");
// Create states
PIStateLambda *idle = new PIStateLambda(
[]() { piCout << "Entering Idle state\n"; },
[]() { piCout << "Exiting Idle state\n"; },
"Idle"
);
PIStateLambda *running = new PIStateLambda(
[]() { piCout << "Entering Running state\n"; },
[]() { piCout << "Exiting Running state\n"; },
"Running"
);
PIStateFinal *finish = new PIStateFinal(
[]() { piCout << "Machine finished\n"; },
"Finish"
);
// Add states
machine->addState(idle);
machine->addState(running);
machine->addState(finish);
// Set initial state
idle->setInitialState(idle);
running->setInitialState(running);
machine->setInitialState(idle);
// Add transitions
idle->addTransition(running, 1);
running->addTransition(finish, 2);
// Set finish callback
machine->setOnFinish([]() { piCout << "Machine execution completed\n"; });
// Start machine
machine->start();
// Post events
machine->postEvent(1); // Transition to Running
machine->postEvent(2); // Transition to Finish
\endcode
### Guarded Transitions
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("With Guards");
PIStateLambda *stateA = new PIStateLambda([]() { piCout << "State A\n"; }, nullptr, "A");
PIStateLambda *stateB = new PIStateLambda([]() { piCout << "State B\n"; }, nullptr, "B");
PIStateLambda *stateC = new PIStateLambda([]() { piCout << "State C\n"; }, nullptr, "C");
machine->addState(stateA);
machine->addState(stateB);
machine->addState(stateC);
stateA->setInitialState(stateA);
machine->setInitialState(stateA);
// Transition with guard function
auto *t1 = stateA->addTransition(stateB, 1);
t1->addGuard([](int value) -> bool {
return value > 10;
});
// Transition with another guard function
auto *t2 = stateB->addTransition(stateC, 2);
t2->addGuard([](const PIString &msg) -> bool {
return msg == "allowed";
});
machine->start();
// First call will not execute (value <= 10)
machine->postEvent(1, 5);
// Second call will execute (value > 10)
machine->postEvent(1, 15);
// First call will not execute (msg != "allowed")
machine->postEvent(2, "test");
// Second call will execute (msg == "allowed")
machine->postEvent(2, "allowed");
\endcode
### Timeout Transitions
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("With Timeout");
PIStateLambda *working = new PIStateLambda(
[]() { piCout << "Working...\n"; },
[]() { piCout << "Stop working\n"; },
"Working"
);
PIStateLambda *timeoutState = new PIStateLambda([]() { piCout << "Timeout occurred\n"; }, nullptr, "Timeout");
PIStateFinal *finish = new PIStateFinal([]() { piCout << "Done\n"; }, "Finish");
machine->addState(working);
machine->addState(timeoutState);
machine->addState(finish);
working->setInitialState(working);
machine->setInitialState(working);
// Add timeout transition (after 5 seconds)
working->addTimeoutTransition(timeoutState, PISystemTime::fromSeconds(5));
// Add transition from timeoutState to finish
timeoutState->addTransition(finish, 1);
machine->start();
\endcode
### Compound States
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("Compound States");
// Root state (machine)
PIStateLambda *root = new PIStateLambda([]() { piCout << "Root state\n"; }, nullptr, "Root");
// Compound state with substates
PIStateLambda *parent = new PIStateLambda([]() { piCout << "Parent state\n"; }, nullptr, "Parent");
PIStateLambda *child1 = new PIStateLambda([]() { piCout << "Child 1\n"; }, nullptr, "Child1");
PIStateLambda *child2 = new PIStateLambda([]() { piCout << "Child 2\n"; }, nullptr, "Child2");
parent->setInitialState(child1);
parent->addState(child1);
parent->addState(child2);
root->addState(parent);
machine->addState(root);
machine->setInitialState(root);
// Add transitions
root->addTransition(parent, 1);
parent->addTransition(root, 2);
machine->start();
// Transition to parent
machine->postEvent(1);
// Transition back
machine->postEvent(2);
\endcode
### Parallel States
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("Parallel States");
PIStateLambda *root = new PIStateLambda([]() { piCout << "Root\n"; }, nullptr, "Root");
PIStateLambda *parallel = new PIStateLambda([]() { piCout << "Parallel\n"; }, nullptr, "Parallel");
PIStateLambda *sub1 = new PIStateLambda([]() { piCout << "Substate 1\n"; }, nullptr, "Sub1");
PIStateLambda *sub2 = new PIStateLambda([]() { piCout << "Substate 2\n"; }, nullptr, "Sub2");
// Enable parallel mode
parallel->setParallel(true);
parallel->addState(sub1);
parallel->addState(sub2);
parallel->setInitialState(sub1);
root->addState(parallel);
machine->addState(root);
machine->setInitialState(root);
machine->start();
// When entering parallel, both substates are activated simultaneously
\endcode
### Transitions with Actions
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("With Actions");
PIStateLambda *stateA = new PIStateLambda([]() { piCout << "State A\n"; }, nullptr, "A");
PIStateLambda *stateB = new PIStateLambda([]() { piCout << "State B\n"; }, nullptr, "B");
machine->addState(stateA);
machine->addState(stateB);
machine->setInitialState(stateA);
// Add transition with action
auto *t = stateA->addTransition(stateB, 1);
t->addAction([]() { piCout << "Action during transition\n"; });
machine->start();
machine->postEvent(1);
\endcode
## Thread Safety
State machine is thread-safe. The `postEvent` method uses a queue to handle nested calls, allowing safe event posting from
different threads.
## Usage Rules
### Cannot Create on Stack
\code{.cpp}
// ❌ WRONG
PIStateLambda state([]() {}, nullptr, "State");
// ✅ CORRECT
PIStateLambda *state = new PIStateLambda([]() {}, nullptr, "State");
\endcode
### Proper Object Hierarchy
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("Main");
// States are created via new and added to machine or another state
PIStateLambda *state1 = new PIStateLambda([]() {}, nullptr, "State1");
PIStateLambda *state2 = new PIStateLambda([]() {}, nullptr, "State2");
machine->addState(state1);
machine->addState(state2);
// Transitions are added to states via addTransition
state1->addTransition(state2, 1);
// Machine is started
machine->start();
\endcode
### Memory Cleanup
All objects (states, transitions) are automatically deleted when the parent object is destroyed:
- When `PIStateMachine` is destroyed, all states and transitions are deleted
- When `PIStateBase` is destroyed, all child states and transitions are deleted
## Debugging
For debugging, you can use the `print()` method to output the state tree:
\code{.cpp}
machine->print();
\endcode
Also, you can use `activeAtomics()` to get a list of active states.
## Related Modules
- \ref DateTime module for `PISystemTime` used in timeout transitions
\~\russian
# Машина состояний
## Описание машины состояний
Машина состояний — это поведенческий паттерн проектирования, который позволяет объекту изменять свое поведение при изменении
внутреннего состояния. Реализация в PIP основана на стандарте **SCXML ** (State Chart XML).
### Основные концепции SCXML
- **State (Состояние)** — узел в дереве состояний, может быть атомарным или составным
- **Transition (Переход)** — связь между состояниями, срабатывает при событии
- **Event (Событие)** — триггер, вызывающий переход
- **Guard (Сторожевая функция)** — условие, которое должно быть истинным для выполнения перехода
- **Action (Действие)** — операция, выполняемая при переходе
- **Parallel (Параллельное состояние)** — состояние, в котором все подсостояния активируются одновременно
### Особенности реализации PIP
- **Нельзя создавать state, event, transition в стеке** — все объекты создаются через `new` и управляются через указатели
- **Машина состояний — это корневой state** — `PIStateMachine` наследуется от `PIStateBase`
- **В с е переходы и состояния удаляются автоматически** — при уничтожении родительского объекта
### Основные типы состояний
- **Атомарное состояние** — состояние без вложенных подсостояний
- **Составное состояние** — состояние, содержащее вложенные подсостояния
- **Финальное состояние** — завершающее состояние, указывающее на окончание работы машины
- **Параллельное состояние** — состояние, в котором все подсостояния активируются одновременно
### Виды переходов
- **Обычный переход** — срабатывает при получении определенного события
- **Переход по таймауту** — срабатывает автоматически через заданный промежуток времени
- **Переход с guard** — срабатывает только при выполнении определенного условия
## PIP State Machine API
### Основные классы
#### PIStateMachine
Основной класс машины состояний.
**Ключевые методы: **
- `bool start()` — запускает выполнение машины состояний
- `bool isRunning()` — проверяет, запущена ли машина
- `void setOnFinish(std::function<void()> f)` — устанавливает коллбэк завершения
- `bool postEvent(int event_id, Args... args)` — отправляет событие в машину состояний
#### PIStateBase
Базовый класс для всех состояний.
**Ключевые методы: **
- `virtual void onEnter()` — вызывается при входе в состояние
- `virtual void onExit()` — вызывается при выходе из состояния
- `void addState(PIStateBase *s)` — добавляет дочернее состояние
- `PITransitionBase *addTransition(PIStateBase *target, int event_id)` — добавляет переход
- `PITransitionTimeout *addTimeoutTransition(PIStateBase *target, PISystemTime timeout)` — добавляет переход по таймауту
- `void setParallel(bool yes)` — устанавливает параллельный режим
#### PIStateLambda
Состояние с lambda-коллбэками.
\code{.cpp}
PIStateLambda(std::function<void()> on_enter, std::function<void()> on_exit = nullptr, const PIString &n = {})
\endcode
#### PIStateFinal
Финальное состояние машины состояний.
\code{.cpp}
PIStateFinal(std::function<void()> on_finish = nullptr, const PIString &n = {})
\endcode
#### PITransitionBase
Базовый класс для переходов.
**Ключевые методы: **
- `PITransitionBase *addGuard(std::function<R(Args...)> f)` — добавляет сторожевую функцию
- `PITransitionBase *addAction(std::function<void()> a)` — добавляет действие
- `void trigger()` — запускает переход
#### PITransitionTimeout
Переход, срабатывающий через заданный промежуток времени.
## Примеры использования
### Простая машина состояний
\code{.cpp}
#include "pistatemachine.h"
#include "pisystemtime.h"
// Создаем машину состояний
PIStateMachine *machine = new PIStateMachine("Main");
// Создаем состояния
PIStateLambda *idle = new PIStateLambda(
[]() { piCout << "Entering Idle state\n"; },
[]() { piCout << "Exiting Idle state\n"; },
"Idle"
);
PIStateLambda *running = new PIStateLambda(
[]() { piCout << "Entering Running state\n"; },
[]() { piCout << "Exiting Running state\n"; },
"Running"
);
PIStateFinal *finish = new PIStateFinal(
[]() { piCout << "Machine finished\n"; },
"Finish"
);
// Добавляем состояния
machine->addState(idle);
machine->addState(running);
machine->addState(finish);
// Устанавливаем начальное состояние
idle->setInitialState(idle);
running->setInitialState(running);
machine->setInitialState(idle);
// Добавляем переходы
idle->addTransition(running, 1);
running->addTransition(finish, 2);
// Устанавливаем коллбэк завершения
machine->setOnFinish([]() { piCout << "Machine execution completed\n"; });
// Запускаем машину
machine->start();
// Отправляем события
machine->postEvent(1); // Перейти в Running
machine->postEvent(2); // Перейти в Finish
\endcode
### Машина состояний с guard-функциями
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("With Guards");
PIStateLambda *stateA = new PIStateLambda([]() { piCout << "State A\n"; }, nullptr, "A");
PIStateLambda *stateB = new PIStateLambda([]() { piCout << "State B\n"; }, nullptr, "B");
PIStateLambda *stateC = new PIStateLambda([]() { piCout << "State C\n"; }, nullptr, "C");
machine->addState(stateA);
machine->addState(stateB);
machine->addState(stateC);
stateA->setInitialState(stateA);
machine->setInitialState(stateA);
// Переход с guard-функцией
auto *t1 = stateA->addTransition(stateB, 1);
t1->addGuard([](int value) -> bool {
return value > 10;
});
// Переход с другой guard-функцией
auto *t2 = stateB->addTransition(stateC, 2);
t2->addGuard([](const PIString &msg) -> bool {
return msg == "allowed";
});
machine->start();
// Первый вызов не выполнится (value <= 10)
machine->postEvent(1, 5);
// Второй вызов выполнится (value > 10)
machine->postEvent(1, 15);
// Первый вызов не выполнится (msg != "allowed")
machine->postEvent(2, "test");
// Второй вызов выполнится (msg == "allowed")
machine->postEvent(2, "allowed");
\endcode
### Машина состояний с таймаутами
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("With Timeout");
PIStateLambda *working = new PIStateLambda(
[]() { piCout << "Working...\n"; },
[]() { piCout << "Stop working\n"; },
"Working"
);
PIStateLambda *timeoutState = new PIStateLambda([]() { piCout << "Timeout occurred\n"; }, nullptr, "Timeout");
PIStateFinal *finish = new PIStateFinal([]() { piCout << "Done\n"; }, "Finish");
machine->addState(working);
machine->addState(timeoutState);
machine->addState(finish);
working->setInitialState(working);
machine->setInitialState(working);
// Добавляем переход по таймауту (через 5 секунд)
working->addTimeoutTransition(timeoutState, PISystemTime::fromSeconds(5));
// Добавляем переход из timeoutState в finish
timeoutState->addTransition(finish, 1);
machine->start();
\endcode
### Составные состояния
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("Compound States");
// Корневое состояние (машина)
PIStateLambda *root = new PIStateLambda([]() { piCout << "Root state\n"; }, nullptr, "Root");
// Составное состояние с подсостояниями
PIStateLambda *parent = new PIStateLambda([]() { piCout << "Parent state\n"; }, nullptr, "Parent");
PIStateLambda *child1 = new PIStateLambda([]() { piCout << "Child 1\n"; }, nullptr, "Child1");
PIStateLambda *child2 = new PIStateLambda([]() { piCout << "Child 2\n"; }, nullptr, "Child2");
parent->setInitialState(child1);
parent->addState(child1);
parent->addState(child2);
root->addState(parent);
machine->addState(root);
machine->setInitialState(root);
// Добавляем переходы
root->addTransition(parent, 1);
parent->addTransition(root, 2);
machine->start();
// Переход в parent
machine->postEvent(1);
// Переход обратно
machine->postEvent(2);
\endcode
### Параллельные состояния
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("Parallel States");
PIStateLambda *root = new PIStateLambda([]() { piCout << "Root\n"; }, nullptr, "Root");
PIStateLambda *parallel = new PIStateLambda([]() { piCout << "Parallel\n"; }, nullptr, "Parallel");
PIStateLambda *sub1 = new PIStateLambda([]() { piCout << "Substate 1\n"; }, nullptr, "Sub1");
PIStateLambda *sub2 = new PIStateLambda([]() { piCout << "Substate 2\n"; }, nullptr, "Sub2");
// Включаем параллельный режим
parallel->setParallel(true);
parallel->addState(sub1);
parallel->addState(sub2);
parallel->setInitialState(sub1);
root->addState(parallel);
machine->addState(root);
machine->setInitialState(root);
machine->start();
// При входе в parallel о б а подсостояния активируются одновременно
\endcode
### Машина состояний с действиями
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("With Actions");
PIStateLambda *stateA = new PIStateLambda([]() { piCout << "State A\n"; }, nullptr, "A");
PIStateLambda *stateB = new PIStateLambda([]() { piCout << "State B\n"; }, nullptr, "B");
machine->addState(stateA);
machine->addState(stateB);
machine->setInitialState(stateA);
// Добавляем переход с действием
auto *t = stateA->addTransition(stateB, 1);
t->addAction([]() { piCout << "Action during transition\n"; });
machine->start();
machine->postEvent(1);
\endcode
## Потокобезопасность
Машина состояний PIP потокобезопасна. Метод `postEvent` использует очередь для обработки вложенных вызовов, что позволяет
безопасно отправлять события из разных потоков.
## Правила использования
### Нельзя создавать в стеке
\code{.cpp}
// ❌ НЕЛЬЗЯ
PIStateLambda state([]() {}, nullptr, "State");
// ✅ ПРАВИЛЬНО
PIStateLambda *state = new PIStateLambda([]() {}, nullptr, "State");
\endcode
### Правильная иерархия объектов
\code{.cpp}
PIStateMachine *machine = new PIStateMachine("Main");
// Состояния создаются через new и добавляются в машину или другое состояние
PIStateLambda *state1 = new PIStateLambda([]() {}, nullptr, "State1");
PIStateLambda *state2 = new PIStateLambda([]() {}, nullptr, "State2");
machine->addState(state1);
machine->addState(state2);
// Переходы добавляются к состояниям через addTransition
state1->addTransition(state2, 1);
// Машина запускается
machine->start();
\endcode
### Очистка памяти
В с е объекты (состояния, переходы) автоматически удаляются при уничтожении родительского объекта:
- При уничтожении `PIStateMachine` удаляются все состояния и переходы
- При уничтожении `PIStateBase` удаляются все дочерние состояния и переходы
## Отладка
Для отладки можно использовать метод `print()` для вывода дерева состояний:
\code{.cpp}
machine->print();
\endcode
Также можно использовать `activeAtomics()` для получения списка активных состояний.
## Связанные модули
- \ref DateTime модуль для `PISystemTime` , используемого в переходах по таймауту