Skip to content

X-Square-Robot/sdk_hand

Repository files navigation

ZBL Five-Finger Dexterous Hand SDK

SDK for the ZBL five-finger dexterous hand, providing ROS 2 interfaces and ROS2-based high-level Python/C++ APIs for secondary development.

⚠️ Safety Warning

Warning

  1. Before operation

    • Ensure the hand is firmly mounted
    • Keep clear of fingers during motion
    • Maintain a safe distance before enabling position control
  2. Do not launch multiple ROS processes with overlapping parameters at the same time

    Running ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py starts right-hand control services. Launching multiple overlapping processes may cause abnormal behavior and damage finger motors.

  3. In dual-hand scenarios, do not connect both hands through the same USB hub (shared upstream port). Two uncompressed YUYV 640x480 @30fps streams require about 36 MB/s and may saturate one USB 2.0 upstream link, causing one camera to fail with Unable to start stream. If you need both camera streams, connect each hand to a different USB port on the host.

  4. Do not plug/unplug power or USB cables while powered on

    Always power off the hand before plugging or unplugging any cable to avoid hardware damage.


Directory Structure

sdk_project/
├── .devcontainer/           # VSCode/Cursor dev container configuration
│   ├── devcontainer.json
│   ├── docker-compose.yml
│   ├── Dockerfile.base      # Base image: system + dependency packages
│   ├── Dockerfile           # Dev image: user setup + COPY deps/ (~30s)
│   ├── apt.list
│   ├── ros2_apt.list
│   └── pip.list
├── deps/                    # Dependencies
├── lib/
│   ├── python/              # Python wrapper API (on top of ROS2)
│   └── cpp/                 # C++ wrapper API (on top of ROS2)
├── examples/
│   ├── python/              # Python examples
│   └── cpp/                 # C++ examples
├── docs/                    # API references
│   ├── API_ROS2_CN.md       # ROS2 interface reference (CN)
│   ├── API_ROS2.md          # ROS2 interface reference (EN)
│   ├── API_SDK_CN.md        # SDK wrapper API reference (CN)
│   └── API_SDK.md           # SDK wrapper API reference (EN)
├── setup_serial.sh          # Host serial setup script
├── README.md                # This file
└── README_CN.md             # Chinese version

Quick Start

Requirements

  • Linux (Ubuntu 22.04+ recommended)
  • At least 2 USB ports available (USB 2.0+)
  • Docker and Docker Compose
  • USB Type-C adapter
  • Stable DC power supply, at least 36V / 4A

Install Docker and Docker Compose (Host)

Install Docker: Docker official docs

Install Docker Compose: Docker Compose official docs

Configure Docker mirror (recommended in Mainland China)

To speed up image pulls (especially base images such as ubuntu:24.04), configure registry mirrors.

Tip

  • You can configure multiple mirror endpoints for better failover
  • Remove/replace unavailable endpoints as needed
  • This significantly improves base image build speed

Create or edit /etc/docker/daemon.json:

sudo mkdir -p /etc/docker
sudo vim /etc/docker/daemon.json

Example full JSON:

{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://dockerproxy.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://docker.nju.edu.cn"
  ]
}

Apply:

sudo systemctl daemon-reload
sudo systemctl restart docker

Verify:

docker info | grep -A 5 "Registry Mirrors"

Common mirror sources:

Mirror URL Notes
DaoCloud https://docker.m.daocloud.io Recommended, usually stable
DockerProxy https://dockerproxy.com Community-maintained
USTC https://docker.mirrors.ustc.edu.cn Good for education networks
NJU https://docker.nju.edu.cn Good for education networks
Aliyun https://<your_id>.mirror.aliyuncs.com Register in Aliyun CR first

Connect the Hand

Connect USB cable

Connect the hand to host via USB cable. image

Connect power

  1. Connect the power cable to the hand power port
    image
  2. Set voltage to 30V and current to 4A
    image
  3. Turn on power

Configure Serial Port

Run on the host machine (not inside container), from open_hand_sdk root.

Check PID

By default, left hand is d601, right hand is d602.

bash setup_serial.sh --identify

Example:

=== Connected GD32 CDC Devices ===
  /dev/ttyACM0  serial=358C657933BB  PID=d602  (RIGHT hand — ZBL dexterous hand)

The PID determines which installation mode to use:

Hardware PID Output hint
Right hand d602 PID=d602 (RIGHT hand ...)
Left hand d601 PID=d601 (LEFT hand ...)
Same PID on both hands identical Use --device to pin each device node

You can also query directly:

udevadm info /dev/ttyACM0 | grep ID_MODEL_ID
# E: ID_MODEL_ID=d602

Install udev rules

Choose one of the following modes. Hands shipped with different left/right PIDs (the default) should use Mode A.

Merge semantics. --install-rules performs a per-symlink merge into /etc/udev/rules.d/99-zbl-dexterous-hand.rules: only the symlinks written this run are replaced; rules for other symlinks (e.g. the other hand, the exoskeleton) and any manual edits OUTSIDE the # >>> zbl-managed: <symlink> / # <<< zbl-managed: <symlink> markers are preserved across runs. Use the standalone --reset command (see below) to remove all script-managed blocks.

Mode A — by PID (recommended)

When the left/right hands have different PIDs (d601 / d602), udev matches each hand by PID, so plug order doesn't matter. The two single-side commands can be run separately and the second one will not wipe the first.

bash setup_serial.sh --install-rules                    # right hand only (--side defaults to right, PID d602)
bash setup_serial.sh --install-rules --side left        # left hand only  (PID d601) — keeps right hand rule
bash setup_serial.sh --install-rules --side dual        # both hands at once

Mode B — pin by device node

When two devices share the same PID (two same-PID hands, or two same-PID exoskeletons), pin a specific ttyACMx to each one. The script can only pin one hand and at most one exo per call, so run it once per side. Thanks to the merge semantics above, the second call does not wipe what the first call wrote.

Pin only the hands:

bash setup_serial.sh --install-rules --side right --device /dev/ttyACM0
bash setup_serial.sh --install-rules --side left  --device /dev/ttyACM1

Pin a hand and its exoskeleton in one call (--exo enables exo handling, --exo-device pins the exo node):

bash setup_serial.sh --install-rules --side right --device /dev/ttyACM0 --exo --exo-device /dev/ttyACM2

Two hands plus two exoskeletons (four devices, two calls, all preserved across runs):

bash setup_serial.sh --install-rules --side right --device /dev/ttyACM0 --exo --exo-device /dev/ttyACM2
bash setup_serial.sh --install-rules --side left  --device /dev/ttyACM1 --exo --exo-device /dev/ttyACM3

You can also mix Mode A and Mode B — e.g. install both hands by PID once, then pin only the exoskeletons by node:

bash setup_serial.sh --install-rules --side dual                                              # hands by PID
bash setup_serial.sh --install-rules --side right --exo --exo-device /dev/ttyACM2             # right exo by node
bash setup_serial.sh --install-rules --side left  --exo --exo-device /dev/ttyACM3             # left  exo by node

After installation the script automatically prints each expected symlink with its real device node and VID/PID/serial:

=== Verify symlinks (rules file: /etc/udev/rules.d/99-zbl-dexterous-hand.rules) ===
  [OK]      /dev/zbl_right_dexterous_hand    -> /dev/ttyACM0   (VID:0483 PID:d602 serial=EDAA6579427F)
  [OK]      /dev/zbl_left_dexterous_hand     -> /dev/ttyACM2   (VID:0483 PID:d601 serial=DB2C657934B9)

All 2 expected symlink(s) present and ready to use.

If any line shows [MISSING], power cycle and replug the USB cable, then re-check:

bash setup_serial.sh --side dual          # or --side left / --side right
# fallback: ls -la /dev/zbl_*

Mode C — bypass udev: pass raw serial paths directly to the launch file (no udev rules needed):

ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py side:=right serial_port:=/dev/ttyACM0
ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py side:=left  serial_port:=/dev/ttyACM1
ros2 launch zbl_dexterous_hand_bringup dual_hand.launch.py left_serial_port:=/dev/ttyACM1 right_serial_port:=/dev/ttyACM0

Reset / uninstall rules

Run the standalone --reset command to remove every script-managed block from the rules file. It cannot be combined with --install-rules or --identify. Manual rules outside the markers are kept; if nothing else remains, the rules file itself is deleted.

bash setup_serial.sh --reset
Removing 4 zbl-managed block(s) from /etc/udev/rules.d/99-zbl-dexterous-hand.rules:
  - zbl_right_dexterous_hand
  - zbl_left_dexterous_hand
  - right_exo_skeleton
  - left_exo_skeleton
No manual rules left; removing /etc/udev/rules.d/99-zbl-dexterous-hand.rules entirely.

Replug the USB cable to make any removed /dev/zbl_* and /dev/*_exo_skeleton symlinks disappear immediately.

Start Dev Container

Docker uses a two-layer image structure:

Image Dockerfile Rebuild when
zbl_hand_sdk:base_latest Dockerfile.base apt.list / ros2_apt.list / pip.list / Dockerfile.base changed (~10 min)
zbl_hand_sdk_image:v0.1.1 Dockerfile deps/ updated (~30s)
# First time (one-time, ~10 min)
cd /path/to/open_hand_sdk
docker build -f .devcontainer/Dockerfile.base -t zbl_hand_sdk:base_latest .

# Rebuild dev image on existing base (~30s)
cd .devcontainer
docker compose build
docker compose up -d

Or use VSCode/Cursor Dev Containers:

  1. Install Dev Containers
  2. Open project folder
  3. F1 -> Dev Containers: Reopen in Container

Start Hand Control Process

Enter container

docker exec -it zbl_hand_sdk bash

source commands are already written into .bashrc in .devcontainer/Dockerfile, so no extra sourcing is required after entering the container.

Camera device path configuration

The hand camera is mounted on the lower palm area.

Camera device paths are configured in:

  • /opt/xr/core/install/zbl_dexterous_hand_bringup/share/zbl_dexterous_hand_bringup/config/camera_right.yaml
  • /opt/xr/core/install/zbl_dexterous_hand_bringup/share/zbl_dexterous_hand_bringup/config/camera_left.yaml

How to find the correct /dev/video*:

  1. Check existing devices before plugging camera USB:
ls /dev/video*

image

  1. Plug camera USB, check again. Newly appeared devices belong to the camera.
    For example, if /dev/video4 appears, set video_device: /dev/video4.
    If two devices appear (for example 4 and 5), usually the even index (4) is the capture stream.

image image

Launch hand control process

# ── Single hand ─────────────────────────────────────────────────────────────
# Right hand (default)
ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py &

# Left hand
ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py side:=left &

# With eye-in-hand camera (camera:=true is the default, set false to disable)
ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py camera:=true &
ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py side:=left camera:=true &

# With exoskeleton teleoperation
ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py use_exoskeleton:=true &
ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py side:=left use_exoskeleton:=true &

# Custom exo serial port (default: {side}_exo_skeleton udev symlink)
ros2 launch zbl_dexterous_hand_bringup single_hand.launch.py use_exoskeleton:=true exo_serial_port:=/dev/ttyACM0 &

# ── Dual hand ────────────────────────────────────────────────────────────────
ros2 launch zbl_dexterous_hand_bringup dual_hand.launch.py &

# With cameras (both on by default)
ros2 launch zbl_dexterous_hand_bringup dual_hand.launch.py right_camera:=true left_camera:=true &

# With exoskeleton teleoperation (both sides)
ros2 launch zbl_dexterous_hand_bringup dual_hand.launch.py use_exoskeleton:=true &

# Custom exo serial ports for dual hand
ros2 launch zbl_dexterous_hand_bringup dual_hand.launch.py use_exoskeleton:=true \
    right_exo_serial_port:=/dev/ttyACM0 left_exo_serial_port:=/dev/ttyACM1 &

If launched correctly, the hand performs a homing motion (close then open).

Verify controllers:

ros2 control list_controllers

Expected output (dual-hand example):

right_dexterous_hand_controller         x2robot_controllers/DexterousHandCommandController  inactive
right_hand_tactile_sensor_broadcaster   x2robot_controllers/TashanTactileSensorBroadcaster  active
left_hand_tactile_sensor_broadcaster    x2robot_controllers/TashanTactileSensorBroadcaster  active
left_dexterous_hand_controller          x2robot_controllers/DexterousHandCommandController  inactive
joint_state_broadcaster                 joint_state_broadcaster/JointStateBroadcaster       active

{side}_dexterous_hand_controller is inactive by default. Activate it before sending position commands.

Run Examples

cd ~/workspace

# Camera viewer
python3 examples/python/ROS2/camera_viewer.py
python3 examples/python/ROS2/camera_viewer.py --side left
python3 examples/python/ROS2/camera_viewer.py --side dual

# Python ROS2-layer examples
python3 examples/python/ROS2/controller_manager.py --list
python3 examples/python/ROS2/controller_manager.py --switch pos --side dual
python3 examples/python/ROS2/state_monitor.py --side dual
python3 examples/python/ROS2/tactile_monitor.py --side left
python3 examples/python/ROS2/finger_control.py --joint right_1_1 --delta 0.1
python3 examples/python/ROS2/hand_demo.py --gesture fist
python3 examples/python/ROS2/hand_demo.py --sequence --side left

# Python WrapperAPI-layer examples
python3 examples/python/WrapperAPI/state_monitor.py --side dual --hz 10
python3 examples/python/WrapperAPI/tactile_monitor.py --side right --hz 10
python3 examples/python/WrapperAPI/finger_control.py --joint right_2_2 --target 1.02 --duration 2.0
python3 examples/python/WrapperAPI/hand_demo.py --sequence --side dual

# C++ examples
source /opt/xr/core/install/setup.bash
colcon build --base-paths lib/cpp lib/python examples/cpp --symlink-install
source install/setup.bash

ros2 run dexterous_hand_examples state_monitor --side right
ros2 run dexterous_hand_examples tactile_monitor --side left
ros2 run dexterous_hand_examples finger_control --joint right_1_1 --delta 0.1
ros2 run dexterous_hand_examples hand_demo --gesture fist --side dual

See the Examples section below for full command references.


ROS2 Interface Reference

ROS Domain ID defaults to 134. Change ROS_DOMAIN_ID in .devcontainer/docker-compose.yml if needed.

Topics

Command

Topic Type Description
/{side}_dexterous_hand_controller/commands std_msgs/msg/Float64MultiArray 15-joint position command (rad)

State and sensing

Topic Type Description
/joint_states sensor_msgs/msg/JointState Global joint state (pos/vel/effort)
/diagnostics diagnostic_msgs/msg/DiagnosticArray Aggregated diagnostics (device/controller health)
/{side}_hand_tactile x2robot_msgs/msg/TashanTactileData Fingertip tactile data
/{side}_eye_in_hand/image_raw/compressed sensor_msgs/msg/CompressedImage Compressed camera image

Quick checks

# Publish right-hand open pose command (controller must be active)
ros2 topic pub /right_dexterous_hand_controller/commands \
  std_msgs/msg/Float64MultiArray \
  "{data: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}" --once

# Joint states
ros2 topic echo /joint_states

# Diagnostics
ros2 topic echo /diagnostics

# Right-hand tactile
ros2 topic echo /right_hand_tactile

Services

Service Type Description
/controller_manager/list_controllers controller_manager_msgs/ListControllers Query controller states
/controller_manager/switch_controller controller_manager_msgs/SwitchController Switch controllers
ros2 control switch_controllers --activate right_dexterous_hand_controller

Controllers

Controller Plugin Default Description
joint_state_broadcaster joint_state_broadcaster/JointStateBroadcaster active Publishes /joint_states
{side}_dexterous_hand_controller x2robot_controllers/DexterousHandCommandController inactive Position controller
{side}_hand_tactile_sensor_broadcaster x2robot_controllers/TashanTactileSensorBroadcaster active Tactile broadcaster

Joint Model

The hand has 15 independently actuated joints (5 fingers x 3 joints each), in radians.

Index Joint Finger Description Range (rad)
0 {prefix}1_1_joint Thumb Splay / abduction [0, 1.8316]
1 {prefix}1_2_joint Thumb Proximal flexion [0, 0.872]
2 {prefix}1_3_joint Thumb Distal flexion [0, 1.57]
3 {prefix}2_1_joint Index Splay [-0.348, 0.348]
4 {prefix}2_2_joint Index MCP flexion [0, 1.57]
5 {prefix}2_3_joint Index PIP flexion [0, 1.57]
6 {prefix}3_1_joint Middle Splay [-0.348, 0.348]
7 {prefix}3_2_joint Middle MCP flexion [0, 1.57]
8 {prefix}3_3_joint Middle PIP flexion [0, 1.57]
9 {prefix}4_1_joint Ring Splay [-0.348, 0.348]
10 {prefix}4_2_joint Ring MCP flexion [0, 1.57]
11 {prefix}4_3_joint Ring PIP flexion [0, 1.57]
12 {prefix}5_1_joint Pinky Splay [-0.348, 0.348]
13 {prefix}5_2_joint Pinky MCP flexion [0, 1.57]
14 {prefix}5_3_joint Pinky PIP flexion [0, 1.57]

{prefix} is right_ or left_. {prefix}N_4_joint are mimic joints (DIP = PIP * 1), not independently commanded.


Python API

Import

No pip install is required. Add lib/python into PYTHONPATH. Example scripts do this automatically via examples/python/sdk_paths.py.

from dexterous_hand import DexterousHand

DexterousHand constructor

DexterousHand(
    side: str = "right",
    node: Optional[rclpy.node.Node] = None,
    *,
    manage_controller: bool = True,
)
Argument Type Description
side str right or left
node Node | None Optional external node
manage_controller bool If False, skip activate/deactivate (monitor-only scenarios)

Controller methods

Method Return Description
activate(timeout=10.0) bool Activate {side}_dexterous_hand_controller
deactivate(timeout=10.0) bool Deactivate position controller
is_active() bool Whether controller is active

Motion methods

Method Return Description
set_positions(positions) None Publish one 15D target (clipped to limits)
move_to(positions, duration=2.0, dt=0.02) bool Smoothstep interpolated motion
open(duration=1.5) bool Move to all zeros (URDF rest pose)

State methods

Method Return Description
get_joint_positions(timeout=5.0) np.ndarray(15,) | None Joint positions (rad)
get_joint_velocities() np.ndarray(15,) | None Joint velocities (rad/s)
get_joint_efforts() np.ndarray(15,) | None Joint efforts
get_tactile_data() TashanTactileData | None Tactile message

Lifecycle

Method Description
spin_background() Non-blocking background spin thread
spin() Blocking spin
destroy() Stop and destroy resources

Constants

from dexterous_hand import (
    NUM_JOINTS,
    JOINT_LOWER,
    JOINT_UPPER,
    JOINT_SUFFIXES,
    FINGER_LABELS,
)

Demo poses are defined in examples/python/demo_poses.py: POSE_OPEN, POSE_FIST, POSE_PINCH, POSE_POINT, POSE_THREE, POSE_THUMBS_UP.


C++ API

Include

dexterous_hand_cpp is header-only:

#include "dexterous_hand/dexterous_hand.hpp"
find_package(dexterous_hand_cpp REQUIRED)
ament_target_dependencies(my_node dexterous_hand_cpp rclcpp)

Constructor

DexterousHand(
    const std::string& side = "right",
    rclcpp::Node::SharedPtr node = nullptr);

Controller methods

Method Return Description
activate(double timeout=10.0) bool Activate position controller
deactivate(double timeout=10.0) bool Deactivate position controller
isActive(double timeout=5.0) bool Query controller state

Motion methods

Method Return Description
setPositions(const Positions&) void Publish one 15D target (clipped to limits)
moveTo(const Positions&, double duration=2.0, double dt=0.02) bool Smoothstep interpolated motion
open(double duration=1.5) bool Move to all zeros

State methods

Method Return Description
getJointPositions(double timeout=5.0) std::optional<Positions> Joint positions
getJointVelocities() std::optional<Positions> Joint velocities
getJointEfforts() std::optional<Positions> Joint efforts

Lifecycle

Method Description
spinBackground() Background callback thread
destroy() Stop and destroy resources
getNode() Get internal node

Constants

namespace dexterous_hand {
constexpr int NUM_JOINTS = 15;
using Positions = std::array<double, NUM_JOINTS>;
constexpr Positions JOINT_LOWER = { ... };
constexpr Positions JOINT_UPPER = { ... };
}

Demo poses are in examples/cpp/demo_poses.hpp (zbl_hand_demo namespace).


Examples

Examples are organized into two layers:

  • ROS2/: direct topic/service usage
  • WrapperAPI/: based on DexterousHand

Python examples

Script Layer Description
ROS2/controller_manager.py ROS2 List/switch controllers (--list, --switch pos, --side)
ROS2/state_monitor.py ROS2 Joint state + diagnostics monitor (--side right/left/dual)
ROS2/tactile_monitor.py ROS2 Tactile monitor (--side right/left/dual)
ROS2/finger_control.py ROS2 Single-joint control (--joint + --delta/--target)
ROS2/hand_demo.py ROS2 Gesture demo (--gesture or --sequence)
ROS2/camera_viewer.py ROS2 Camera viewer (subscribes to image_raw/compressed only)
WrapperAPI/state_monitor.py Wrapper Joint + status + tactile + diagnostics terminal UI (--side, --hz, --no-diagnostics)
WrapperAPI/tactile_monitor.py Wrapper Tactile terminal UI (--side, --hz)
WrapperAPI/finger_control.py Wrapper Single-joint CLI (same as ROS2 version)
WrapperAPI/hand_demo.py Wrapper Gesture demo (same CLI as ROS2 version)

Common commands (run under ~/workspace/examples/python):

cd ~/workspace/examples/python

# ROS2 layer
python3 ROS2/controller_manager.py --list
python3 ROS2/controller_manager.py --switch pos --side dual
python3 ROS2/state_monitor.py --side dual
python3 ROS2/tactile_monitor.py --side left
python3 ROS2/finger_control.py --joint right_1_1 --delta 0.1
python3 ROS2/hand_demo.py --gesture fist
python3 ROS2/hand_demo.py --sequence --side left
python3 ROS2/camera_viewer.py --side dual

# WrapperAPI layer
python3 WrapperAPI/state_monitor.py --side dual --hz 10
python3 WrapperAPI/state_monitor.py --no-diagnostics
python3 WrapperAPI/tactile_monitor.py --side right --hz 10
python3 WrapperAPI/finger_control.py --joint right_2_2 --target 1.02 --duration 2.0
python3 WrapperAPI/hand_demo.py --sequence --side dual

Gesture targets and sequences are defined in examples/python/demo_poses.py: GESTURE_TARGETS, DEMO_SEQUENCE.

C++ examples

Program Layer Description
ROS2/state_monitor ROS2 Joint state monitor (--side right/left)
ROS2/tactile_monitor ROS2 Tactile monitor (--side right/left)
WrapperAPI/finger_control Wrapper Single-joint control (--joint + --delta/--target)
WrapperAPI/hand_demo Wrapper Gesture demo (--gesture/--sequence --side right/left/dual)
cd ~/workspace
source /opt/xr/core/install/setup.bash
colcon build --base-paths lib/cpp lib/python examples/cpp --symlink-install
source install/setup.bash

ros2 run dexterous_hand_examples state_monitor --side right
ros2 run dexterous_hand_examples tactile_monitor --side left
ros2 run dexterous_hand_examples finger_control --joint right_1_1 --delta 0.1
ros2 run dexterous_hand_examples hand_demo --gesture fist --side dual
ros2 run dexterous_hand_examples hand_demo --sequence --side left

Command Cheat Sheet

# List controllers
ros2 control list_controllers

# Activate right position controller
ros2 control switch_controllers --activate right_dexterous_hand_controller

# Activate left position controller
ros2 control switch_controllers --activate left_dexterous_hand_controller

# Joint states
ros2 topic echo /joint_states

# Diagnostics
ros2 topic echo /diagnostics

# Publish open command (right hand)
ros2 topic pub /right_dexterous_hand_controller/commands \
  std_msgs/msg/Float64MultiArray \
  "{data: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}" --once

# List topics
ros2 topic list

# Tactile
ros2 topic echo /right_hand_tactile

# List nodes
ros2 node list

# Record bag
ros2 bag record /joint_states \
  /diagnostics \
  /right_dexterous_hand_controller/commands \
  /right_hand_tactile

FAQ

Q1: No module named 'rclpy'

Source workspace first:

source /opt/xr/core/install/setup.bash

Q2: Serial device not found

Check USB connection and udev rules:

bash setup_serial.sh --install-rules
# replug USB cable
bash setup_serial.sh

Q3: Controller switching fails

Check whether launch process is running:

ros2 node list | grep controller_manager

If missing, restart launch process.

Q4: Left hand not recognized in dual-hand mode

Check PID of each hand (plug one hand at a time):

bash setup_serial.sh --identify

If PIDs differ (d601/d602):

bash setup_serial.sh --install-rules --side dual

If both hands share the same PID, pin them by device node. Run once per side; thanks to the merge semantics described above, the second call does not wipe the first:

bash setup_serial.sh --install-rules --side right --device /dev/ttyACM0
bash setup_serial.sh --install-rules --side left  --device /dev/ttyACM1

Q5: Joint position out of limits

If you see an error like Joint position is out of bounds for the joint and the control process exits, keep commanded joint targets within limits and restart the process.

image

Q6: Joint angles become nan after exoskeleton calibration

image

This is usually caused by improper wearing or loose finger-link cables. If the linkage cannot be tensioned during calibration, the driver may fail to compute valid angles. Re-wear the exoskeleton and check whether each finger-link cable can be properly tightened.

Q7: After exoskeleton calibration, the palm is open but PIP joints are not close to 0

image

This usually means the spring cannot retract normally after being stretched, leaving the PIP joint in a continuously pulled state. Check whether the spring can extend and retract normally.

Q8: During teleoperation, the exoskeleton finger direction is opposite to the dexterous hand

For example, the exoskeleton thumb abducts outward, but the dexterous hand moves inward:

image

Adjust the corresponding joint direction in:

vim /opt/xr/core/install/exoskeleton_hand_driver_ros/lib/exoskeleton_hand_driver_ros/config/exoskeleton_hand_retarget.conf

For example, change the thumb joint 1 (abduction/adduction) direction from 2.0 to -2.0.

image


Notes

  1. Ensure there are no obstacles around the hand before control.

  2. Startup order: launch control process first, then run application after controllers are loaded.

  3. Use interpolated motion when moving to target positions to avoid abrupt motion and motor stall from finger interference.

  4. Controller default is inactive: you must explicitly activate it before control.

  5. Command rate: recommended 50 Hz (dt=20ms), while hardware loop runs at 200 Hz.

  6. Multi-system isolation: use export ROS_DOMAIN_ID=<number> when multiple systems share one network.


About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors