How to Use MoveIt2 with SMACC2
Getting Started
Required Installations
ROS 2 Jazzy
# Follow https://docs.ros.org/en/jazzy/Installation.html
sudo apt install ros-jazzy-desktop
MoveIt2
sudo apt install ros-jazzy-moveit
Robot Description + MoveIt Config (e.g., Panda):
sudo apt install ros-jazzy-moveit-resources-panda-moveit-config
Assembling the Workspace
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src
# SMACC2 framework and client libraries
git clone https://github.com/robosoft-ai/SMACC2.git -b jazzy
The packages you need are:
smacc2— core state machine frameworksmacc2_msgs— SMACC2 message definitionscl_moveit2z— MoveIt2 client library (insideSMACC2/smacc2_client_library/)sm_panda_cl_moveit2z_cb_inventory— reference state machine (insideSMACC2/smacc2_sm_reference_library/)
Building the Workspace
cd ~/ros2_ws
source /opt/ros/jazzy/setup.bash
colcon build --packages-select cl_moveit2z sm_panda_cl_moveit2z_cb_inventory
source install/setup.bash
Note
You can also use colcon build --packages-up-to sm_panda_cl_moveit2z_cb_inventory
to build only the required dependency chain.
Launching the Application
You need two terminals. Source the workspace in each:
source ~/ros2_ws/install/setup.bash
Terminal 1 — MoveIt2 Demo (move_group + RViz)
ros2 launch moveit_resources_panda_moveit_config demo.launch.py
Terminal 2 — State Machine
source ~/ros2_ws/install/setup.bash
ros2 launch sm_panda_cl_moveit2z_cb_inventory \
sm_panda_cl_moveit2z_cb_inventory.py
The state machine executes a behavior inventory automatically:
Wait(15s) → AcquireSensors → MoveKnownState → MoveJoints → MoveCartesianRelative →
UndoLastTrajectory → EndEffectorRotate → MoveEndEffector → ...
Monitor with:
# Current state
ros2 topic echo /sm_panda_cl_moveit2z_cb_inventory/smacc/status
# Transition log
ros2 topic echo /sm_panda_cl_moveit2z_cb_inventory/smacc/transition_log
Tour of the MoveIt2 Client Behavior Library
The cl_moveit2z client library wraps the MoveIt2 MoveGroupInterface
and PlanningSceneInterface for SMACC2 integration. It provides
component-based motion planning, trajectory execution, and object
manipulation.
For the full API reference, see the cl_moveit2z source.
Folder Structure
cl_moveit2z/
├── include/cl_moveit2z/
│ ├── cl_moveit2z.hpp # Client
│ ├── client_behaviors.hpp # Behavior includes
│ ├── common.hpp
│ ├── client_behaviors/
│ │ ├── cb_move_joints.hpp
│ │ ├── cb_move_end_effector.hpp
│ │ ├── cb_move_known_state.hpp
│ │ ├── cb_move_cartesian_relative2.hpp
│ │ ├── cb_move_end_effector_trajectory.hpp
│ │ ├── cb_end_effector_rotate.hpp
│ │ ├── cb_circular_pivot_motion.hpp
│ │ ├── cb_attach_object.hpp
│ │ ├── cb_detach_object.hpp
│ │ └── cb_undo_last_trajectory.hpp
│ └── components/
│ ├── cp_motion_planner.hpp
│ ├── cp_trajectory_executor.hpp
│ ├── cp_trajectory_history.hpp
│ ├── cp_trajectory_visualizer.hpp
│ ├── cp_joint_space_trajectory_planner.hpp
│ ├── cp_tf_listener.hpp
│ └── cp_grasping_objects.hpp
├── src/cl_moveit2z/
│ └── cl_moveit2z.cpp
├── CMakeLists.txt
└── package.xml
Components
Unlike ClPx4Mr or ClNav2Z, ClMoveit2z does not create components
in onComponentInitialization(). All components are created at the
orthogonal level, giving the state machine full control over which components
are active.
// ClMoveit2z constructors
ClMoveit2z(std::string groupName);
ClMoveit2z(const moveit::planning_interface::MoveGroupInterface::Options & options);
Component |
Purpose |
Key Methods / Signals |
|---|---|---|
|
Joint-space and Cartesian planning |
|
|
Execute planned trajectories |
|
|
Store executed trajectories for undo |
|
|
RViz trajectory visualization |
|
|
IK-based planning from Cartesian waypoints |
|
|
Transform listener |
|
|
Collision objects for pick-and-place |
|
Behaviors
Behavior |
Constructor Parameters |
Description |
|---|---|---|
|
|
Move to a joint configuration |
|
|
Move end-effector to a Cartesian pose |
|
|
Load joint values from YAML and move |
|
|
Relative Cartesian motion |
|
|
Follow a Cartesian trajectory |
|
|
Rotate end-effector around its Z-axis |
|
|
Circular pivot motion |
|
optional |
Attach a collision object to the gripper |
|
(none) |
Detach the attached collision object |
|
optional |
Replay a recorded trajectory in reverse |
All behaviors post EvCbSuccess on completion and EvCbFailure on error.
Trajectory behaviors may also post EvJointDiscontinuity or
EvIncorrectInitialPosition.
Using the MoveIt2 Client Behavior Library
Configuring the Orthogonal
Create an orthogonal that instantiates the ClMoveit2z client with a
planning group name, then add the components you need:
#include <cl_moveit2z/cl_moveit2z.hpp>
class OrArm : public smacc2::Orthogonal<OrArm>
{
public:
void onInitialize() override
{
auto client =
this->createClient<cl_moveit2z::ClMoveit2z>("panda_arm");
client->createComponent<cl_moveit2z::CpJointSpaceTrajectoryPlanner>();
client->createComponent<cl_moveit2z::CpTrajectoryExecutor>();
client->createComponent<cl_moveit2z::CpTfListener>();
client->createComponent<cl_moveit2z::CpTrajectoryVisualizer>();
client->createComponent<cl_moveit2z::CpTrajectoryHistory>();
auto graspingComponent = client->createComponent<
cl_moveit2z::CpGraspingComponent>();
graspingComponent->gripperLink_ = "tool0";
graspingComponent->createGraspableBox(
"virtualBox", 0, 0.5, 0.5, 0.1, 0.1, 0.1);
}
};
Register the orthogonal in your state machine’s onInitialize():
struct SmMyManipulation
: smacc2::SmaccStateMachineBase<SmMyManipulation, StInitial>
{
void onInitialize() override
{
this->createOrthogonal<OrArm>();
}
};
Using a Behavior
Behaviors are configured in a state’s staticConfigure() using
configure_orthogonal<>. Constructor parameters are passed as arguments.
The behavior executes asynchronously on state entry and posts events
that drive transitions:
#include <cl_moveit2z/client_behaviors/cb_move_joints.hpp>
struct StMoveJoints1
: smacc2::SmaccState<StMoveJoints1, SmMyManipulation>
{
using SmaccState::SmaccState;
typedef boost::mpl::list<
Transition<EvCbSuccess<CbMoveJoints, OrArm>, StNextState, SUCCESS>
> reactions;
static void staticConfigure()
{
std::map<std::string, double> jointValues{
{"panda_joint1", 0.0},
{"panda_joint2", 0.0},
{"panda_joint3", 0.0},
{"panda_joint4", -M_PI / 2},
{"panda_joint5", 0.0},
{"panda_joint6", M_PI / 2},
{"panda_joint7", 0.0}
};
configure_orthogonal<OrArm, CbMoveJoints>(jointValues);
}
void runtimeConfigure()
{
// Optional: adjust behavior parameters after instantiation
this->getClientBehavior<OrArm, CbMoveJoints>()->scalingFactor_ = 1;
}
};
The pattern is the same for all MoveIt2 behaviors — change the behavior class and its parameters:
// Move end-effector to a Cartesian pose
geometry_msgs::msg::PoseStamped target_pose;
target_pose.header.frame_id = "panda_link0";
target_pose.pose.position.x = 0.3;
target_pose.pose.position.y = 0.2;
target_pose.pose.position.z = 0.4;
target_pose.pose.orientation.w = 1.0;
configure_orthogonal<OrArm, CbMoveEndEffector>(target_pose, "panda_link8");
// Move to a named configuration loaded from YAML
configure_orthogonal<OrArm, CbMoveKnownState>(
"sm_panda_cl_moveit2z_cb_inventory", "config/known_states/home.yaml");
// Rotate end-effector 90 degrees around its Z-axis
configure_orthogonal<OrArm, CbEndEffectorRotate>(M_PI / 2);
SOFT Requirement Pattern
MoveIt2 behaviors use the SOFT requirement pattern for optional
component access. When a behavior calls requiresComponent() with
ComponentRequirement::SOFT, it gracefully degrades if the component
isn’t present:
// Inside a behavior's onEntry():
CpMotionPlanner * motionPlanner = nullptr;
this->requiresComponent(motionPlanner,
smacc2::ComponentRequirement::SOFT);
if (motionPlanner != nullptr)
{
// Use component-based approach (preferred)
auto result = motionPlanner->planToJointTarget(
jointValueTarget_, options);
}
else
{
// Fallback to legacy direct MoveGroupInterface calls
moveGroupInterface.setJointValueTarget(jointValueTarget_);
auto result = moveGroupInterface.plan(plan);
}
The contrast with HARD (the default): a HARD requirement causes an
error if the component is missing. Use HARD for essential components,
SOFT for optional enhancements.
Planning Groups and Frames
MoveIt2 organizes joints into planning groups defined in the robot’s
SRDF. The group name passed to ClMoveit2z determines which joints the
planner controls:
Parameter |
Example (Panda) |
Description |
|---|---|---|
Planning group |
|
Joint group for motion planning |
End-effector link |
|
Tip link for Cartesian goals |
Reference frame |
|
Base frame for pose targets |
Gripper link |
|
Link for object attachment |
Writing Your Own Behavior
To add a new manipulation behavior, create a class that inherits from
SmaccAsyncClientBehavior, acquires the components it needs, and posts
events when done.
Here is a complete example — a behavior that moves to a joint configuration and then opens the gripper:
Header (include/cl_moveit2z/client_behaviors/cb_move_and_open_gripper.hpp):
#pragma once
#include <smacc2/smacc.hpp>
namespace cl_moveit2z
{
class CpMotionPlanner;
class CpTrajectoryExecutor;
class CbMoveAndOpenGripper : public smacc2::SmaccAsyncClientBehavior
{
public:
CbMoveAndOpenGripper(
std::map<std::string, double> jointTarget);
void onEntry() override;
void onExit() override;
private:
std::map<std::string, double> jointTarget_;
CpMotionPlanner * motionPlanner_ = nullptr;
CpTrajectoryExecutor * executor_ = nullptr;
};
} // namespace cl_moveit2z
Source (src/cl_moveit2z/client_behaviors/cb_move_and_open_gripper.cpp):
#include <cl_moveit2z/client_behaviors/cb_move_and_open_gripper.hpp>
#include <cl_moveit2z/components/cp_motion_planner.hpp>
#include <cl_moveit2z/components/cp_trajectory_executor.hpp>
namespace cl_moveit2z
{
CbMoveAndOpenGripper::CbMoveAndOpenGripper(
std::map<std::string, double> jointTarget)
: jointTarget_(jointTarget)
{
}
void CbMoveAndOpenGripper::onEntry()
{
// 1. Acquire components
this->requiresComponent(motionPlanner_);
this->requiresComponent(executor_);
// 2. Plan to joint target
moveit::planning_interface::MoveGroupInterface::Plan plan;
auto result = motionPlanner_->planToJointTarget(jointTarget_);
if (result)
{
// 3. Execute the plan
executor_->executePlan(result.value());
this->postSuccessEvent();
}
else
{
RCLCPP_ERROR(getLogger(), "Planning failed");
this->postFailureEvent();
}
}
void CbMoveAndOpenGripper::onExit() {}
} // namespace cl_moveit2z
The key steps for any custom MoveIt2 behavior:
Acquire components with
requiresComponent()inonEntry()Plan through
CpMotionPlannerExecute through
CpTrajectoryExecutorPost events (
postSuccessEvent()/postFailureEvent())Clean up in
onExit()
Writing Your Own Component
Components manage reusable logic that multiple behaviors share. Write a new component when you need to:
Add planning scene management not covered by existing components
Implement custom motion planning strategies
Add reusable monitoring (e.g., force/torque thresholds)
A component inherits from ISmaccComponent and optionally from
ISmaccUpdatable for periodic updates.
Here is a complete example — a component that manages named joint configurations:
Header (include/cl_moveit2z/components/cp_named_configurations.hpp):
#pragma once
#include <smacc2/smacc.hpp>
#include <map>
#include <string>
namespace cl_moveit2z
{
class CpNamedConfigurations : public smacc2::ISmaccComponent
{
public:
CpNamedConfigurations();
virtual ~CpNamedConfigurations();
void onInitialize() override;
void addConfiguration(const std::string & name,
const std::map<std::string, double> & joints);
std::map<std::string, double> getConfiguration(
const std::string & name) const;
bool hasConfiguration(const std::string & name) const;
private:
std::map<std::string, std::map<std::string, double>> configs_;
};
} // namespace cl_moveit2z
Source (src/cl_moveit2z/components/cp_named_configurations.cpp):
#include <cl_moveit2z/components/cp_named_configurations.hpp>
namespace cl_moveit2z
{
CpNamedConfigurations::CpNamedConfigurations() {}
CpNamedConfigurations::~CpNamedConfigurations() {}
void CpNamedConfigurations::onInitialize()
{
RCLCPP_INFO(getLogger(), "CpNamedConfigurations: initialized");
}
void CpNamedConfigurations::addConfiguration(
const std::string & name,
const std::map<std::string, double> & joints)
{
configs_[name] = joints;
}
std::map<std::string, double>
CpNamedConfigurations::getConfiguration(
const std::string & name) const
{
return configs_.at(name);
}
bool CpNamedConfigurations::hasConfiguration(
const std::string & name) const
{
return configs_.count(name) > 0;
}
} // namespace cl_moveit2z
Add the component at the orthogonal level:
auto configs = client->createComponent<cl_moveit2z::CpNamedConfigurations>();
configs->addConfiguration("home", {
{"panda_joint1", 0.0}, {"panda_joint2", 0.0},
{"panda_joint3", 0.0}, {"panda_joint4", -M_PI/2},
{"panda_joint5", 0.0}, {"panda_joint6", M_PI/2},
{"panda_joint7", 0.0}
});
When to write a component vs. putting logic in a behavior:
Criterion |
Component |
Behavior |
|---|---|---|
Lifetime |
State machine scoped (lives as long as the client) |
State scoped (created/destroyed with each state) |
Reusability |
Shared across multiple behaviors |
Single-purpose per state |
ROS 2 I/O |
Owns publishers/subscribers |
Uses components for I/O |
Signals |
Emits signals for behaviors to connect to |
Connects to component signals, posts state machine events |