state/motion stream stay the same.
Why MoveIt isn’t in the SDK
The SDK is framework-neutral. MoveIt and its message package,moveit_msgs, exist only in ROS images, so code that imports them belongs to the driver that ships those images. The SDK defines the contract (MotionBackend) and a registry. Your driver supplies the implementation.
Run MoveIt as a sidecar
Runmove_group in its own container beside your driver, the way the Kinova and Piper drivers do:
- a
moveitcompose service on thecyberwaveos/ros2-moveitimage, in the twin’s ROS namespace; - the plant (your driver or its vendor node) publishes
robot_description, the TF tree, joint states and the MoveIt config package for this arm; - trajectory bridges expose the arm’s
FollowJointTrajectoryaction, so MoveIt executes through the same controller as everything else.
ros-$ROS_DISTRO-moveit-msgs. Gate the moveit service’s health on an advertised action, such as sequence_move_group, not on the node name: the node registers before the robot model has loaded.
Write a MotionBackend
Put the backend in your driver package, next to theBaseROS2Driver node that owns its ROS clients. It needs:
nameandplanning_frame;available()andunavailable_refusal(): cheap checks that the action servers and services are ready, and the reason when they aren’t;validate_goal(goal)andplan_and_execute(goal, on_update, on_done): rclpy action clients forsequence_move_group(Pilz PTP and LIN) andmove_action(OMPL). A goal is aMotionGoal(one pose, PTP or LIN) or aGraspGoal(ranked grasp candidates). Both arrive in your planning frame. A backend without a grasp search refuses aGraspGoalinvalidate_goal;apply_scene(request, diff),attach(request)anddetach(object_id): theapply_planning_sceneandget_planning_sceneservices, sent as diffs;cancel(): cancel the active goal, fire and forget.
moveit_msgs lazily, inside functions, or behind a guard. The driver then still starts without it (in unit tests, manifest export, or an image without MoveIt) and reports why planning is unavailable instead of crashing.
Register it and switch from config
Extend the registry with_motion_backend_factories and assign kinematics_backend in configure():
"pinocchio" registered by extending super()’s dictionary rather than replacing it. If MoveIt plans in a frame other than the arm’s base frame, also override _motion_planning_frame() so the command catalog describes the frame you plan in.
Status and refusals
The SDK owns the contract, so a backend only reports facts:state/motioncarrieskinematics_backend(the driver’s default backend),backend_available,backend(the one answering the current motion), andplanner, the planner name your backend reports (for examplepilz_ptp,pilz_linorompl);- when
available()is false, every planned-motion command is refused withbackend_unavailable. Build the refusal withbackend_unavailable(name, detail)so the detail names the backend; - a failed plan is
planning_failed, a controller that didn’t follow isexecution_failed, and a stopped goal iscancelled; - guards, their order, scene revisions and the attached payload are handled by the SDK before your backend is called. A backend first used after a
scene_setis given that scene, and any carried payload, before its first goal.
kinematics_backend names it. A request can always name another registered backend in backend, for example "pinocchio" to plan in-process while MoveIt is the default.
Test with fakes
Every decision in a backend can run without ROS. Inject the MoveIt client into the backend, and test against a fake that records goals and answers with chosen error codes. At the driver level, register a fakeMotionBackend through _motion_backend_factories, then drive the same command sequence with kinematics_backend = "pinocchio" and with your backend’s name. The two state/motion payloads should match, apart from the backend and planner names and the timing.
Reference implementations
- The Piper driver’s MoveIt backend,
cw_piper/motion/moveit/incyberwave-edge-nodes/piper/public: configuration, the planner chain (Pilz PTP, then OMPL on failures it can route around), the grasp as Pilz sequences,MoveItErrorCodesclassification and the rclpy client, with its tests. - The Kinova Gen3 driver’s bridge,
cyberwave_kinova_bringup/scripts/cyberwave_bridge.py: a guardedmoveit_msgsimport and aMoveGroupSequenceclient for Cartesian waypoint missions.