Prerequisites

This guide targets Ubuntu 22.04 LTS with ROS2 Humble Hawksbill (EOL May 2027), Python 3.10, and the MoveIt2 binary release for Humble. Install the full desktop stack before proceeding:

  • ROS2 Humble desktop-full: sudo apt install ros-humble-desktop-full — includes rviz2, rqt, and all core libraries.
  • MoveIt2: sudo apt install ros-humble-moveit — pulls in moveit_core, moveit_ros_planning, moveit_ros_move_group, and all planner plugins.
  • ros2_control: sudo apt install ros-humble-ros2-control ros-humble-ros2-controllers — mandatory for hardware abstraction.
  • Python packages: pip install transforms3d numpy scipy for kinematic utilities used in the tutorial snippets.
  • colcon: sudo apt install python3-colcon-common-extensions for building your custom packages.

Verify your environment: ros2 doctor --report should show no errors. If you see "network configuration" warnings on a single machine, export ROS_LOCALHOST_ONLY=1 to prevent multicast issues.

URDF Setup

The Unified Robot Description Format (URDF) is the single source of truth for your arm's kinematics, dynamics, and hardware interface. A well-authored URDF prevents the majority of planning failures.

Joint limits are the first thing to get right. Soft limits (used by MoveIt2 for planning) should be 5° inside the hard limits (used by the firmware for emergency stop). If your arm's J2 hardware limit is −120° to +120°, set the URDF lower="-2.0944" and upper="2.0944" (±120° in radians), then set MoveIt2 joint limits to ±117° in joint_limits.yaml.

Inertia tensors are commonly wrong and cause unstable simulation dynamics. For a link of mass m approximated as a cylinder of radius r and length L, use ixx = m*(3r²+L²)/12, iyy = ixx, izz = m*r²/2. Off-diagonal terms are zero for symmetric links. Wrong inertia (e.g., all-zero or placeholder values) will cause Gazebo physics to explode and can make MoveIt2's collision checker behave unpredictably.

Visual vs. collision geometry: Use high-poly meshes (DAE/OBJ) for visual tags and simplified convex hull meshes for collision tags. MoveIt2's FCL collision checker runs at planning time against the collision geometry — over-detailed meshes increase planning time 5–10×. Generate convex hulls with python3 -c "import trimesh; m = trimesh.load('link.dae'); m.convex_hull.export('link_collision.stl')".

The ros2_control hardware interface tag must appear inside your URDF's <robot> element. This tag declares the control interface type and plugin class for your hardware driver:

  • hardware plugin: Set to your driver, e.g. openarm_hardware/OpenArmSystemHardware for OpenArm arms.
  • command interface: Declare position for position control. Also declare velocity and effort if your driver supports them.
  • state interface: Always declare position and velocity. Add effort if the hardware provides torque feedback.
  • param tag: Pass hardware-specific parameters (e.g. serial port, baud rate, robot IP) through param tags inside the hardware element.

MoveIt2 Core Concepts

Understanding these four concepts before writing a single line of planning code will save hours of debugging.

MoveGroup is the high-level planning interface. It wraps the planning pipeline, the planning scene, and the execution manager. You interact with it via the MoveGroupInterface C++ class or the moveit_commander Python wrapper. A MoveGroup is defined for a named group in your SRDF (e.g. "arm", "gripper") and acts as the entry point for all planning requests.

Planning pipeline chains a planner plugin with optional pre/post-processing adapters. MoveIt2 ships three planner backends — choose based on your task:

PlannerBest ForLimitations
OMPL (default)General-purpose, cluttered environments, no dynamics neededNon-deterministic; replanning may give different paths
CHOMPSmooth, gradient-optimized trajectoriesCan get stuck in local minima; slow in tight spaces
PILZ Industrial Motion PlannerDeterministic LIN/PTP/CIRC industrial movesNo obstacle avoidance; requires clear workspace

Planning scene is a live 3D representation of the environment. It starts from your URDF and is updated at runtime with collision objects (boxes, spheres, meshes) added via the PlanningSceneInterface. Always add your table, fixture, and known obstacles to the scene before planning — MoveIt2 plans through free space, not around unknown objects.

Constraint planning: MoveIt2 supports path constraints (e.g., keep end-effector upright throughout motion), joint constraints, position constraints, and orientation constraints. Constraints are specified in the MotionPlanRequest. OMPL's constraint-aware planning requires the ompl_constraint_planning plugin. For simple orientation maintenance (e.g., keep gripper level while moving), an orientation constraint with tolerance ±10° around the pitch and roll axes works reliably.

Motion Planning Tutorial

This four-step workflow takes you from a fresh terminal to executing a collision-checked Cartesian path.

Step 1 — Launch the demo: ros2 launch moveit2_tutorials demo.launch.py brings up rviz2 with the Panda demo arm. Swap the launch file for your own arm's MoveIt2 configuration package (generated by the MoveIt2 Setup Assistant).

Step 2 — Set planning scene: Publish a CollisionObject message on /planning_scene. Add a box representing your work table at the correct pose. Confirm it appears in rviz2's "Planning Scene" display before continuing.

Step 3 — Plan a Cartesian path: Use computeCartesianPath(waypoints, eef_step=0.01, jump_threshold=0.0) where eef_step=0.01 means one waypoint per centimeter of end-effector travel. A return value of 1.0 means 100% of the path was computed. Values below 0.9 typically mean a joint limit or collision was hit mid-path — adjust your waypoints or relax the constraint.

Step 4 — Execute with collision check: Call execute(plan) which passes the trajectory through the trajectory execution manager. The manager streams joint commands to ros2_control at the configured frequency (typically 250–500 Hz). If a collision is detected during execution by the monitored planning scene (requires the is_diff=True scene update stream), MoveIt2 will abort the trajectory.

Teleoperation Servo Mode

moveit_servo enables real-time Cartesian velocity control without replanning. It accepts TwistStamped messages at up to 100 Hz and continuously generates joint velocity commands that track the requested end-effector velocity, subject to joint limits and singularity avoidance.

To launch servo mode for your arm, add the servo node to your launch file with the servo configuration YAML. Key parameters:

  • move_group_name: The planning group to servo — must match your SRDF group name.
  • cartesian_command_in_topic: Default /servo_node/delta_twist_cmds. Publish TwistStamped here at 50–100 Hz.
  • joint_command_out_topic: Output JointTrajectory forwarded to ros2_control.
  • incoming_command_timeout: Set to 0.1s. If no command is received within this window, servo stops the arm — a critical safety feature for teleop.
  • lower_singularity_threshold / hard_stop_singularity_threshold: Set to 17 and 30 (condition number thresholds). The arm will slow down then stop as it approaches singularity.

For teleoperation data collection, publish twist commands derived from your input device (VR controller, SpaceMouse, or glove) at a consistent 100 Hz. Jitter in command frequency causes velocity discontinuities that degrade demonstration quality.

Common Issues and Fixes

  • Controller timing / "Trajectory point is not strictly increasing": Ensure your hardware interface implements HW_IF_POSITION (not HW_IF_VELOCITY) for position-controlled arms. Velocity interfaces require precise timing that USB-based arms cannot guarantee.
  • Joint limit violations during planning: Apply a 5° (0.0873 rad) safety margin in joint_limits.yaml by setting max_position and min_position inside the hard limits. This prevents the planner from generating trajectories that trigger the hardware's limit switches mid-execution.
  • Unstable dynamics in simulation: Wrong URDF inertia values are the most common cause. Verify each link's inertia with ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(xacro my_robot.urdf.xacro)" and inspect inertia in the rqt robot description viewer.
  • MoveGroup fails to connect: Check that the move_group node is running (ros2 node list | grep move_group) and that your RMW (default Cyclone DDS) has the same domain ID as your other nodes. Set ROS_DOMAIN_ID=0 consistently.
  • Servo produces jerky motion: Ensure your servo command loop runs at a fixed rate. Use a ROS2 timer with create_timer(0.01, callback) rather than a while loop with sleep().

Key Packages Reference

PackagePurposeInstall
moveit_servoReal-time Cartesian servo / teleopIncluded in ros-humble-moveit
moveit_planners_omplOMPL sampling-based plannersIncluded in ros-humble-moveit
moveit_visual_toolsrviz2 debugging markers and trajectory visualizationros-humble-moveit-visual-tools
joint_state_broadcasterBroadcasts /joint_states from ros2_controlros-humble-ros2-controllers
robot_state_publisherPublishes TF tree from URDF + joint statesros-humble-robot-state-publisher
moveit_ros_perceptionOctoMap integration for 3D obstacle avoidanceIncluded in ros-humble-moveit

OpenArm 101 MoveIt2 Configuration

The OpenArm 101 ships with pre-built MoveIt2 configuration, but understanding the structure helps when customizing for your tasks.

  • URDF/XACRO: openarm_description/urdf/openarm_101.urdf.xacro defines 6 revolute joints + 1 prismatic gripper joint. Joint limits are set 5 degrees inside hardware limits. Inertia tensors are computed from CAD models.
  • SRDF: openarm_moveit_config/config/openarm_101.srdf defines the "arm" planning group (joints 1-6) and "gripper" group (gripper joint). Two named poses: "home" (all zeros) and "ready" (joints at [0, -0.5, 0.7, 0, 0.8, 0] rad).
  • joint_limits.yaml: Velocity limits set to 1.0 rad/s for safety during teleoperation. Acceleration limits at 2.0 rad/s^2. Adjust these for faster autonomous motion after testing.
  • kinematics.yaml: KDL solver by default. For tasks requiring IK near singularity, switch to trac_ik: sudo apt install ros-humble-trac-ik-kinematics-plugin and update kinematics.yaml.

Python MoveGroupInterface Example

A complete Python example that plans and executes a pick-and-place sequence using the MoveIt2 Python API:

#!/usr/bin/env python3
"""MoveIt2 pick-and-place example for OpenArm 101."""
import rclpy
from rclpy.node import Node
from moveit2 import MoveIt2
from geometry_msgs.msg import PoseStamped
import numpy as np

class PickPlace(Node):
    def __init__(self):
        super().__init__("pick_place")
        self.moveit2 = MoveIt2(
            node=self,
            joint_names=[f"joint_{i}" for i in range(1, 7)],
            base_link_name="base_link",
            end_effector_name="gripper_link",
            group_name="arm",
        )
        # Wait for MoveIt2 to be ready
        self.moveit2.wait_for_execution(timeout=10.0)

    def execute(self):
        # 1. Move to ready pose
        self.moveit2.move_to_configuration(
            [0.0, -0.5, 0.7, 0.0, 0.8, 0.0]
        )
        self.moveit2.wait_for_execution()

        # 2. Move to pick pose (Cartesian)
        pick_pose = PoseStamped()
        pick_pose.header.frame_id = "base_link"
        pick_pose.pose.position.x = 0.3
        pick_pose.pose.position.y = 0.0
        pick_pose.pose.position.z = 0.05
        pick_pose.pose.orientation.w = 1.0
        self.moveit2.move_to_pose(pick_pose)
        self.moveit2.wait_for_execution()

        # 3. Close gripper
        self.moveit2.move_to_configuration(
            joint_positions=[0.0],  # closed
            joint_names=["gripper_joint"],
            group_name="gripper",
        )
        self.moveit2.wait_for_execution()

        # 4. Move to place pose
        place_pose = PoseStamped()
        place_pose.header.frame_id = "base_link"
        place_pose.pose.position.x = 0.3
        place_pose.pose.position.y = -0.2
        place_pose.pose.position.z = 0.1
        place_pose.pose.orientation.w = 1.0
        self.moveit2.move_to_pose(place_pose)
        self.moveit2.wait_for_execution()

        # 5. Open gripper
        self.moveit2.move_to_configuration(
            joint_positions=[0.04],  # open
            joint_names=["gripper_joint"],
            group_name="gripper",
        )
        self.moveit2.wait_for_execution()

def main():
    rclpy.init()
    node = PickPlace()
    node.execute()
    rclpy.shutdown()

if __name__ == "__main__":
    main()

Cartesian Path Planning: Detailed Workflow

Cartesian paths are essential for tasks requiring the end-effector to follow a specific trajectory in 3D space (wiping, drawing, insertion). Key considerations:

  • Waypoint spacing: Set eef_step=0.01 (1 cm between waypoints) for smooth motion. Larger steps (5 cm) produce jerky motion at corners. Smaller steps (<5 mm) increase planning time without visible improvement.
  • Jump threshold: Set jump_threshold=0.0 to disable the jump detector (recommended for most tasks). A non-zero threshold aborts planning if any joint moves more than the threshold between adjacent waypoints, which catches IK discontinuities but also rejects valid paths near joint limits.
  • Fraction check: computeCartesianPath returns a fraction (0.0-1.0) indicating how much of the requested path was achievable. If fraction < 0.95, the path likely crosses a joint limit or collision boundary. Adjust the approach direction or relax the orientation constraint.
  • Orientation constraints during Cartesian motion: For tasks like pouring or inserting, add an orientation constraint to keep the gripper level. Set orientation tolerance to +/-10 degrees around pitch and roll axes, with 360-degree freedom in yaw.

Collision Checking: Adding Objects to the Planning Scene

MoveIt2 only avoids collisions with objects it knows about. Always add your workspace objects before planning:

  • Table surface: Add as a box primitive with dimensions matching your table, positioned at the correct height. This is the most commonly missing collision object and causes arm-table crashes.
  • Camera mounts: Add simplified box shapes for overhead and side camera mounts. These are often forgotten until the arm hits a camera.
  • Attached objects: When the gripper grasps an object, attach it to the end-effector link using attach_collision_object. This prevents the planner from generating paths that collide the grasped object with the environment. Detach when placing.
  • OctoMap (dynamic obstacles): For environments with moving obstacles, feed a depth camera's point cloud to MoveIt2's OctoMap integration. MoveIt2 builds a 3D occupancy map and plans around it. Use moveit_ros_perception and set octomap_resolution: 0.02 (2 cm voxels) for a good balance of detail and planning speed.

Related Guides

Work with SVRC

SVRC provides ROS2 and MoveIt2 integration support for all arm platforms we sell and service.

  • OpenArm Resources -- open-source URDF, MoveIt2 config, and ros2_control driver for OpenArm 101
  • Hardware Store -- purchase OpenArm 101 ($4,500) with pre-configured ROS2 driver
  • Repair and Maintenance -- ROS2 integration services, driver development, and calibration
  • Data Platform -- upload teleoperation data collected via MoveIt2 servo mode
  • Contact Us -- request ROS2 integration support for your arm platform