Integrating a Gripper (SoftGripper) into MoveIt

1. Objective — What You Are Building and Why

In this lesson you will learn how to integrate a pneumatic SoftGripper with suction cup into MoveIt.

This is a very important lesson, because here you start to see the real difference between:

  • making something work with a quick service call
  • and integrating it properly into a full robotics pipeline

In the previous lesson, you already created a Fairino bridge for the gripper, triggered through SetDO. That means you already built a ROS2 service layer that lets you control the digital outputs of the Fairino controller from ROS2.

That was a great first step.

But now we want something more advanced:

we want MoveIt to control the gripper.

And here the problem starts.

MoveIt does not want a simple ROS2 service.
MoveIt expects a GripperCommand action.

Also, MoveIt likes working in closed loop.
This means it wants not only a command, but also some kind of feedback telling it what happened.

But our SoftGripper is not a classical electric gripper.

It is a discrete pneumatic device.

It is basically:

  • open
  • close
  • idle

So there is no real finger position we can read continuously.

That means we need to create a small adapter layer that makes MoveIt happy.

We will do three important things in this lesson:

  1. Build the gripper bridge node that controls the pneumatic gripper and publishes a fake joint state
  2. Create a custom MoveIt configuration package that includes a fake gripper joint
  3. Build an action adapter layer so MoveIt can control the gripper as if it were a normal gripper

At the end of the lesson, you will have a full pipeline where:

  • MoveIt controls the 6 robot joints
  • MoveIt also controls 1 fake gripper joint
  • the fake gripper joint is translated into real DO commands on the Fairino controller

So in practice, your system will handle 7 joints total:

  • 6 real robot joints
  • 1 fake gripper joint

This is exactly the kind of integration work that teaches you how real robotic applications are built.

2. GitHub Package Reference

You can follow this lesson in three ways, as usual.

Option 1 — Clone the repositories and study the architecture

Gripper bridge package:

https://github.com/LearnRoboticsWROS/fairino_bridge_gripper_master_class

MoveIt config package:

https://github.com/LearnRoboticsWROS/fr3wml_camera_gripper_moveit_config_master_class

Option 2 — Follow the video and build everything with me

This is the best option if you want to understand:

  • where the files go
  • what needs to be modified
  • why these modifications are necessary

Option 3 — Try to build the pipeline yourself first

You can try to answer this question on your own:

How can I make MoveIt control a pneumatic gripper that only supports ON/OFF commands?

Then compare your solution with mine.

That is one of the best exercises in this whole module.

3. Code and Architecture Explanation — What, How, and Why

This lesson is long, so let’s keep the idea simple.

We are building a pipeline with these pieces:

  1. A gripper bridge node
    Turns Fairino DO commands into ROS2 services and publishes a fake gripper joint state
  2. A custom MoveIt package
    Adds a fake gripper joint to the robot model
  3. A gripper action server
    Converts MoveIt GripperCommand actions into the gripper services
  4. A joint state merger
    Combines robot joint states and fake gripper joint state into a single /joint_states

This is the trick that lets MoveIt think the gripper is a normal controllable joint.

Part A — The Gripper Bridge Node

The first node is FairinoGripperNode.

Its job is simple:

  • call the Fairino remote command service
  • send SetDO(...) commands
  • expose simple ROS2 services like:
    • /gripper/open
    • /gripper/close
    • /gripper/idle
    • /gripper/suction_on
    • /gripper/suction_off
  • publish a fake joint state for the gripper

This is the key idea:

the real hardware is discrete, but we expose it to ROS2 in a clean way.

Why do we need this node?

Because Fairino understands commands like:

  • SetDO(0,1)
  • SetDO(1,0)

But the rest of the ROS2 system should not need to know these low-level vendor details.

So this node acts like a small translator.

Important design choice: safety interlock

Inside the node, open and close DOs must never be ON at the same time.

That is why the node uses this logic:

  • turn everything OFF first
  • wait a small amount of time
  • then enable only the desired DO

This is called a break-before-make logic.

That is not just software elegance.
That is hardware safety.

The fake gripper joint state

This node also publishes:

  • /gripper/joint_states

with one fake joint:

  • softgripper_joint

And we map the states like this:

  • open → 0.0
  • close → 1.0
  • idle → 0.5

This is a very important trick.

The pneumatic gripper has no real continuous position feedback.
But MoveIt wants a command plus feedback loop.

So we create a fake position state.

We are basically saying:

“MoveIt, here is the feedback you want.”

This is not a physical joint.
It is a software representation of the gripper state.

That is the idea you must really understand here.

Part B — Creating the MoveIt Package

Now we move to the MoveIt side.

We create the MoveIt package using the MoveIt Setup Assistant.

But here comes a very important point:

when you create the MoveIt package, you must do it as if the gripper does not exist.

Why?

Because the gripper in our URDF was imported as a single monolithic block.

It has no real moving joints.

So the Setup Assistant cannot generate the kind of gripper integration we need.

That part must be done manually.

Also, we are not using the standard MoveIt ros2_control flow for execution, because we already have our own custom robot bridge and our own custom gripper bridge.

So the Setup Assistant helps us generate the base package, but after that we must put our hands into the files and modify them ourselves.

This is exactly the kind of thing a robotics engineer must know how to do.

Part C — Adding a Fake Gripper Joint in the URDF

In the .urdf.xacro file we add:

<link name="softgripper_link">

</link>

<joint name="softgripper_joint" type="prismatic">

  <parent link="wrist3_link"/>

  <child link="softgripper_link"/>

  <origin xyz="0 0 0" rpy="0 0 0"/>

  <axis xyz="0 0 1"/>

  <limit lower="0.0" upper="1.0" effort="1.0" velocity="1.0"/>

</joint>

Why do we do this?

Because MoveIt wants a joint that can be commanded.

So we create a fake prismatic joint called:

  • softgripper_joint

This joint is not there to move geometry in a realistic way.

It is there to give MoveIt:

  • something to command
  • something to track
  • something to close the loop with

Again, this is an adapter trick.

We are not simulating the real deformation of the soft gripper.
We are simply giving MoveIt the interface it expects.

Part D — Modifying the SRDF

Then we modify the .srdf file like this:

<group name="SoftGripper">

  <joint name="softgripper_joint"/>

</group>

<group_state name="open" group="SoftGripper">

  <joint name="softgripper_joint" value="0.0"/>

</group_state>

<group_state name="close" group="SoftGripper">

  <joint name="softgripper_joint" value="1.0"/>

</group_state>

<group_state name="idle" group="SoftGripper">

  <joint name="softgripper_joint" value="0.5"/>

</group_state>

Why do we need these group states?

Because MoveIt needs named target states it can use for planning and execution.

We are defining three logical positions:

  • open
  • close
  • idle

These are not real finger positions from an encoder.
They are software states representing what we want the gripper to do.

This is how we map a discrete pneumatic device into the MoveIt world.

Part E — Modifying ros2_controllers.yaml

We simplify the controller setup.

We keep only:

  • joint_state_broadcaster

and we remove the standard trajectory controller generated by ros2_control.

Why?

Because we are not using the default ros2_control execution for the robot.
We already have our custom bridges.

So here the important part is:

we only need the system to publish joint states properly.

That includes:

  • the 6 robot joints
  • the 1 fake gripper joint

Part F — Modifying moveit_controllers.yaml

Then we define two MoveIt controllers:

  • moveit_joint_controller
  • softgripper_controller

The important part is this:

softgripper_controller:

 type: GripperCommand

 action_ns: gripper_command

 default: false

 joints:

  - softgripper_joint

This is where we tell MoveIt:

for this joint, use a GripperCommand action interface.

That is exactly what we need.

Because now MoveIt can send gripper commands in the format it expects.

Then we will adapt those commands and convert them into our ROS services.

Part G — Custom move_group.yaml

We create a custom move_group.yaml that points MoveIt to our custom controller configuration.

This becomes important in the custom launch file, because we want MoveIt to use our controller structure, not the default one.

Part H — Custom demo.launch.py

We then create a custom MoveIt launch file.

The reason is simple:

we are using custom controllers and custom bridges

So the standard generated launch file is not enough.

In this launch file we:

  • load the robot description
  • start robot_state_publisher
  • start move_group
  • open RViz
  • spawn joint_state_broadcaster
  • spawn moveit_joint_controller

But we do not spawn the gripper action server here.

Why not?

Because the gripper command adapter will live inside the fairino gripper package.

That package is where the actual conversion between services and actions is handled.

So the MoveIt package stays focused on the MoveIt configuration, while the Fairino gripper package handles the real gripper execution logic.

That is a good architectural separation.

Part I — Creating the Gripper Action Server

Now we build the adapter layer.

This is the node gripper_action_server.py.

Its job is:

  • expose a GripperCommand action server
  • receive requests from MoveIt
  • translate them into service calls:
    • /gripper/open
    • /gripper/close
    • /gripper/idle

This is the heart of the integration.

Why do we need it?

Because MoveIt speaks in actions for grippers.
But our gripper bridge exposes services.

So this node is an adapter between actions and services.

That is the big idea.

How the action server works

The action name is:

  • /softgripper_controller/gripper_command

That name must match what MoveIt expects.

Then inside the execute callback, we read:

  • goal_handle.request.command.position

And based on the requested position, we decide:

  • near 0.0 → open
  • near 1.0 → close
  • near 0.5 → idle

Then the action callback simply triggers the corresponding service.

So the flow is:

MoveIt action → action callback → ROS service → Fairino SetDO → real gripper

This is a perfect example of an adapter layer.

Why this is so important

This node is not doing anything magical.

It is simply converting:

  • one ROS interface type
    into
  • another ROS interface type

But this is exactly what real robotics integration often looks like.

You take what one subsystem wants.
You convert it into what another subsystem understands.

That is robotics engineering.

Part J — Merging Robot and Gripper Joint States

Now we solve one last important problem.

The robot publishes its own joint states.

The gripper bridge publishes a fake gripper joint state.

But MoveIt and robot_state_publisher want a single /joint_states.

So we create a joint_state_merger node.

Its job is:

  • subscribe to /joint_states_robot
  • subscribe to /gripper/joint_states
  • merge both into one /joint_states

This is a very smart trick.

We remap the original robot joint states to a temporary topic:

  • /joint_states_robot

Then we keep the gripper joint state separate:

  • /gripper/joint_states

Then we merge them into one final topic:

  • /joint_states

So in the end, MoveIt sees a single joint state stream containing:

  • joint1
  • joint2
  • joint3
  • joint4
  • joint5
  • joint6
  • softgripper_joint

That is the full 7-joint system.

Why this merger is necessary

Because otherwise:

  • the robot and gripper publish separately
  • MoveIt does not receive one coherent state
  • the broadcaster and state publisher may not behave properly

The merger solves this cleanly.

Again, this is another very practical integration trick.

Part K — Launching the Full Bridge

Inside the bridge launch file we start:

  • joint_state_bridge
  • follow_joint_trajectory_server2
  • fairino_gripper_control
  • gripper_action_server
  • joint_state_merger

This means the bridge package now exposes everything MoveIt needs:

  • FollowJointTrajectory for the robot
  • GripperCommand for the gripper
  • merged joint states for the full system

This is your full hardware-facing integration layer.

Part L — The Final Bringup

At the end, inside your custom package fr3wml_camera_gripper, you create the bringup launch file.

This is the file that starts the full application.

So now your architecture is complete.


4. Demo — Testing the Full Pipeline

Now we test the whole pipeline.

After colcon build, open three terminals.

Terminal 1 — Driver layer

ros2 run fairino_hardware ros2_cmd_server

This starts the hardware driver layer.

Terminal 2 — Robot and gripper bridge

ros2 launch fairino_bridge bridge_fr3wml_gripper.launch.py

This starts:

  • robot trajectory bridge
  • gripper control node
  • gripper action server
  • joint state merger

Terminal 3 — Full bringup with MoveIt

ros2 launch fr3wml_camera_gripper bringup_fr3wml_camera_gripper.launch.py

Now RViz2 opens.

You will see:

  • the robot
  • the gripper
  • the planning groups
  • and now also the SoftGripper planning group

You can control the SoftGripper directly from the MoveIt Motion Planning plugin by sending goal states like:

  • open
  • close
  • idle

This is the point where the student should really see the simulation-to-reality gap.

Because something interesting happens:

MoveIt controls the gripper correctly, but in RViz the gripper does not visibly open and close.

Why?

Because the gripper was modeled as a monolithic rigid block.

So geometrically it does not animate like a real fingered gripper.

And that is completely fine.

We are not trying to simulate soft pneumatic deformation.

We are simply giving MoveIt the interfaces it needs so that the real application works.

That is the engineering trade-off.

You built a bridge and an adapter that let MoveIt talk to a real pneumatic gripper, even though the internal physics are not simulated.

That is a very valuable skill.

5. Key Takeaways

In this lesson you built a complete integration path between a pneumatic SoftGripper and MoveIt.

You learned that:

  • a simple ROS2 service is not enough for MoveIt gripper control
  • MoveIt expects a GripperCommand action
  • MoveIt also wants some form of feedback for closed-loop execution
  • a pneumatic gripper has no true continuous position feedback
  • so you can create a fake joint and a fake joint state
  • then expose an action server adapter
  • and merge all joint states into one final /joint_states

This lesson is long and a bit complex, but the main idea is actually simple:

you adapted a real pneumatic tool to the interface MoveIt expects.

That is exactly the kind of work you will often do in real robotics projects.

You should also understand something deeper now:

  • the MoveIt Setup Assistant gives you a starting point
  • but real applications always require manual modifications
  • and you must know where, how, and why to put your hands into the files

That is why following this lesson step by step in the video is so useful.

Do not just copy the files.

Try to understand:

  • why the fake joint was created
  • why the action server was needed
  • why the joint state merger was necessary
  • why the launch files were split this way

If you understand these choices, you are not just repeating a tutorial anymore.

You are starting to think like someone who can build a real robotics pipeline.

Complete and Continue  
Discussion

0 comments