ROS packages for Robotiq grippers and sensors.
| Package | Description | ROS Version |
|---|---|---|
| robotiq_tsf | TSF-85 tactile sensor driver | ROS 2 Humble / Jazzy / Lyrical (main) / ROS 1 Noetic (noetic) |
| grippers | ROS 2 ros2_control driver for Robotiq 2F adaptive grippers (2F-85, 2F-140), on the Robotiq C++ SDK |
ROS 2 Humble / Jazzy / Lyrical |
main supports the three live LTS distros from one branch — build it on whichever
one your robot already runs.
| Distro | Ubuntu | Gripper action | gripper_cmd action type |
|---|---|---|---|
| Humble | 22.04 Jammy | position_controllers/GripperActionController |
control_msgs/action/GripperCommand |
| Jazzy | 24.04 Noble | parallel_gripper_action_controller/GripperActionController |
control_msgs/action/ParallelGripperCommand |
| Lyrical | 26.04 Resolute | parallel_gripper_action_controller/GripperActionController |
control_msgs/action/ParallelGripperCommand |
Rolling builds and passes the full suite as well, and CI watches it, but it is not a supported target: it is a moving development branch, so a break there is a heads-up rather than a release blocker. Its job does not gate merges. Kilted and other non-LTS releases are not built.
The action type differs because parallel_gripper_controller does not exist on
Humble — ROS itself only gained it in Jazzy. robotiq_control.launch.py picks the
matching controller config automatically from $ROS_DISTRO, and everything else
(package names, launch files, controller names, topics, the
/robotiq_gripper_controller/gripper_cmd namespace, the xacro macro arguments)
is identical across all three. On Humble that makes this repo a drop-in
replacement for PickNik's humble branch — see
Migrating from PickNik's ros2_robotiq_gripper.
The other difference is a gripper that is missing at bringup. On Jazzy and Lyrical
ros2_control_node stays up with the hardware component unconfigured, the
spawners give up after 15 s at most, and the launch prints the two commands that
recover without a relaunch once the gripper is plugged in. Humble's
controller_manager cannot keep the node alive in that state, so there the launch
ends and a relaunch is the way back. shutdown_on_failure:=false keeps the launch
running in either case, for launch files that include this one next to nodes that
should outlive the gripper.
The repository is versioned as a whole: every package carries the same version
and each release is one tag, v<version>, covering both the gripper and TSF
stacks. A release touching only one stack still moves the other's version.
VERSION at the repo root is the authoritative copy, on its first
line. package.xml must hold a literal version string — ament, rosdep and bloom
parse it statically — so the value is necessarily duplicated there;
dev/version.py propagates it, and a pre-commit hook fails on drift:
dev/version.py # show the version, report any drift
dev/version.py --set 1.2.0 # bump VERSION and every package.xmlVERSION also lists the package.xml files holding a copy, as a reminder of
what a bump touches. That list is generated by --set and checked by the hook,
so adding or removing a package fails until it is regenerated.
Tags predating this scheme were per-stack: V1.0.0 released the TSF packages,
and 0.0.1 is PickNik's original gripper release.
Bug reports, fixes and features are welcome. Robotiq maintains these packages; contributions come in as pull requests from a fork. See CONTRIBUTING.md for the development setup, the formatting and test requirements, and the distro and PickNik compatibility constraints that shape review.
| Repository | Description | ROS Version |
|---|---|---|
| robotiq | Original ROS 1 Industrial Robotiq packages (archived, 3D CAD models are outdated) | ROS 1 Indigo / Kinetic / Melodic |
Also check out these community-maintained ROS drivers for Robotiq products.
| Repository | Description | ROS Version |
|---|---|---|
| ros2_epick_gripper | ROS 2 driver for the EPick vacuum gripper | ROS 2 Humble |
| rq_fts_ros2_driver | ROS 2 driver for the Robotiq force-torque sensor | ROS 2 Humble |
| ros2_RobotiqGripper_UR | ROS 2 driver for Robotiq grippers on UR robots | ROS 2 Humble |
Launch the sensor node (SDK-backed — parses the sensor via the
extern/tactile_sensors submodule, so clone with --recurse-submodules):
ros2 run robotiq_tsf poll_data_sdk_nodeThe device is autodetected (udev symlink, then USB descriptor); override with
--ros-args -p device:=/dev/ttyACM0.
The node publishes on the following topics:
| Topic | Message Type | Description |
|---|---|---|
TactileSensor/StaticData |
robotiq_tsf/StaticData |
Taxel pressure data — two fingers, each with 28 uint16 values |
TactileSensor/Dynamic |
robotiq_tsf/Dynamic |
Dynamic force data — two fingers, each with one int16 value |
TactileSensor/Accelerometer |
robotiq_tsf/Accelerometer |
Raw accelerometer — two readings of [x, y, z] int16 |
TactileSensor/Gyroscope |
robotiq_tsf/Gyroscope |
Raw gyroscope — two readings of [x, y, z] int16 |
TactileSensor/EulerAngle |
robotiq_tsf/EulerAngle |
Fused orientation — two readings of [roll, pitch, yaw] float32 |
TactileSensor/Quaternion |
robotiq_tsf/Quaternion |
Fused orientation — two readings of [w, x, y, z] float64 |
TactileSensor/Timestamp |
robotiq_tsf/Timestamp |
Per-finger firmware timestamp — two uint16 values |
Note:
EulerAngleandQuaternionare only published after the IMU bias calibration period completes at startup.
tactile_viz_node renders TactileSensor/StaticData as per-taxel 3D markers and per-finger 2D heatmap images, with the pad TFs to place them. Two launches:
ros2 launch robotiq_tsf tactile_viz.launch.py # driver + viz + RViz, standalone
ros2 launch robotiq_tsf gripper_tactile_viz.launch.py \
com_port:=/dev/ttyUSB0 # single window: 2F-85 gripper + pads on its fingertipsCommon args (see --show-args for the full list):
| Arg | Default | Description |
|---|---|---|
poller |
poll_data_sdk_node |
Driver executable publishing StaticData — override to use an alternative poller |
rviz / rviz_config |
true / packaged config |
Toggle RViz / point it at your own config (tactile_viz.launch.py) |
use_fake_hardware |
false |
Gripper ros2_control mock (gripper_tactile_viz.launch.py) |
tactile_delay |
8.0 |
Seconds to delay the viz start so the baseline is captured after gripper activation (gripper_tactile_viz.launch.py) |
The node publishes visualization_msgs/MarkerArray on /tactile/markers and sensor_msgs/Image heatmaps on /tactile_viz/finger0_heatmap / /tactile_viz/finger1_heatmap. On startup it averages the first baseline_frames messages into a per-taxel baseline and subtracts it (re-zero anytime: ros2 topic pub --once /tactile_viz/zero std_msgs/msg/Empty); readings below noise_floor render quiet. Pad geometry, frames, color scale, and heatmap options are parameters of tactile_viz_node.
In the combined launch the pad frames are TF-mounted on the gripper fingertip links, so the heatmaps follow the fingers as the gripper opens and closes.
ROS 2 ros2_control driver for Robotiq 2F adaptive grippers, under grippers/.
Descriptions ship for the 2F-85 and the 2F-140; robotiq_control.launch.py defaults to the
2F-85, so pass gripper_model:=2f_140 for a 2F-140. That argument selects the gripper macro in the
xacro and the joint the controller drives (robotiq_85_left_knuckle_joint or finger_joint); a
custom description keeps model:=<path> and sets gripper_joint explicitly. The controller configs
in config/ write that joint as $(var gripper_joint), which only the launch resolves — loaded
directly into your own ros2_control_node they are not valid as they stand. Hardware validation to
date is on a 2F-85 — the 2F-140 description ships untested against hardware.
The driver itself is model-agnostic: it needs a serial link and the gripper_closed_position of
whatever is attached. A Hand-E therefore works once you supply a URDF for it, but no Hand-E
description ships here yet.
The driver runs on the Robotiq C++ grippers SDK, which arrives as
the extern/grippers submodule — so clone with --recurse-submodules. The SDK owns the serial link
and the Modbus exchange (one FC 0x17 transaction per cycle, on its own thread); robotiq_driver is
the ros2_control layer above it, and its read() / write() copy the SDK's process image rather
than touching the bus.
Robotiq maintains this driver. It started from PickNik Robotics'
ros2_robotiq_gripper (BSD-3-Clause),
imported with git subtree at upstream commit 3b6cf8f so its history came along, and has been
developed here since: the in-tree Modbus implementation and the vcs imported serial package it
depended on were replaced by the SDK, three distros build from one branch, and bugs still open
upstream (#114,
#88) are fixed here. Upstream
copyright and <author> tags are retained; issues and support go to
robotiq/ros/issues, not to PickNik.
This repository is the maintained continuation of the PickNik package. Package names (robotiq_driver, robotiq_controllers, robotiq_description, robotiq_hardware_tests), launch files, controller names, and the /robotiq_gripper_controller/gripper_cmd action are all unchanged, so at the workspace level it is a drop-in replacement.
Docker (recommended):
git clone --recurse-submodules https://github.com/robotiq/ros.git
cd ros && ./docker/run.sh gripperExisting ROS 2 workspace (Humble, Jazzy or Lyrical — same steps on all three):
cd ~/ws/src
rm -rf ros2_robotiq_gripper # remove the PickNik clone to prevent duplicate package name failures
rm -rf serial # no longer used: the gripper SDK talks to the port itself
git clone --recurse-submodules https://github.com/robotiq/ros.git
cd ~/ws
rosdep install --from-paths src --ignore-src -y
rm -rf build install # clear artifacts built from the PickNik sources
colcon buildThere is no vcs import step any more: the driver's transport comes from the
extern/grippers submodule instead of the external serial package, so a
--recurse-submodules clone is the whole dependency story. rosdep resolves
the right gripper action controller for your distro on its own.
This repository also contains the TSF-85 sensor stack. To build only the gripper packages and their dependencies, replace the last step with:
colcon build --packages-up-to robotiq_description robotiq_controllers robotiq_hardware_testsStaying on Humble: nothing to change. This repository builds on Humble and keeps PickNik's Humble controller and action surface, so your existing action clients, launch overrides and xacro arguments work untouched:
PickNik humble |
This repository on Humble | |
|---|---|---|
| ROS distro | Humble | Humble |
| Serial transport | serial package, vcs imported |
the extern/grippers SDK submodule (libserialport) |
robotiq_gripper_controller type |
position_controllers/GripperActionController |
position_controllers/GripperActionController |
gripper_cmd action type |
control_msgs/action/GripperCommand |
control_msgs/action/GripperCommand |
As on PickNik's humble, the Humble controller cannot claim the hardware's
set_gripper_max_effort / set_gripper_max_velocity interfaces — Humble's
gripper_controllers has no parameters for them (PickNik's
use_effort_interface / use_speed_interface entries were silently ignored
there). Per-goal speed and force therefore come from the xacro's
gripper_speed_multiplier / gripper_force_multiplier, exactly as before.
Moving to Jazzy or Lyrical at the same time is the one breaking step, and the
break is upstream ROS's, not this repository's — gripper_controllers is gone in
Kilted+, and parallel_gripper_controller is its replacement from Jazzy on
(upstream PickNik PR #103).
Action clients then switch to the ParallelGripperCommand goal — a
sensor_msgs/JointState naming the knuckle joint:
# Humble (PickNik humble, and this repo on Humble)
ros2 action send_goal /robotiq_gripper_controller/gripper_cmd \
control_msgs/action/GripperCommand \
"{command: {position: 0.4, max_effort: 50.0}}"
# Jazzy / Lyrical
ros2 action send_goal /robotiq_gripper_controller/gripper_cmd \
control_msgs/action/ParallelGripperCommand \
"{command: {name: ['robotiq_85_left_knuckle_joint'], position: [0.4], effort: [40.0]}}"position is still the knuckle angle in radians (≈ 0.0 open → ~0.8 closed on a 2F-85), now as an array on the named joint; max_effort becomes the optional effort / velocity arrays. See Commanding the gripper for details.
use_fake_hardwareandbaudratelaunch args onrobotiq_control.launch.py(hardware-free bringup, previously hardcoded off; a custom baudrate, previously an edit to the xacro)- Actionable error messages when the gripper does not respond (24 V power, RS-485 wiring,
slave_address/baudratehints) - A unified Docker image with device mapping (Docker)
- Active maintenance — PickNik's in-tree README and CI were removed; docs live in this README, and issues go to robotiq/ros/issues
| Package | Description |
|---|---|
robotiq_driver |
ros2_control hardware interface, over the extern/grippers SDK (Modbus RTU on serial) |
robotiq_msgs |
Messages the driver and controllers publish |
robotiq_controllers |
Gripper command / activation controllers, and the status broadcaster |
robotiq_description |
URDF/xacro, meshes, RViz + bringup launch |
robotiq_hardware_tests |
Hardware integration tests |
Bring up a gripper:
ros2 launch robotiq_description robotiq_control.launch.py # real hw, com_port:=/dev/ttyUSB0
ros2 launch robotiq_description robotiq_control.launch.py use_fake_hardware:=true # ros2_control mock
ros2 launch robotiq_description robotiq_control.launch.py launch_rviz:=true # + RViz visualization
ros2 launch robotiq_description robotiq_control.launch.py baudrate:=<rate>
ros2 launch robotiq_description robotiq_control.launch.py sim_topic_based:=true \
sim_joint_commands_topic:=/sim/joint_commands sim_joint_states_topic:=/sim/joint_states # topic_based_ros2_controlThis activates joint_state_broadcaster, robotiq_gripper_controller, robotiq_activation_controller and robotiq_gripper_status_broadcaster.
sim_topic_based swaps the hardware plugin for topic_based_ros2_control/TopicBasedSystem: any
simulator that exchanges sensor_msgs/JointState on two topics (Isaac Sim, or a bridge of your
own), named by sim_joint_commands_topic / sim_joint_states_topic. That plugin exports only the standard joint interfaces, so the launch loads
config/robotiq_controllers.topic_based.yaml for it (per-goal effort/velocity are accepted and ignored;
a stall aborts the goal rather than succeeding, since a simulator that publishes no joint velocities
would otherwise report every failed grasp as success) and skips robotiq_activation_controller,
whose reactivate_gripper GPIO only the driver and the mock declare. robotiq_gripper_status_broadcaster
is still spawned, and publishes nothing, because the plugin reports none of the gripper's own status. Renamed in 1.2.0: PickNik's
sim_isaac, isaac_joint_commands and isaac_joint_states (launch arguments and macro parameters,
shipped in 1.1.0) still work and log a deprecation warning, sim_isaac:=true keeping its old
/isaac_joint_commands / /isaac_joint_states topic defaults; they are removed in the next major
release. To stay on those topic names without the deprecated arguments:
ros2 launch robotiq_description robotiq_control.launch.py sim_topic_based:=true \
sim_joint_commands_topic:=/isaac_joint_commands sim_joint_states_topic:=/isaac_joint_statesTopicBasedSystem matches
joints to the simulator's JointState by name, so the simulator must publish this description's
six joint names for the model you launch, each with your prefix, or nothing moves:
gripper_model |
driven joint | mimic joints |
|---|---|---|
2f_85 |
robotiq_85_left_knuckle_joint |
robotiq_85_right_knuckle_joint, robotiq_85_left_inner_knuckle_joint, robotiq_85_right_inner_knuckle_joint, robotiq_85_left_finger_tip_joint, robotiq_85_right_finger_tip_joint |
2f_140 |
finger_joint |
right_outer_knuckle_joint, left_inner_knuckle_joint, right_inner_knuckle_joint, left_inner_finger_joint, right_inner_finger_joint |
The plugin is not a dependency of this package. On Humble install
ros-humble-topic-based-ros2-control; on Jazzy and Lyrical it has no binary release, so build
topic_based_ros2_control in an
overlay once:
git clone https://github.com/PickNikRobotics/topic_based_ros2_control <ws>/src/topic_based_ros2_control
cd <ws>
colcon build --packages-select topic_based_ros2_control --cmake-args -DBUILD_TESTING=OFF(-DBUILD_TESTING=OFF skips its ros_testing test dependency, which a runtime install does not
have.)
The ros2_control xacros also carry a sim_gazebo parameter (gz_ros2_control/GazeboSimSystem)
for cells that embed the gripper macro in their own Gazebo description; this launch does not wire
it, since Gazebo hosts its own controller_manager.
On Jazzy and Lyrical, robotiq_gripper_controller is a parallel_gripper_action_controller/GripperActionController, so its action /robotiq_gripper_controller/gripper_cmd takes a control_msgs/action/ParallelGripperCommand — a sensor_msgs/JointState goal (not the older GripperCommand). On Humble it is position_controllers/GripperActionController taking control_msgs/action/GripperCommand; see Supported ROS 2 distros.
The bringup above holds its terminal, so open a second terminal, exec into the running container, then send a goal:
# host: open a second shell into the running container (per-distro container name)
docker exec -it robotiq_ros2_jazzy bash
# inside the container — Jazzy / Lyrical:
ros2 action send_goal /robotiq_gripper_controller/gripper_cmd \
control_msgs/action/ParallelGripperCommand \
"{command: {name: ['robotiq_85_left_knuckle_joint'], position: [0.4], effort: [40.0]}}"
# inside the container — Humble:
ros2 action send_goal /robotiq_gripper_controller/gripper_cmd \
control_msgs/action/GripperCommand \
"{command: {position: 0.4, max_effort: 50.0}}"position is the joint angle in radians (≈ 0.0 open → ~0.8 closed on a 2F-85); on Jazzy/Lyrical effort and velocity are optional max limits, mapped to the controller's set_gripper_max_effort / set_gripper_max_velocity interfaces.
effort sets the gripper's grip threshold, as a fraction of gripper_max_force written to rFR. It sets the maximum current at the motor, and does not directly control the maximum force the gripper applies: that is also largely influenced by the closing speed. Nothing reads a force out of the gripper, and motor_current is not convertible to one.
robotiq_gripper_status_broadcaster publishes the gripper's own status on /robotiq_gripper_status_broadcaster/status, as a robotiq_msgs/GripperStatus, at the controller rate:
ros2 topic echo /robotiq_gripper_status_broadcaster/statusobject_detection is the gripper's own answer to "am I holding something", and the only authoritative one. Prefer it to the gripper_cmd action's stalled flag, which is a stall heuristic, and to comparing the commanded opening against the achieved one. The message names all four of its states, and every fault code, as constants.
Where the gripper reports no fault this cycle, gripper_fault and gripper_fault_severity read GRIPPER_FAULT_UNKNOWN, not NONE. That is what a client sees under use_fake_hardware:=true and sim_topic_based:=true, whose plugins export no fault interface. Treat it as "cannot say" rather than "healthy"; motor_current says the same thing with NaN.
The broadcaster is spawned in every hardware mode. Only the driver reports these values, so under use_fake_hardware:=true and sim_topic_based:=true it activates, says so once, and publishes nothing. use_dummy keeps the driver loaded and does publish. While the driver reports a link fault, the topic repeats the last reading it got from the gripper.
The same values reach a controller as state interfaces. robotiq_driver exports six on the gripper joint, all read out of the status block the SDK exchanges every cycle. Read them from /dynamic_joint_states, or with ros2 control list_hardware_interfaces.
| Interface | Unit | Description |
|---|---|---|
position |
rad | Angle of the driven knuckle joint, from gPO — not the opening in millimetres. 0.0 open → ~0.8 closed on a 2F-85, over a linear approximation of a travel the gripper reports in 227 counts |
velocity |
rad/s | Always 0.0. The status block carries no velocity, and the driver does not differentiate the position |
motor_current |
A | gCU, the motor current the manual gives as 10 mA per count, so 0.0 to 2.55 |
object_status |
— | gOBJ verbatim: 0 moving, 1 object held while opening, 2 object held while closing, 3 at the requested position |
gripper_fault |
— | gFLT as the manual numbers it: 0 none, 5 action delayed, 7 activation required, 8 over temperature, 9 no communication, 10 under voltage, 11 automatic release running, 12 internal fault, 13 activation fault, 14 overcurrent, 15 automatic release complete. The register is four bits wide, so 1, 2, 3, 4 and 6 can also arrive; they are undefined faults, not "no fault" |
gripper_fault_severity |
— | How bad the gripper fault is: 0 none, 1 warning, 2 minor, 3 major. A major fault clears only on a reactivation. An undefined code reports 3, so a switch over the codes above cannot silently drop one |
All six read NaN until the component is activated; activation seeds them from the gripper's first settled status read, and read() refreshes them every cycle after that. The sentinel matters most for object_status, where every value in range is meaningful — 0 is "moving", not "no reading" — so NaN is the only way to say the gripper has not answered yet. Test for it before comparing against 0..3.
motor_current is motor current, not grip force, and it is not convertible to one.
The driver reports the fault code and its severity separately because the SDK decodes both. Publishing the raw fault byte instead would put that decoding in every consumer.
The byte also carries kFLT, the controller's own fault, which nothing exposes. Nobody has needed it, and it is a different failure from the gripper's; open an issue if you do. Both names say gripper_ for that reason, so adding the controller's pair later needs no rename.
The codes are numbers on the wire, so the driver also names the fault in its own throttled warning, for example reports gripper fault OverTemperature (code 8). An undefined code reads Unrecognized. A consumer that wants the name in its own output takes it from the SDK's toString.
None of the four beyond position and velocity are ros2_control standard interface names (there are HW_IF_ constants for position, velocity and effort, and nothing for any of these), so consumers spell them out. The descriptions declare them on the real-hardware branch only — mock_components/GenericSystem, Gazebo and the topic-based plugin never write them — so under use_fake_hardware:=true all four are absent rather than wrong.
robotiq_driver reads these from the <hardware> block of the ros2_control description (robotiq_description/urdf/2f_*.ros2_control.xacro):
| Parameter | Default | Description |
|---|---|---|
gripper_closed_position |
required | Joint angle in radians at a fully closed gripper — the scale of the whole position mapping |
COM_port |
/dev/ttyUSB0 |
Serial port |
baudrate |
115200 |
Must match the gripper's persisted setting, which is why it is also a launch argument, baudrate:=<rate>. Rejected outside 1..1000000. Most units stay at 115200; the gripper's own rate is changed in the Robotiq User Interface (Modbus RTU Parameters), not from here, and the gripper must be rebooted afterwards |
timeout |
0.5 |
Per-transaction serial timeout, in seconds |
slave_address |
0x09 |
Modbus slave address; 0x09 as the manual prints it, a bare number as the decimal it looks like |
connection_frequency |
100 |
Rate of the SDK's background exchange cycle, in Hz; 0 free-runs |
activation_timeout |
15 |
Seconds allowed for activation and for fault recovery |
gripper_max_speed / gripper_max_force |
0.150 m/s / 235 N |
Full scale used to turn the speed/effort command interfaces into rSP / rFR register fractions. rFR caps the motor current rather than the applied force — see Commanding the gripper. Command-side only, and nothing scales a state interface by them. Rejected unless finite and above zero |
gripper_speed_multiplier / gripper_force_multiplier |
1.0 |
Initial fractions published on those interfaces |
use_dummy |
false |
Drive a fake gripper instead of hardware. Off for the usual falsey spellings — empty, 0, false, no, off, in any case — on for anything else |
A malformed value is reported and the default stands; only gripper_closed_position fails the transition — missing, malformed, zero, or non-finite. A speed or force command the driver cannot turn into a register leaves that register at its previous value.
use_dummyselects the SDK's fake gripper: no port is opened, activation is instant, and the fingers report wherever they were last commanded. It keeps the real plugin loaded, so the gripper and activation controllers still bind. That is what distinguishes it from theuse_fake_hardware:=truelaunch argument, which swaps the plugin out forros2_control'smock_components/GenericSystem.
Activating the hardware component runs the gripper's reset handshake: it clears any latched fault and runs the calibration sweep. It therefore releases any grip and moves the fingers through their full range — activate with the workspace clear. Deactivating clears the command block including rACT, which resets the gripper and likewise releases any grip. This is what the driver has always done; the SDK exposes a conservative alternative (leave a healthy gripper alone, refuse to reset a faulted one) that is not wired up here yet.
A fault can also be cleared without cycling the lifecycle state, by writing to the reactivate_gripper/reactivate_gripper_cmd command interface (exposed as a GPIO on the description, and driven by robotiq_activation_controller) — same handshake, same consequences. reactivate_gripper_response reads 1.0 once the recovery succeeds; a failure is logged but leaves the interface as it was, as it did before. A deactivation arriving while a recovery is in flight waits for it to finish before resetting.
activation_timeout bounds both. The old driver had no timeout and could block the transition indefinitely.
Pass launch_rviz:=true to open RViz with the gripper model (default config robotiq_description/rviz/view_urdf.rviz); the joints update live from joint_state_broadcaster. Override with rvizconfig:=/path/to/your.rviz. Args combine — e.g. use_fake_hardware:=true launch_rviz:=true to visualize without hardware. Running via docker/run.sh already forwards X11, so the RViz window displays from inside the container (if it can't connect to the display, run xhost +local:root on the host once).
To inspect the model in RViz with no gripper attached (no ros2_control), use the visualization-only launch — robot_state_publisher, RViz, and a joint_state_publisher_gui slider:
ros2 launch robotiq_description view_gripper.launch.pyDrag the robotiq_85_left_knuckle_joint slider; the five finger joints follow it via URDF mimic (≈ 0.0 open → ~0.8 closed).
Goal-based commanding also works without hardware: robotiq_control.launch.py use_fake_hardware:=true launch_rviz:=true brings up all four controllers against mock_components/GenericSystem, and gripper_cmd goals drive the model — the five finger joints follow the knuckle via URDF mimic, exactly as on hardware. Use the slider above when you want to pose the model by hand instead.
Mock and hardware publish the same /joint_states contract on every distro: the knuckle joint alone, with robot_state_publisher deriving the mimicked finger joints from the URDF. Only the Gazebo and topic-based paths (sim_gazebo, sim_topic_based) declare the mimicked joints to ros2_control, since those simulators supply their own joint state.
Unit tests live in each package's test/ (or tests/) directory and run without hardware. Build and run all tests from the repository root:
source /opt/ros/jazzy/setup.bash # or humble / lyrical
colcon build
colcon test
colcon test-result --verboseTo scope to a single package, pass --packages-select <package> to colcon build and colcon test.
Test executables land under build/<package>/, mirroring the package's test-directory layout; run one directly for gtest options such as --gtest_filter:
./build/<package>/test/<test_executable> --gtest_filter='<TestSuite>.*'CI builds the packages and runs their unit tests on pull requests, once per supported distro — Humble, Jazzy and Lyrical (ci-ros-build-test.yml).
The docker/ folder provides scripts to build and run the TSF-85 and 2F grippers inside a container. Clone with submodules to pull in the required utilities:
git clone --recurse-submodules https://github.com/robotiq/ros.gitIf you already cloned without --recurse-submodules:
git submodule update --initTwo SDKs live under extern/ as submodules and are built into the image:
| Submodule | Repository | Used by |
|---|---|---|
extern/tactile_sensors |
Robotiq/tactile_sensors | robotiq_tsf's poll_data_sdk_node |
extern/grippers |
Robotiq/grippers | robotiq_driver |
extern/COLCON_IGNORE keeps colcon from building either SDK's standalone
CMake project as a workspace package; the packages that need them compile them
in-tree.
| Script | Description |
|---|---|
run.sh |
Builds a single ROS 2 image with the whole workspace (sensor + grippers) and launches a shell with that product's devices mapped: ./run.sh [gripper|sensor|both]. |
sensor_install.sh |
Sets up udev rules and permissions for bare-metal (non-Docker) use |
Dockerfile_TSF85_ROS2andbuild_launch_docker_ros2.share kept as deprecation shims pointing toDockerfile/run.sh sensor.
The Dockerfile is a multi-stage build: a builder stage compiles the workspace, and a slim runtime stage (the default) ships only the built workspace plus runtime deps — no compiler, no colcon/rosdep, no test deps.
Build it directly (the context must be the repo root — the Dockerfile COPYs robotiq_tsf/, grippers/ and extern/tactile_sensors/sdk_cpp):
docker build -f docker/Dockerfile -t robotiq_ros2:jazzy .Build options:
--build-arg ROS_DISTRO=…(defaultjazzy) —humble,jazzyandlyricalare all built in CI.--build-arg WITH_GUI=false— headless: drop rviz2/rqt/joint-state-publisher-gui (and their mesa/Qt/VTK), for a much smaller image on robots that don't visualize.--target builder— a dev image with the full toolchain, for building inside the container.
docker build --build-arg WITH_GUI=false -f docker/Dockerfile -t robotiq_ros2:jazzy-headless .
docker build --target builder -f docker/Dockerfile -t robotiq_ros2:dev .Then run it with the sensor/gripper devices mapped via ./docker/run.sh [gripper|sensor|both].
Pick the distro with ROS_DISTRO (default jazzy); each one gets its own image
tag and container name, so switching does not reuse the other's build:
./docker/run.sh gripper # robotiq_ros2:jazzy / robotiq_ros2_jazzy
ROS_DISTRO=humble ./docker/run.sh gripper # robotiq_ros2:humble / robotiq_ros2_humble
ROS_DISTRO=lyrical ./docker/run.sh both # robotiq_ros2:lyrical / robotiq_ros2_lyrical