A ROS 2 bridge for Rokoko Smartgloves solved-hand data. It connects to
rkk-hand-solver's solved-hand output and republishes it as ROS 2 topics,
an optional TF broadcast, optional RViz markers, and a yaw-calibration
service.
- Prerequisites
- Installing ROS 2 β Linux Β· macOS Β· Windows
- Building
- Running
- Running in Docker
- Published interface
- Calibrating
- Visualizing in RViz
- Configuration
- Troubleshooting
- If you see flickering in RViz
- License
Rokoko Device SDK 0.10.0 or newer, from sdk.rokoko.com. Earlier versions do not emit the OpenXR joint group this bridge reads. It ships the driver and the hand solver that the bridge sits behind; see the SDK's own documentation for anything about those. Check your version with:
rokoko doctorSmartglovess, connected over USB or WiFi.
ROS 2 Humble, Jazzy or Lyrical, on Linux, macOS or Windows β see Installing ROS 2.
ROS 2's Python bindings (
rclpyand the message packages) are not on PyPI β they exist only inside a ROS 2 installation and become importable when you source itssetup.bash. Avenvoruvenvironment cannot see them, so run this package against the system interpreter. If you need isolation, use a Docker image with ROS 2 inside it rather than a Python virtual environment.
This bridge supports three ROS 2 distributions. Each one is tied to an Ubuntu release, so pick the one that matches your machine:
| ROS 2 | Ubuntu | Installation |
|---|---|---|
| Humble Hawksbill | 22.04 | docs.ros.org/en/humble |
| Jazzy Jalisco | 24.04 | docs.ros.org/en/jazzy |
| Lyrical Luth | 26.04 | docs.ros.org/en/lyrical |
The commands below use <distro> for humble, jazzy or lyrical. The
installation guides list every supported platform and method β Debian
packages, RPM packages, binary archives, and source builds. Whatever you
choose, you also need colcon to build the workspace, and you must source
the installation in every shell that runs ROS 2 commands.
If your Ubuntu release does not match any of these, use Docker instead.
On Debian and Ubuntu, add the ROS 2 apt repository as the installation guide for your distro describes, then:
sudo apt install ros-<distro>-desktop # includes RViz; several hundred packages
sudo apt install ros-dev-tools # colcon, rosdep, vcstoolOn other distributions, use the RPM packages or the binary archive from the installation guide β everything after this point works the same way.
Source it, and add the line to your ~/.bashrc if you would rather not
repeat it:
source /opt/ros/<distro>/setup.bashVerify with ros2 doctor.
There are no binary packages for macOS, so ROS 2 is built from source. macOS (source) install guide: Humble Β· Jazzy Β· Lyrical
Two things to know before starting:
- It is a source build β expect a long compile, with Homebrew
prerequisites rather than
apt. - It requires disabling System Integrity Protection (SIP), which means rebooting into recovery mode. Read that part of the guide first.
Then source it, from wherever you built it:
source ~/ros2_<distro>/install/setup.bashEverything from Building onward is the same on every platform.
Untested by the maintainers β reports welcome.
ROS 2 ships binary packages for Windows. Windows (binary) install guide: Humble Β· Jazzy Β· Lyrical
Nothing in this bridge is platform-specific β it is plain Python over a TCP connection β so it should run once ROS 2 is installed. The part to check first is whether the Rokoko Device SDK is installed, since the driver and solver have to run somewhere; see the SDK's own documentation.
Three differences from the commands elsewhere in this README:
- Source ROS 2 and the workspace with the batch scripts rather than
source:call C:\dev\ros2_<distro>\local_setup.bat call install\setup.bat
- Use
pythonrather thanpython3. - In
upstream.yaml,executable:may need to name the.exeor give a full path, depending on how the SDK installs.
cd ros2_ws
colcon build
source install/setup.bashinstall/setup.bash has to be sourced in every new shell you launch or run
from. The workspace holds three packages:
| Package | Contents |
|---|---|
rkk_hand_msgs |
the message definitions |
rkk_hand_bridge |
the bridge node |
rkk_hand_bringup |
launch files for the whole chain |
Run the tests with:
python3 -m pytest src/rkk_hand_bridge/test/Start everything β driver, solver and bridge β with one command:
ros2 launch rkk_hand_bringup smartglove_hands.launch.pyTo also open RViz, and draw the hands in it:
ros2 launch rkk_hand_bringup smartglove_hands.launch.py \
publish_markers:=true publish_tf:=true parent_frame_id:=world rviz:=trueCtrl-C stops the bridge, the solver and the driver together.
Attaching to a solver that is already running β start the bridge alone:
ros2 launch rkk_hand_bridge hand_bridge.launch.pyThe commands used for the driver and solver come from a config file β see Upstream config file to change them.
In another sourced shell:
ros2 topic list
ros2 topic echo /rkk_hand_bridge/hand/description --once
ros2 topic echo /rkk_hand_bridge/hand/joints --once
ros2 topic echo /diagnostics --oncehand/description should name your device and list 26 joints;
hand/joints should show changing pose data as you move your hand.
The official ros:<distro>-ros-base images run the bridge on any Linux
host, whatever its Ubuntu release. The Rokoko Device SDK stays on the host:
the driver and solver run there, and the bridge in the container reaches
the solver over the host network.
Start the solver on the host:
rkk-hand-solver --emit-openxr --driver-arg=-ef --driver-arg=1024From the repository root, start a container:
docker run -it --rm --network host --ipc host \
-v "$PWD":/repo:ro \
ros:jazzy-ros-base bash--network hostlets the bridge reach the solver on127.0.0.1and makes the topics visible on the host.--ipc hostis needed alongside it: ROS 2 sends data over shared memory between processes on the same host, and without it topics are listed but no messages arrive.- The repository is mounted read-only and copied before building, so the container does not write into your checkout.
To use RViz, or any other window, from the container, allow containers to use your display on the host:
xhost +local:dockerand add the display and GPU to docker run:
docker run -it --rm --network host --ipc host \
-e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
--device /dev/dri \
-v "$PWD":/repo:ro \
ros:jazzy-ros-base bashInside the container, build:
apt-get update && apt-get install -y ros-dev-tools
source /opt/ros/jazzy/setup.bash
mkdir -p /ws && cp -r /repo/ros2_ws/src /ws/ && cd /ws
rosdep update && rosdep install --from-paths src --ignore-src -r -y
colcon build
source install/setup.bashThen launch the bridge. spawn_solver:=false attaches to the solver on
the host instead of starting one, since the container has no SDK:
ros2 launch rkk_hand_bringup smartglove_hands.launch.py spawn_solver:=falseWith the display flags above, you can also open RViz:
ros2 launch rkk_hand_bringup smartglove_hands.launch.py \
spawn_solver:=false publish_markers:=true publish_tf:=true parent_frame_id:=world rviz:=trueSet up RViz as in Visualizing in RViz. For Humble,
replace jazzy with humble in the image name and the source line.
With more than one ROS 2 container on the host network, pass --no-daemon
to ros2 topic and ros2 node commands, or give each container its own
-e ROS_DOMAIN_ID=<n>. The containers otherwise share one ros2 daemon,
and a daemon from another distro crashes the CLI.
| Name | Type | Notes |
|---|---|---|
/rkk_hand_bridge/hand/description |
rkk_hand_msgs/HandDescription |
one per hand, latched: device id, handedness, joint names and radii |
/rkk_hand_bridge/hand/joints |
rkk_hand_msgs/HandJoints |
one per solved frame: 26 joint poses |
/rkk_hand_bridge/hand/markers |
visualization_msgs/MarkerArray |
RViz drawing, off unless publish_markers is set |
/tf |
tf2_msgs/TFMessage |
one transform per joint, off unless publish_tf is set |
/diagnostics |
diagnostic_msgs/DiagnosticArray |
per-hand health, published every second |
/rkk_hand_bridge/calibrate |
std_srvs/Trigger |
see Calibrating |
Joints are in XrHandJointEXT order and match joint_names in the
description.
The hand field carries one of the constants defined on the message β
HAND_UNKNOWN (0), HAND_LEFT (1) or HAND_RIGHT (2). Compare against
the constants rather than the numbers: 0 is reserved for unknown so that a
message whose hand was never set cannot pass for a real one.
TF frames are named rkk_<hand>_hand_xr_<joint_name>.
Two gloves are supported, one left and one right. Two gloves of the same handedness are not.
The solver streams poses referenced to magnetic north, so until you calibrate, the hand is correct in shape but rotated by an arbitrary amount about the vertical axis.
- Call:
ros2 service call /rkk_hand_bridge/calibrate std_srvs/srv/Trigger {}- You now have
calibrate_delay_s(5 seconds by default) to get into position β reaching for the keyboard to trigger the call is itself a hand movement, so the pose is sampled after the delay, not at the moment you press enter. - By the time the delay ends, hold your hand level, fingers pointing in the direction you want to become "forward". With two gloves, point both hands the same way.
A successful call names the hand it measured:
success: true
message: calibrated yaw offset to -2.834 rad from the left hand (device 843143769)
/diagnostics moves from WARN/"connected, yaw uncalibrated" to
OK/"connected" immediately, and RViz updates on the next frame. One
offset applies to every connected hand. Re-run it whenever you like.
"Forward" defaults to the parent frame's +X axis; calibrate_facing_rad
changes that.
HandJoints is a custom message, so RViz cannot draw it directly. Two
options, both off by default and both needing parent_frame_id:
publish_markersβ a sphere per joint joined by a skeleton. This is the one that looks like a hand.publish_tfβ a coordinate frame per joint. Useful for checking orientations, cluttered as a picture of a hand.
ros2 launch rkk_hand_bringup smartglove_hands.launch.py \
publish_markers:=true publish_tf:=true parent_frame_id:=world rviz:=truerviz:=true starts RViz with its own saved settings. Set it up once:
- Global Options β Fixed Frame β
world(or yourparent_frame_id). - Add β By topic β
/rkk_hand_bridge/hand/markersβ MarkerArray. - File β Save Config, and it reopens that way.
RViz needs the fixed frame to exist in TF before it draws anything, which
is why the command above also passes publish_tf:=true. If your gloves
drop out, the frame goes with them; anchor it independently to avoid that:
ros2 run tf2_ros static_transform_publisher --frame-id world --child-frame-id rkk_hand_anchorEach hand gets its own RViz namespaces β rkk_<hand>_hand/joints and
rkk_<hand>_hand/bones β so spheres and skeleton can be toggled
separately. Connected hands are drawn side by side rather than stacked.
Every parameter can be set as a launch argument (name:=value) or changed
on a running node with ros2 param set /rkk_hand_bridge <name> <value>.
Numeric parameters are doubles, so write 0.0, not 0.
| Parameter | Default | Meaning |
|---|---|---|
solver_host |
127.0.0.1 |
where the solver is |
solver_port |
12277 |
solver's port |
reconnect_delay_s |
0.5 |
wait before retrying a lost connection; doubles up to 10s |
stamp_source |
auto |
auto, epoch, offset or receive β how header.stamp is derived |
parent_frame_id |
(empty) | frame TF and markers are published in; required for either |
publish_tf |
false |
broadcast a transform per joint |
publish_markers |
false |
publish RViz markers |
marker_rate_hz |
40.0 |
marker updates per second, shared across all hands |
marker_lifetime_s |
0.5 |
how long a marker survives without an update |
marker_joint_scale |
1.0 |
scales every joint sphere |
marker_max_joint_radius_m |
0.010 |
caps sphere size so wrists don't swamp fingers |
marker_hand_spacing_m |
0.45 |
how far apart hands are drawn |
calibrate_facing_rad |
0.0 |
direction calibration treats as forward |
calibrate_max_disagreement_rad |
30Β° |
how far apart hands may point when calibrating |
calibrate_delay_s |
5.0 |
wait before sampling, so calling the service isn't itself the movement it measures |
Marker parameters affect the drawing only. hand/joints and /tf always
carry every frame of every hand at full rate.
Defaults live in
ros2_ws/src/rkk_hand_bridge/rkk_hand_bridge/constants.py. Change them
there and rebuild to change them permanently.
rkk_hand_bringup's launch file adds:
| Argument | Default | Meaning |
|---|---|---|
spawn_solver |
true |
start the solver, or attach to a running one |
config |
(packaged file) | upstream config file, see below |
rviz |
false |
open RViz alongside the bridge |
The solver and driver are ordinary programs rather than ROS nodes, so their command lines live in a config file rather than being ROS parameters:
ros2_ws/src/rkk_hand_bringup/config/upstream.yaml
solver:
executable: rkk-hand-solver
args: ["--emit-openxr"]
driver_args: ["--auto-stream-usb", "-ef", "1024"]args runs the solver as written, and each entry of driver_args is
forwarded on to the driver. What those arguments mean is covered by the
Rokoko Device SDK's own documentation. --emit-openxr is the one this
bridge depends on: it is the joint group the bridge reads.
Edit that file, or point config:=/path/to/my.yaml at your own. Keys you
leave out fall back to the packaged file, so a partial file is fine.
To run the driver yourself rather than have the solver start it, add a
driver section and give the solver --no-driver:
solver:
executable: rkk-hand-solver
args: ["--emit-openxr", "--no-driver"]
driver:
executable: rokoko-sdk
args: ["-vv", "-ef", "1024"]That is also how to change the driver's arguments for gloves on WiFi
rather than USB β again, see the SDK documentation for which arguments
those are. WiFi gloves must be on the same network as this machine and
pointed at its LAN address rather than 127.0.0.1.
ros2: command not found β source ROS 2:
source /opt/ros/<distro>/setup.bash.
Unable to locate package ros-<distro>-desktop β that distro has no
packages for your Ubuntu release. Check the table in
Installing ROS 2, or use Docker.
Package 'rkk_hand_bringup' not found β source the workspace:
source install/setup.bash from ros2_ws, after colcon build.
cannot reach the RGMP v2 server at 127.0.0.1:12276 β the solver is
running but the driver is not. Start rokoko-sdk, or let the launch file
do it.
No joints, and a warning about joints_openxr β the solver was started
without --emit-openxr. /diagnostics reports this as an error naming the
device.
RBDP Protocol Error / Could not create device β the driver cannot
talk to the hub. Usually a stale process still holding the USB device:
check pgrep -x rokoko-sdk, kill it, and replug the hub.
Nothing appears in RViz β check the Fixed Frame matches
parent_frame_id, that you added the MarkerArray display on
/rkk_hand_bridge/hand/markers, and that publish_markers:=true was
passed.
Hand is rotated the wrong way β call the calibrate service, see Calibrating.
This is not caused by this bridge, or by any ROS node β it reproduces with RViz opened on its own, with nothing publishing anything. It is a known, unfixed bug in how Mutter (GNOME's compositor) handles XWayland clients being resized:
- ros2/rviz#1079 β major screen flickering/tearing
- ros2/rviz#1052 β flickering with HiDPI scaling enabled
- GNOME/mutter#2539 β non-interactive resizes cause black flicker
- GNOME/mutter#3241 β XWayland clients flickering since GNOME 45
What actually triggers it is resizing a window that is already on
screen β maximizing, -geometry, snapping, tiling β not the size it
ends up at. A plain launch that is never resized afterward does not
flicker:
rviz2Some people report Qt/GNOME environment variables that help on their setup, but nothing reliable across machines β if it still flickers for you, avoid resizing/maximizing the window, or live with it.
Apache License 2.0 β see LICENSE.
Contributions must be signed off under the Developer Certificate of Origin, unless covered by the MAINTAINERS DCO list; see CONTRIBUTING.md.
