Tutorial 1 — Your First State Machine
In this tutorial you will clone the SMACC2 repository, build the sm_atomic state machine, run it, and walk through every source file to understand how SMACC2 state machines are structured.
Prerequisites
ROS 2 Jazzy installed and sourced (see Getting Started)
A colcon workspace (e.g.
~/ros2_ws/)
Build and Run sm_atomic
# Source ROS 2
source /opt/ros/jazzy/setup.bash
# Clone SMACC2 into your workspace (skip if already cloned)
cd ~/ros2_ws/src
git clone https://github.com/robosoft-ai/SMACC2.git
# Build only sm_atomic and its dependency
cd ~/ros2_ws
colcon build --packages-select sm_atomic cl_ros2_timer
# Source the workspace
source install/setup.bash
# Launch the state machine
ros2 launch sm_atomic sm_atomic.py
You should see log output showing the state machine cycling between State1 and State2.
Project Structure
sm_atomic/
├── include/sm_atomic/
│ ├── sm_atomic.hpp # State machine definition
│ ├── orthogonals/
│ │ └── or_timer.hpp # Timer orthogonal
│ └── states/
│ ├── st_state_1.hpp # State 1
│ └── st_state_2.hpp # State 2
├── src/sm_atomic/
│ └── sm_atomic_node.cpp # Entry point
├── launch/
│ └── sm_atomic.py # ROS 2 launch file
├── config/
│ └── sm_atomic_config.yaml # Parameters
├── CMakeLists.txt
└── package.xml
The Entry Point
The node file boots ROS 2 and hands control to the SMACC2 runtime:
// sm_atomic_node.cpp
#include <sm_atomic/sm_atomic.hpp>
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
smacc2::run<sm_atomic::SmAtomic>();
}
smacc2::run<>() creates the state machine, enters the initial state, and spins the ROS 2 node until shutdown.
The State Machine
// sm_atomic.hpp
#include <smacc2/smacc.hpp>
// CLIENTS
#include <cl_ros2_timer/cl_ros2_timer.hpp>
// CLIENT BEHAVIORS
#include <cl_ros2_timer/client_behaviors/cb_timer_countdown_loop.hpp>
#include <cl_ros2_timer/client_behaviors/cb_timer_countdown_once.hpp>
// ORTHOGONALS
#include "orthogonals/or_timer.hpp"
using namespace boost;
using namespace smacc2;
namespace sm_atomic
{
// STATE forward declarations
class State1;
class State2;
// STATE_MACHINE
struct SmAtomic : public smacc2::SmaccStateMachineBase<SmAtomic, State1>
{
using SmaccStateMachineBase::SmaccStateMachineBase;
virtual void onInitialize() override { this->createOrthogonal<OrTimer>(); }
};
} // namespace sm_atomic
#include "states/st_state_1.hpp"
#include "states/st_state_2.hpp"
Key points:
SmaccStateMachineBase<SmAtomic, State1>— the first template parameter is the state machine itself (CRTP), the second is the initial state.onInitialize()creates all orthogonals. Orthogonals are created once and live for the entire state machine lifetime.State headers are included after the class definition so they can reference
SmAtomicand the forward-declared state names.
The Orthogonal
// orthogonals/or_timer.hpp
#include <chrono>
#include <cl_ros2_timer/cl_ros2_timer.hpp>
#include <smacc2/smacc.hpp>
using namespace std::chrono_literals;
namespace sm_atomic
{
class OrTimer : public smacc2::Orthogonal<OrTimer>
{
public:
void onInitialize() override
{
this->createClient<cl_ros2_timer::ClRos2Timer>();
}
};
} // namespace sm_atomic
Orthogonal<OrTimer>uses CRTP to identify the orthogonal type at compile time.createClient<ClRos2Timer>()creates the timer client. The client lives as long as the state machine.
State 1
// states/st_state_1.hpp
#include <smacc2/smacc.hpp>
namespace sm_atomic
{
using namespace cl_ros2_timer;
using namespace smacc2::default_transition_tags;
struct State1 : smacc2::SmaccState<State1, SmAtomic>
{
using SmaccState::SmaccState;
// TRANSITION TABLE
typedef mpl::list<
Transition<EvTimer<CbTimerCountdownOnce, OrTimer>, State2, SUCCESS>
>reactions;
// STATE FUNCTIONS
static void staticConfigure()
{
configure_orthogonal<OrTimer, CbTimerCountdownLoop>(3s);
configure_orthogonal<OrTimer, CbTimerCountdownOnce>(5s);
}
void runtimeConfigure() {}
void onEntry() { RCLCPP_INFO(getLogger(), "On Entry!"); }
void onExit() { RCLCPP_INFO(getLogger(), "On Exit!"); }
};
} // namespace sm_atomic
Walking through each piece:
SmaccState<State1, SmAtomic>— first param is the state (CRTP), second is the parent (the state machine).Transition table —
mpl::listofTransition<Event, TargetState, Tag>. WhenEvTimerfires fromCbTimerCountdownOnceonOrTimer, the machine transitions toState2taggedSUCCESS.staticConfigure()— called once at compile-time registration. It assigns client behaviors to orthogonals. Here, two behaviors run concurrently onOrTimer: a countdown loop (fires every 3 seconds) and a countdown once (fires after 5 seconds).onEntry()/onExit()— called when the state is entered or exited.
State 2
// states/st_state_2.hpp
#include <smacc2/smacc.hpp>
namespace sm_atomic
{
using namespace cl_ros2_timer;
using namespace smacc2::default_transition_tags;
struct State2 : smacc2::SmaccState<State2, SmAtomic>
{
using SmaccState::SmaccState;
typedef mpl::list<
Transition<EvTimer<CbTimerCountdownOnce, OrTimer>, State1, SUCCESS>
>reactions;
static void staticConfigure()
{
configure_orthogonal<OrTimer, CbTimerCountdownOnce>(5s);
}
void runtimeConfigure() { RCLCPP_INFO(getLogger(), "Entering State2"); }
void onEntry() { RCLCPP_INFO(getLogger(), "On Entry!"); }
void onExit() { RCLCPP_INFO(getLogger(), "On Exit!"); }
};
} // namespace sm_atomic
State2 transitions back to State1 after 5 timer ticks, creating an infinite loop: State1 → State2 → State1 → …
How It Works
smacc2::run<SmAtomic>()creates the state machine and callsonInitialize(), which createsOrTimer.The machine enters
State1.staticConfigure()attachesCbTimerCountdownLoop(3s)andCbTimerCountdownOnce(5s)toOrTimer.After 5 seconds,
CbTimerCountdownOncefiresEvTimer.The transition table matches the event and the machine transitions to
State2.State1::onExit()runs, thenState2::onEntry()runs.State2 configures its own
CbTimerCountdownOnce(5s)and the cycle repeats.
Observing with the SMACC RTA
The SMACC2 Runtime Analyzer (RTA) provides a live visualization of your state machine. While the state machine is running, you can connect to it at robosoft.ai/viewer to see:
The current active state
State transition history
Orthogonal and client behavior status
You can also inspect state machine status from the command line:
ros2 topic echo /sm_atomic/smacc/status
Next Steps
In Tutorial 2 — Adding States and Transitions you will add a third state and learn about custom transition tags.