A C++17 implementation demonstrating the Hierarchical State Machine (HSM) pattern using std::variant for type-safe state representation. Includes a threaded HSM with JSON messaging protocol, commands, and timeout support.
This project showcases modern C++ patterns for implementing complex state machines with:
- Hierarchical (nested) states using
std::variant - Type-safe event handling with
std::visit - State entry/exit actions for resource management
- Composite states containing sub-states
- Event-driven transitions with proper action handling
- Threaded HSM running in a dedicated thread
- Commands (non-state-changing actions with state restrictions)
- JSON message protocol for inter-thread communication
- Synchronous/asynchronous execution with timeout support
The Laser Tracker HSM implements a multi-level state hierarchy:
stateDiagram-v2
[*] --> Off
Off --> Operational : PowerOn
Operational --> Off : PowerOff
state Operational {
[*] --> Initializing
Initializing --> Idle : InitComplete
Initializing --> Error : InitFailed
Idle --> Tracking : StartSearch
Idle --> Error : ErrorOccurred
Error --> Initializing : Reset
state Tracking {
[*] --> Searching
Searching --> Locked : TargetFound
Locked --> Measuring : StartMeasure
Locked --> Searching : TargetLost
Measuring --> Locked : StopMeasure
Measuring --> Searching : TargetLost
}
Tracking --> Idle : ReturnToIdle
Tracking --> Error : ErrorOccurred
}
flowchart LR
subgraph Events
PowerOn([PowerOn])
PowerOff([PowerOff])
InitComplete([InitComplete])
InitFailed([InitFailed])
StartSearch([StartSearch])
TargetFound([TargetFound])
TargetLost([TargetLost])
StartMeasure([StartMeasure])
StopMeasure([StopMeasure])
MeasurementComplete([MeasurementComplete])
ErrorOccurred([ErrorOccurred])
Reset([Reset])
ReturnToIdle([ReturnToIdle])
end
PowerOn --> |Off → Operational| InitComplete
InitComplete --> |Initializing → Idle| StartSearch
StartSearch --> |Idle → Searching| TargetFound
TargetFound --> |Searching → Locked| StartMeasure
StartMeasure --> |Locked → Measuring| MeasurementComplete
MeasurementComplete --> |Records point| StopMeasure
StopMeasure --> |Measuring → Locked| ReturnToIdle
ReturnToIdle --> |Tracking → Idle| PowerOff
classDiagram
class HSM {
-State currentState_
+processEvent(Event) bool
+getCurrentStateName() string
+isInState~S~() bool
+printState() void
-transitionTo~NewState~(newState) void
}
class State {
<<variant>>
Off | Operational
}
class Operational {
+OperationalSubState subState
+onEntry() void
+onExit() void
+getSubStateName() string
}
class OperationalSubState {
<<variant>>
Initializing | Idle | Tracking | Error
}
class Tracking {
+TrackingSubState subState
+onEntry() void
+onExit() void
+getSubStateName() string
}
class TrackingSubState {
<<variant>>
Searching | Locked | Measuring
}
HSM --> State
State --> Operational
Operational --> OperationalSubState
OperationalSubState --> Tracking
Tracking --> TrackingSubState
The ThreadedHSM class extends the basic HSM with multi-threading support:
- Dedicated Worker Thread: HSM runs in its own thread, processing messages from a queue
- Thread-Safe State Access: Query state safely from any thread
- Message-Based Communication: Events and commands sent via thread-safe queue
- Synchronous & Asynchronous: Choose blocking or fire-and-forget message sending
- Timeout Support: Configurable timeouts prevent indefinite blocking
- Command Buffering: Messages queued during sync operations
Commands are actions that don't change state but are restricted to specific states:
| Command | Valid States | Sync | Description |
|---|---|---|---|
Home |
Idle | Yes | Move to home position |
GetPosition |
Idle, Locked, Measuring | No | Get current position |
SetLaserPower |
Any Operational | No | Adjust laser power (0.0-1.0) |
Compensate |
Idle, Locked | Yes | Apply environmental compensation |
GetStatus |
Any | No | Get system status |
MoveRelative |
Idle, Locked | Yes | Relative movement by azimuth/elevation |
Messages use a unified JSON format for both requests and responses:
Request Format:
{
"id": 1,
"name": "Home",
"params": { "speed": 100.0 },
"sync": true,
"timeoutMs": 5000
}Response Format:
{
"id": 1,
"success": true,
"result": { "position": { "azimuth": 0.0, "elevation": 0.0 } },
"error": null
}#include "ThreadedHSM.hpp"
LaserTracker::ThreadedHSM tracker;
tracker.start();
// Send async event (fire and forget)
tracker.sendEventAsync(Events::PowerOn{});
// Send sync event and wait for response
auto response = tracker.sendEventSync(Events::InitComplete{});
// Send command and get result
auto result = tracker.sendCommand(Commands::Home{50.0});
if (result.success)
{
std::cout << "Home complete: " << result.params.toJson() << "\n";
}
tracker.stop();// Top-level state variant
using State = std::variant<States::Off, States::Operational>;
// Nested state variants for hierarchy
using OperationalSubState = std::variant<Initializing, Idle, Tracking, Error>;
using TrackingSubState = std::variant<Searching, Locked, Measuring>;Benefits:
- Compile-time type safety
- No virtual function overhead
- Exhaustive pattern matching with
std::visit
bool processEvent(const Event& event)
{
return std::visit(
[this, &event](auto& state) -> bool
{
return this->handleEvent(state, event);
},
currentState_);
}Benefits:
- Type-safe event routing
- Compiler enforces handling all state types
- Clean separation of concerns
struct Idle
{
static constexpr const char* name = "Idle";
void onEntry() const
{
std::cout << " [ENTRY] Idle: Ready for operation\n";
}
void onExit() const
{
std::cout << " [EXIT] Idle: Activating systems\n";
}
};Benefits:
- Resource acquisition/release at state boundaries
- Clear lifecycle management
- Debuggable state transitions
struct Tracking
{
TrackingSubState subState;
void onEntry() const
{
std::cout << " [ENTRY] Tracking\n";
std::visit([](const auto& s) { s.onEntry(); }, subState);
}
void onExit() const
{
std::visit([](const auto& s) { s.onExit(); }, subState);
std::cout << " [EXIT] Tracking\n";
}
};Benefits:
- Encapsulates sub-state behavior
- Proper entry/exit ordering
- Hierarchical event handling
struct Searching
{
static constexpr const char* name = "Searching";
// ...
};Benefits:
- Zero runtime overhead for state names
- Compile-time string constants
- Enables template metaprogramming
return std::visit(
[](const auto& s) -> std::string
{
using T = std::decay_t<decltype(s)>;
if constexpr (std::is_same_v<T, Tracking>)
{
return std::string(s.name) + "::" + s.getSubStateName();
}
else
{
return s.name;
}
},
subState);Benefits:
- Compile-time type selection
- No runtime branching overhead
- Type-specific code paths
- C++17 compatible compiler (GCC 7+, Clang 5+, MSVC 2017+)
- CMake 3.14+
# Create build directory
mkdir build && cd build
# Configure
cmake ..
# Build
cmake --build .
# Run
./bin/laser_tracker_hsmg++ -std=c++17 -pthread -o laser_tracker_hsm main.cpp./laser_tracker_hsm --help # Show help
./laser_tracker_hsm --all # Run all demos
./laser_tracker_hsm --interactive # Interactive mode
./laser_tracker_hsm # Show menu| Command | Description |
|---|---|
power_on |
Power on the laser tracker |
power_off |
Power off the laser tracker |
init_ok |
Signal initialization complete |
init_fail |
Signal initialization failed |
search |
Start searching for target |
found <dist> |
Target found at distance (mm) |
lost |
Target lost |
measure |
Start measuring |
point <x> <y> <z> |
Record a measurement point |
stop |
Stop measuring |
idle |
Return to idle state |
error <code> |
Simulate an error |
reset |
Reset from error state |
state |
Print current state |
help |
Show help |
quit |
Exit |
Basic HSM Demos (1-6):
- Normal Operation Flow - Happy-path workflow from power-on through measurement
- Error Handling and Recovery - Initialization failures and error recovery
- Target Loss and Reacquisition - Handling target loss during tracking
- Invalid Event Handling - Demonstrating ignored events in wrong states
- State Inspection API - Querying state machine state
- Comprehensive Stress Test - Multiple complete cycles
Threaded HSM Demos (7-12): 7. Threaded HSM Basic Operation - Async/sync event sending 8. Commands with State Restrictions - Command validation and execution 9. Synchronous Command Buffering - Message queuing during sync operations 10. JSON Message Protocol - Raw JSON message handling 11. Message Timeout Handling - Timeout behavior demonstration 12. Mixed Events and Commands - Complex multi-threaded scenarios
| Current State | Event | Next State |
|---|---|---|
| Off | PowerOn | Operational::Initializing |
| Operational::* | PowerOff | Off |
| Initializing | InitComplete | Idle |
| Initializing | InitFailed | Error |
| Idle | StartSearch | Tracking::Searching |
| Idle | ErrorOccurred | Error |
| Tracking::* | ReturnToIdle | Idle |
| Tracking::* | ErrorOccurred | Error |
| Searching | TargetFound | Locked |
| Locked | StartMeasure | Measuring |
| Locked | TargetLost | Searching |
| Measuring | MeasurementComplete | Measuring (internal) |
| Measuring | StopMeasure | Locked |
| Measuring | TargetLost | Searching |
| Error | Reset | Initializing |
StateMachine/
├── CMakeLists.txt # CMake build configuration
├── LaserTrackerHSM.hpp # Core HSM implementation (header-only)
├── ThreadedHSM.hpp # Threaded HSM with commands & JSON messaging
├── main.cpp # Demo application
├── .clang-format # Code formatting configuration
├── claude.md # Claude Code guidelines
└── README.md # This file
- Header-Only Implementation: Both
LaserTrackerHSM.hppandThreadedHSM.hppare header-only for easy integration - Value Semantics: States are value types stored in variants, avoiding heap allocation
- No External Dependencies: Uses only C++ standard library (including
<thread>,<mutex>,<future>) - Compile-Time Safety: Type errors are caught at compile time, not runtime
- Unified Messaging: Events and commands use the same message infrastructure
- Built-in JSON: Simple JSON implementation without external library dependencies
This is a demonstration project for educational purposes.