Create MoveIt2 configuration package for a custom robot

Objective

In this lesson, we configure MoveIt2 for our custom robot.

The goal is not only to generate a MoveIt configuration package, but also to understand what MoveIt is doing, how it connects to ROS 2 Control, and where the critical files are when we want to switch between:

RViz-only simulation

Gazebo simulation

Real robot execution

By the end of this lesson, you should understand:

What MoveIt2 is used for.

How to test MoveIt2 with the Panda robot.

How to create a MoveIt configuration package with MoveIt Setup Assistant.

The difference between ROS 2 Controllers and MoveIt Controllers.

Why the automatically generated ros2_control.xacro file can create conflicts.

How to structure a custom launch file that brings up MoveIt, ROS 2 Control, RViz, and the required controllers.

What, Why, How

What are we doing?

We are taking our custom robot description and generating a MoveIt2 configuration package.

This package will contain the files needed by MoveIt to understand:

The robot model.

The planning groups.

The kinematics solver.

The joint limits.

The controller configuration.

The RViz configuration.

The launch files.

MoveIt is the layer that allows us to plan robot motion.

ROS 2 Control is the layer that executes the motion through controllers.

The important concept is this:

MoveIt plans the trajectory.
ROS 2 Control executes the trajectory.
The hardware interface or simulator applies the command to the robot.

Why are we doing this?

Until now, we moved the robot in Gazebo by sending commands directly to ROS 2 controllers.

That is useful to understand low-level control.

But in a real robot application, especially with a 6-axis industrial robot, we usually do not want to manually write every joint trajectory.

We want to say:

Move the end effector here.

Avoid collisions.

Respect joint limits.

Plan a valid path.

Execute the trajectory.

This is exactly where MoveIt becomes important.

MoveIt is widely used because it gives us a standard motion planning layer for robotic arms. It can compute inverse kinematics, plan collision-free trajectories, interface with different planners such as OMPL, and send the final trajectory to controllers.

How are we doing it?

We follow this workflow:

First, we install MoveIt2.

Then we launch a Panda robot demo to verify that MoveIt works correctly.

Then we open MoveIt Setup Assistant.

We load our custom robot description.

We define the planning group.

We generate the MoveIt configuration package.

Then we inspect and clean the generated files, especially the controller-related files.

Finally, we create a custom launch file that starts the correct nodes and controllers.

Installing and Testing MoveIt2

For the installation, I recommend following the official MoveIt2 installation page directly.

In this video, I will only show the main idea.

For ROS 2 Humble on Ubuntu 22.04, the installation is usually done with:

sudo apt install ros-humble-moveit

After the installation, we want to verify that MoveIt is working.

A common way is to launch a Panda robot MoveIt demo.

Depending on the packages installed on your machine, the command can be similar to:

ros2 launch moveit_resources_panda_moveit_config demo.launch.py

or, if you are following the MoveIt tutorials workspace:

ros2 launch moveit2_tutorials demo.launch.py

The goal here is simple: before configuring our custom robot, we want to make sure MoveIt itself is installed and working.

When RViz opens, you will see the Panda robot and the MoveIt MotionPlanning panel.

In RViz, the end effector has interactive markers.

The arrows are used to change the position of the end effector.

The rotation rings are used to change the orientation of the end effector.

So visually, you can drag the end effector to a new pose, click Plan, and then Execute.

This is the basic MoveIt workflow:

Target pose→ inverse kinematics→ motion planning→ trajectory execution

At this point, the robot is not connected to real hardware. This demo usually uses a fake or mock execution mode.

That is useful because we can test planning without needing Gazebo or a physical robot.

Creating the MoveIt Configuration Package

Now we create the MoveIt configuration package for our custom robot.

We launch the MoveIt Setup Assistant:

ros2 launch moveit_setup_assistant setup_assistant.launch.py

The Setup Assistant is a graphical tool that generates the MoveIt configuration package from a URDF or Xacro file.

In our case, we load the custom robot description, for example:

cobot_description/urdf/cobot_camera.urdf.xacro

This robot description already contains the robot, the gripper, and possibly the simulated camera.

The Setup Assistant will guide us through the configuration.

The main steps are:

Load the robot model.

Generate the self-collision matrix.

Define the planning group.

Define optional robot poses.

Configure end effectors if needed.

Generate the MoveIt package.

The most important step is the planning group.

For a 6-axis robot, we usually create a planning group called something like:

arm

or:

manipulator

This group contains the joints used for motion planning.

For example:

base_link__link1
link1__link2
link2__link3
link3__link4
link4__link5
link5__link6

MoveIt needs this group because it must know which joints it is allowed to move when solving inverse kinematics and planning trajectories.

Then we select a kinematics solver, for example:

kdl_kinematics_plugin/KDLKinematicsPlugin

For this course, KDL is enough to understand the pipeline.

Later, for more advanced robots or better performance, you may use different IK solvers.

After completing the assistant, we generate a new package, for example:

custom_robot_moveit_config

This package becomes the MoveIt configuration package for our robot.

Code Explanation: What the MoveIt Config Package Contains

After generation, the package contains files such as:

custom_robot_moveit_config/
├── config/
│   ├── custom_robot.urdf.xacro
│   ├── custom_robot.srdf
│   ├── kinematics.yaml
│   ├── joint_limits.yaml
│   ├── moveit_controllers.yaml
│   ├── ros2_controllers.yaml
│   ├── custom_robot.ros2_control.xacro
│   └── moveit.rviz
├── launch/
│   ├── demo.launch.py
│   ├── move_group.launch.py
│   ├── moveit_rviz.launch.py
│   └── ...

The most important files are:

custom_robot.urdf.xacro

This is the robot description used by MoveIt.

Very often, this file includes the original robot Xacro and also includes a generated ROS 2 Control file.

For example:

<xacro:include filename="$(find cobot_description)/urdf/cobot_camera.urdf.xacro"/>
<xacro:include filename="custom_robot.ros2_control.xacro"/>

custom_robot.srdf

This file contains semantic information for MoveIt.

It defines:

Planning groups.

Disabled collision pairs.

End effectors.

Named robot states.

This file does not describe geometry. The geometry comes from the URDF.

The SRDF tells MoveIt how to interpret the robot for planning.

kinematics.yaml

This file tells MoveIt which inverse kinematics solver to use for each planning group.

Example:

arm:
  kinematics_solver: kdl_kinematics_plugin/KDLKinematicsPlugin
  kinematics_solver_search_resolution: 0.005
  kinematics_solver_timeout: 0.005

moveit_controllers.yaml

This file tells MoveIt where to send the trajectories.

MoveIt does not directly control the hardware.

MoveIt sends the planned trajectory to a controller action server.

For example, MoveIt may send a FollowJointTrajectory goal to:

/arm_trajectory_controller/follow_joint_trajectory

ros2_controllers.yaml

This file is for ROS 2 Control.

It defines the real controllers loaded by the controller_manager.

For example:

controller_manager:
  ros__parameters:
    update_rate: 100
    arm_trajectory_controller:
      type: joint_trajectory_controller/JointTrajectoryController
    gripper_position_controller:
      type: position_controllers/GripperActionController
    joint_state_broadcaster:
      type: joint_state_broadcaster/JointStateBroadcaster

Then each controller has its own parameters:

arm_trajectory_controller:
  ros__parameters:
    joints:
      - base_link__link1
      - link1__link2
      - link2__link3
      - link3__link4
      - link4__link5
      - link5__link6
    command_interfaces:
      - position
    state_interfaces:
      - position
      - velocity

This controller receives a trajectory and sends position commands to the robot joints.

Critical Concept: ROS 2 Controllers vs MoveIt Controllers

This is one of the most important parts of the lesson.

There are two different controller concepts.

First, ROS 2 Controllers.

These are the real controllers loaded by ROS 2 Control.

They are managed by:

/controller_manager

Examples:

joint_state_broadcaster
arm_trajectory_controller
gripper_position_controller

These controllers talk to the hardware interface, or to Gazebo, or to a mock system.

Second, MoveIt Controllers.

These are not real hardware controllers.

They are a configuration layer that tells MoveIt how to send the planned trajectory.

MoveIt needs to know:

Which action server should receive the trajectory?

Which joints belong to that controller?

Which controller type should be used?

So the architecture is:

MoveIt
→ moveit_controllers.yaml
→ /arm_trajectory_controller/follow_joint_trajectory
→ ROS 2 Control controller
→ hardware interface / Gazebo / mock system

A common mistake is to confuse these two files.

moveit_controllers.yaml is for MoveIt.

ros2_controllers.yaml is for ROS 2 Control.

MoveIt plans and sends the trajectory.

ROS 2 Control executes the trajectory.

Critical Trap: The Automatically Generated ros2_control.xacro

When MoveIt Setup Assistant generates the MoveIt config package, it often creates a file like:

custom_robot.ros2_control.xacro

Inside this file, you may find something like:

<hardware>
  <plugin>mock_components/GenericSystem</plugin>
</hardware>

This is the mock hardware interface.

The mock system is useful when you want to test MoveIt in RViz without Gazebo and without a real robot.

It pretends to be the robot hardware.

When MoveIt sends a trajectory, the mock system accepts the command and updates the joint states as if the robot moved.

That is very useful for testing.

But there is a problem.

In our previous Gazebo simulation, our original robot Xacro may already contain a <ros2_control> block using:

<plugin>gazebo_ros2_control/GazeboSystem</plugin>

This is the hardware interface used by Gazebo.

So if the MoveIt-generated package includes a second <ros2_control> block with mock hardware, we can create a conflict.

The robot may end up with two different ROS 2 Control definitions.

One says:

Use GazeboSystem

The other says:

Use mock_components/GenericSystem

That is wrong.

You must have one coherent hardware interface for the mode you are running.

The rule is simple:

For RViz-only MoveIt testing, use the mock hardware.

For Gazebo simulation, use gazebo_ros2_control/GazeboSystem.

For a real robot, use the real hardware interface or vendor driver.

So, if you are using MoveIt only in RViz, you can leave the generated custom_robot.ros2_control.xacro as it is.

But if you want to connect MoveIt to Gazebo, you usually need to comment or remove the automatically generated mock ROS 2 Control include and make sure the robot description uses the Gazebo-compatible <ros2_control> block.

This is the trap.

MoveIt Setup Assistant helps you generate the package, but you must understand what it generated.

Do not blindly trust the generated controller configuration.

You need to know which controller stack you are actually loading.

The Custom Launch File

Now we create a launch file that explicitly starts the nodes we need.

This is important because the launch file defines the architecture of the application.

A simplified version looks like this:

Let’s understand what this launch file does.

This block:

moveit_config = (
    MoveItConfigsBuilder("custom_robot", package_name="custom_robot_moveit_config")
    .robot_description(file_path="config/custom_robot.urdf.xacro")
    .robot_description_semantic(file_path="config/custom_robot.srdf")
    .trajectory_execution(file_path="config/moveit_controllers.yaml")
    .planning_pipelines(
        pipelines=["ompl", "chomp", "pilz_industrial_motion_planner"]
    )
    .to_moveit_configs())

loads the complete MoveIt configuration.

It loads the URDF, the SRDF, the kinematics, the planning pipelines, and the MoveIt controller configuration.

This is the central object used by the launch file.

Then we start:

ros2_control_node

This starts the ROS 2 Control controller manager.

It receives:

moveit_config.robot_description

and:

ros2_controllers_path

So the controller manager knows:

Which joints exist.

Which hardware interfaces exist.

Which controllers must be loaded.

Then we spawn the controllers:

joint_state_broadcaster_spawner
arm_controller_spawner
gripper_controller_spawner

The joint_state_broadcaster publishes the joint states.

The arm_trajectory_controller executes arm trajectories.

The gripper_position_controller controls the gripper.

Then we start:

move_group_node

This is the main MoveIt node.

It exposes planning services and actions.

RViz talks to move_group.

Your C++ or Python application can also talk to move_group.

Finally, we start RViz and the robot state publisher.

This launch file is very important because we will reuse this structure for other robots.

For a different robot, the logic remains the same.

You change:

The MoveIt config package.

The robot description.

The controller YAML.

The hardware interface.

But the architecture remains the same.

Demo

First, install MoveIt2 following the official installation procedure.

Then test the Panda demo to make sure MoveIt works.

After that, launch the MoveIt Setup Assistant:

ros2 launch moveit_setup_assistant setup_assistant.launch.py

Load the robot Xacro.

Generate the self-collision matrix.

Create a planning group called, for example:

arm

Add the six robot joints to this group.

Select a kinematics solver.

Generate the package:

custom_robot_moveit_config

Then build the workspace:

cd ~/your_ros2_wscolcon buildsource install/setup.bash

Now launch your custom MoveIt configuration:

ros2 launch custom_robot_moveit_config demo_with_controllers.launch.py

In RViz, open the MotionPlanning panel.

Select the planning group.

Move the interactive marker of the end effector.

Use the arrows to change position.

Use the rings to change orientation.

Click Plan.

If the plan is valid, click Execute.

At this stage, remember what mode you are using.

If you are using mock hardware, the robot will move only in RViz.

If you are using Gazebo hardware, MoveIt should send the trajectory to the ROS 2 controller connected to Gazebo.

If you are using a real robot, the controller must be connected to the real hardware interface or vendor bridge.

The key is that MoveIt does not care about the final hardware.

MoveIt sends a trajectory to a controller interface.

The controller layer decides whether the command goes to mock hardware, Gazebo, or the real robot.

Key Takeaways

MoveIt2 is the motion planning layer. It computes valid robot motions, solves inverse kinematics, checks collisions, and sends trajectories to controllers.

ROS 2 Control is the execution layer. It receives commands and applies them to a hardware interface, Gazebo simulation, or mock system.

The MoveIt Setup Assistant generates a useful starting configuration, but you must inspect the generated files.

The most important trap is the generated custom_robot.ros2_control.xacro.

If it loads mock_components/GenericSystem, it is good for RViz-only testing.

If you want to use Gazebo, you need gazebo_ros2_control/GazeboSystem.

If you want a real robot, you need the real hardware interface or vendor bridge.

Do not load multiple conflicting <ros2_control> blocks for the same robot.

The difference between moveit_controllers.yaml and ros2_controllers.yaml is critical.

moveit_controllers.yaml tells MoveIt where to send trajectories.

ros2_controllers.yaml defines the actual controllers loaded by the ROS 2 Control controller manager.

The custom launch file is the bridge between everything: robot description, MoveIt, ROS 2 Control, controllers, robot state publisher, and RViz.

Once you understand this structure, you can adapt the same workflow to almost any robot.

Complete and Continue  
Discussion

0 comments