Tutorial 7 — Creating a Client
A SMACC2 client is a state-machine-scoped object that lives inside an orthogonal. In the modern, preferred pattern, a client is a pure orchestrator — it creates and composes components but contains no business logic of its own. In this tutorial you will study the ClPx4Mr client as a model for creating your own.
The Orchestrator Pattern
// cl_px4_mr/cl_px4_mr.hpp
#pragma once
#include <smacc2/smacc.hpp>
#include <cl_px4_mr/components/cp_goal_checker.hpp>
#include <cl_px4_mr/components/cp_offboard_keep_alive.hpp>
#include <cl_px4_mr/components/cp_trajectory_setpoint.hpp>
#include <cl_px4_mr/components/cp_vehicle_command.hpp>
#include <cl_px4_mr/components/cp_vehicle_command_ack.hpp>
#include <cl_px4_mr/components/cp_vehicle_local_position.hpp>
#include <cl_px4_mr/components/cp_vehicle_status.hpp>
namespace cl_px4_mr
{
class ClPx4Mr : public smacc2::ISmaccClient
{
public:
ClPx4Mr();
virtual ~ClPx4Mr();
template <typename TOrthogonal, typename TClient>
void onComponentInitialization()
{
this->createComponent<CpVehicleCommand, TOrthogonal, TClient>();
this->createComponent<CpTrajectorySetpoint, TOrthogonal, TClient>();
this->createComponent<CpVehicleLocalPosition, TOrthogonal, TClient>();
this->createComponent<CpOffboardKeepAlive, TOrthogonal, TClient>();
this->createComponent<CpVehicleStatus, TOrthogonal, TClient>();
this->createComponent<CpVehicleCommandAck, TOrthogonal, TClient>();
this->createComponent<CpGoalChecker, TOrthogonal, TClient>();
}
};
} // namespace cl_px4_mr
That is the entire client. No business logic — just 7 createComponent<>() calls. Every piece of functionality lives in a component (see Tutorial 6 — Components), and every action is performed by a behavior (see Tutorial 5 — Client Behaviors).
onComponentInitialization<TOrthogonal, TClient>()
This template method is called automatically by the framework when the client is created inside an orthogonal. The TOrthogonal and TClient type parameters are forwarded to each component so they can post correctly-typed events.
createComponent<>() creates a component, calls its onInitialize(), and registers it with the state machine.
Client Library Package Structure
A client library is a ROS 2 package that builds into a shared library (.so):
cl_example/
├── include/cl_example/
│ ├── cl_example.hpp # Client header
│ ├── client_behaviors/
│ │ ├── cb_behavior_1.hpp # Behavior headers
│ │ └── cb_behavior_2.hpp
│ └── components/
│ ├── cp_component_1.hpp # Component headers
│ └── cp_component_2.hpp
├── src/cl_example/
│ ├── cl_example.cpp # Client implementation
│ ├── client_behaviors/
│ │ ├── cb_behavior_1.cpp # Behavior implementations
│ │ └── cb_behavior_2.cpp
│ └── components/
│ ├── cp_component_1.cpp # Component implementations
│ └── cp_component_2.cpp
├── CMakeLists.txt
└── package.xml
All the .cpp files compile into a single .so shared library. State machines link against this library and include only the headers they need.
Contrast: Clients with Logic
Some older SMACC2 clients (like ClMoveit2z) put business logic directly in the client:
// cl_moveit2z/cl_moveit2z.hpp (older pattern)
class ClMoveit2z : public smacc2::ISmaccClient
{
public:
ClMoveit2z(std::string groupName);
void onInitialize() override;
std::shared_ptr<moveit::planning_interface::MoveGroupInterface>
moveGroupClientInterface;
std::shared_ptr<moveit::planning_interface::PlanningSceneInterface>
planningSceneInterface;
smacc2::SmaccSignal<void()> onSucceded_;
smacc2::SmaccSignal<void()> onFailed_;
void postEventMotionExecutionSucceded();
void postEventMotionExecutionFailed();
};
This pattern works but is less composable. The orchestrator pattern (ClPx4Mr) is preferred for new clients because:
Components can be reused across different clients
Components can be tested independently
The client itself has no state to debug
Adding functionality means adding a new component, not modifying the client
Creating Your Own Client
Follow these steps:
Identify the components you need (publishers, subscribers, monitors, etc.)
Create each component inheriting from
ISmaccComponent(and optionallyISmaccUpdatable)Create the client inheriting from
ISmaccClientwithonComponentInitialization<>()Create behaviors inheriting from
SmaccClientBehaviororSmaccAsyncClientBehaviorCreate an orthogonal that instantiates your client via
createClient<>()
The orthogonal is simple:
class OrExample : public smacc2::Orthogonal<OrExample>
{
public:
void onInitialize() override
{
this->createClient<ClExample>();
}
};
Then use behaviors in your states:
static void staticConfigure()
{
configure_orthogonal<OrExample, CbBehavior1>(/* args */);
}
Summary
You learned:
The orchestrator pattern: clients compose components, behaviors consume them
onComponentInitialization<TOrthogonal, TClient>()creates all components with type contextClient library package structure
How to create a complete client from scratch
Next Steps
In Tutorial 8 — Events and State Reactors you will learn about custom events, typed events, and state reactors that combine multiple events into complex transition logic.