Wuji Dexterous Hand — SDK Setup & Teleop Guide
Complete guide to connecting, streaming, and collecting demonstration data from the Wuji dexterous hand using the wujihandpy SDK, the JSONL bridge agent, and the Fearless Platform.
1. Overview
The Wuji dexterous hand is a high-DOF robotic end effector designed for contact-rich manipulation teleoperation and imitation learning data collection. It provides 5 fingers with 4 joints each (5×4 = 20 joint positions), a 24×32 tactile pressure map, an IMU channel, and an EMF (electromagnetic field) sensor channel for auxiliary contact sensing. Together these signals give a rich state representation suitable for training manipulation policies that generalize across grasp types and object geometries.
The Wuji SDK ecosystem consists of two layers. The lower layer is the wujihandpy Python package that communicates directly with the hardware over USB (VID 0x0483 by default). The upper layer is the JSONL bridge pipeline used by the RoboticsCenter teleop infrastructure: wuji_hand_sdk_stream.py reads from wujihandpy and emits one JSON object per line to stdout; wuji_glove_agent.py launches this script as a subprocess, parses the JSONL, and forwards decoded sensor data to the Fearless Platform via WebSocket.
Wuji Studio (GUI application, v0.7.0 for amd64 Linux) provides standalone visualization of joint positions and pressure maps without the platform pipeline — useful for hardware bring-up and calibration verification.
2. Full Specifications
| Property | Value |
|---|---|
| Fingers | 5 (thumb, index, middle, ring, little) |
| Joints per finger | 4 |
| Total joint positions | 20 (5×4 array, float, radians) |
| Tactile pressure map | 24 rows × 32 columns (768 values, float [0, 1]) |
| IMU channels | 6-axis style (3-axis accelerometer + 3-axis gyro), up to 16 slots in stream |
| EMF channel | Per-finger bend + hand-back yaw (dict keyed by finger name) |
| USB vendor ID | 0x0483 (default) |
| SDK package | wujihandpy (Python) |
| Studio application | Wuji Studio v0.7.0 (Linux amd64 .deb / .tar.gz / .zip) |
| Stream format | JSONL (one JSON object per line, stdout) |
| Default stream Hz | 30 Hz (configurable with --hz) |
| Hand sides supported | Left, right, or auto-detected from first frame |
| Platform device type | wuji_hand |
| Platform agent module | wuji_glove_agent |
| Backend WebSocket URL | ws://127.0.0.1:8000 (default) |
| Telemetry Hz | 30 Hz (configurable with --telemetry-hz) |
| Reconnect policy | Exponential backoff: 1s min, 10s max |
3. Quick Start
Install SDK and connect
-
Install the Wuji SDK Python package.
pip install wujihandpy numpy
-
Plug in the Wuji hand via USB. Confirm the device appears. On Linux:
lsusb | grep 0483. Add your user to thedialoutgroup if needed. -
Start the JSONL stream.
python3 wuji_hand_sdk_stream.py --hand-side right --hz 30
You should see JSON objects printed to stdout at 30 Hz, each containingbends,joint_actual_position_5x4,pressure_map_24x32,imu, andemf. -
Launch the teleop agent connected to the platform.
python3 wuji_glove_agent.py \ --session YOUR_SESSION_ID \ --node-id wuji-right \ --glove right
The agent auto-launches the stream script as a subprocess and begins forwarding data to the platform WebSocket.
Mock mode (no hardware)
Use the included mock_wuji_stream.py to generate synthetic JSONL frames. This emits animated finger bends, a Gaussian pressure-blob tactile map, 6-axis IMU data, and EMF readings — all at configurable Hz:
python3 mock_wuji_stream.py --hz 30 --hand-side right --seed 42
To use the mock stream with the agent, override the default stream command:
python3 wuji_glove_agent.py \ --session YOUR_SESSION_ID \ --node-id wuji-mock \ --wuji-cmd "python3 mock_wuji_stream.py --hz 30 --hand-side right"
"schema": "wuji.mock.v1" field. The agent parses both mock and real frames identically — it looks for the bends, pressure_map_24x32, imu, and emf keys regardless of schema version.
4. SDK / Python Usage
JSONL frame format
Every frame emitted by wuji_hand_sdk_stream.py contains the following fields:
| Field | Type | Shape / Range | Description |
|---|---|---|---|
ts | float | Unix seconds | Frame timestamp (time.time()) |
hand_side | str | "left" | "right" | Which hand this frame belongs to |
bends | dict | 5 keys, [0, 1] | Per-finger normalized bend (thumb, index, middle, ring, little) |
joint_actual_position_5x4 | list[list[float]] | 5×4, radians | Raw joint positions from wujihandpy, reshaped to 5 fingers × 4 joints |
pressure_map_24x32 | list[list[float]] | 24×32, [0, 1] | Tactile pressure distribution across hand surface |
imu | list[float] | up to 16 values | 6-axis IMU: [ax, ay, az, gx, gy, gz, ...] (remaining slots zeroed) |
emf | dict | per-finger + hand_back | EMF sensor readings; each entry is a float or dict with "bend"/"angle" keys |
The bends values are derived from joint_actual_position_5x4 by averaging the 4 joint angles per finger and normalizing from the [0, π/2] range to [0, 1]:
import numpy as np, math
pos = np.array(hand.read_joint_actual_position(timeout=0.4)).reshape(5, 4)
bends = {}
for i, finger in enumerate(["thumb","index","middle","ring","little"]):
val = float(np.mean(pos[i]))
bends[finger] = min(1.0, max(0.0, val / (math.pi / 2.0)))
Bridge agent internals
wuji_glove_agent.py wraps linker_glove_agent.py with a Wuji-specific argument set. Key parameters:
| Flag | Default | Description |
|---|---|---|
--backend | ws://127.0.0.1:8000 | Platform WebSocket base URL |
--session | (required) | Teleop session ID from the platform |
--node-id | wuji-glove-node | Identifier for this device node in the session |
--glove | auto | Hand side: left, right, or auto (read from first frame) |
--device-kind | wuji_hand | Device type registered with platform |
--telemetry-hz | 30.0 | Telemetry forwarding rate to platform |
--wuji-cmd | python3 wuji_hand_sdk_stream.py --hand-side right --hz 30 | Shell command that emits JSONL frames to stdout |
--wuji-pressure-key | pressure_map_24x32 | JSON key for the pressure map array |
--wuji-emf-key | emf | JSON key for the EMF readings |
--wuji-imu-key | imu | JSON key for the IMU array |
--reconnect-min-s | 1.0 | Minimum reconnect backoff (seconds) |
--reconnect-max-s | 10.0 | Maximum reconnect backoff (seconds) |
The agent uses source: "wuji_jsonl" internally to route JSONL parsing through the Wuji-specific decoder in linker_glove_agent.py, which handles the bends, joint_angles, and emf field variants that different Wuji firmware versions may emit.
Bilateral (left + right) setup
Run two agent processes simultaneously with different node IDs connected to the same session:
# Terminal 1 — right hand python3 wuji_glove_agent.py \ --session RC-XXXX-XXXX --node-id wuji-right --glove right \ --wuji-cmd "python3 wuji_hand_sdk_stream.py --hand-side right --serial-number SN001" # Terminal 2 — left hand python3 wuji_glove_agent.py \ --session RC-XXXX-XXXX --node-id wuji-left --glove left \ --wuji-cmd "python3 wuji_hand_sdk_stream.py --hand-side left --serial-number SN002"
--serial-number to wuji_hand_sdk_stream.py when multiple Wuji hands are connected to the same host. The stream script forwards this to wujihandpy.Hand(serial_number=...) for unambiguous USB device selection.
5. Platform Integration
The Fearless Platform WebSocket endpoint at /api/teleop/ws accepts the Wuji agent registration handshake. The agent sends:
{
"role": "robot",
"session_id": "RC-XXXX-XXXX",
"member_id": "wuji-right",
"node_id": "wuji-right",
"node_role": "robot",
"device_type": "wuji_hand",
"capabilities": ["glove", "tactile", "telemetry"],
"source_host": "hostname"
}
After receiving type: "ready", the agent streams telemetry frames. The platform maps the Wuji telemetry into the GloveWorkbench UI panel, which renders:
- Real-time bend bars for all 5 fingers (from the normalized
bendsdict). - 24×32 tactile heatmap (from
pressure_map_24x32), downsampled to display resolution as needed. - 6-joint display values mapped from bends (thumb→j1, index→j2, middle→j3, ring→j4, little→j5, average→j6).
- Gesture state inferred from the overall close ratio (fist / half-close / open).
Episode recording
During an active recording session, every telemetry frame is archived as JSONL. Each row contains the full joints, pressure_map_24x32, imu, and emf payload alongside the session ID and millisecond timestamp. Episodes are available in the platform episode browser for playback, download, or direct ingestion into training pipelines.
downsample_matrix() function reduces the 24×32 map to a smaller display resolution when required. The full-resolution 24×32 array is always preserved in the JSONL episode archive regardless of display settings.
6. Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| wujihandpy import error | Package not installed or Python environment mismatch | Run pip install wujihandpy in the active virtual environment. Confirm with python3 -c "import wujihandpy; print(wujihandpy.__version__)". |
| No device found at 0x0483 | Wrong USB VID, hand not powered, or udev rules missing | Run lsusb to confirm the device appears. Pass the correct --usb-vid to the stream script. On Linux, add a udev rule for the VID/PID or run as root for initial testing. |
| read_joint_actual_position timeout | Hand firmware not responding or USB cable issue | Increase the timeout: edit the timeout=0.4 call in wuji_hand_sdk_stream.py to timeout=1.0. Re-seat the USB cable. The stream script catches exceptions and emits an error frame to keep the pipeline alive. |
| Agent fails to connect to platform WebSocket | Backend not running or wrong --backend URL | Confirm the Fearless backend is running: curl http://localhost:8000/health. Pass the correct URL with --backend ws://HOST:PORT. |
| pressure_map_24x32 always zeros in platform | Tactile hardware not present or firmware returning placeholder | This is expected if only the joint position hardware is connected. Use mock_wuji_stream.py to verify the heatmap rendering pipeline. For production use, confirm your Wuji firmware version includes tactile sensor support. |
lsusb, and the first 10 lines of JSONL output from wuji_hand_sdk_stream.py.
lsusb and your Wuji SDK version.