HumansXドキュメントセンター English公式サイトへ ↗
ナレッジベース › Wuji Glove › マニュアル

Subscribing With Callbacks

SDK Practice

このページは現在英語版のみです。日本語でのご説明が必要な場合は HumansX までお問い合わせください。

Overview

The Wuji Glove exposes six device data streams at once: raw tactile, zoned tactile, EMF poses, finger joint angles, hand skeleton, and tactile point cloud. Add the two cross-device coordinate-transform streams tf and tf_static, and a single subscription pass gives you everything the glove produces in real time.

Callbacks fire on SDK background worker threads, so your main thread never blocks. This is the shortest path to glove data.

Matching examples: examples/python/wuji_glove/0.subscribe_callback.py and examples/c/wuji_glove/0_subscribe_callback.c.

For API and field definitions, see Hand Tracking, Tactile, and Coordinate Transforms.

Prerequisites

Hardware

ItemRequirement
Wuji Glove1
ConnectionEthernet
NetworkThe device uses a static IP. Configure your network adapter on the same subnet. The device used here listens on port 50001

This example only reads. It writes no parameters.

Software

ItemRequirement
Operating systemLinux or a Mac with Apple silicon, meeting the system requirements
Wuji SDKv2026.8.31 or later
Python3.10 or later
C toolchainCMake 3.16 or later, plus the extracted SDK package
  • Python

  • C

pip install wuji-sdk
LIB="libwuji_sdk_c.so"
if [ "$(uname -s)" = "Darwin" ]; then LIB="libwuji_sdk_c.dylib"; fi
cmake -S examples/c/wuji_glove -B build/glove \
  -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
  -DWUJI_SDK_LIB="${SDK}/lib/${LIB}"
cmake --build build/glove --target 0_subscribe_callback

Before You Start

Nothing. Power the device and connect the network. Hand model calibration isn’t a prerequisite for subscribing, though it does affect how well hand_joint_angles and hand_skeleton match your hand. See Hand Model Calibration.

Data Flow

Device streams subscribe through a device handle. Global streams take no device handle and are subscribed once per process.

Frame Structures

Measured on a glove resting on a desk, unworn, sampled for 3 seconds.

Six Device Streams

StreamRatePer frame
tactile132.3 Hzdata of length 744, one z-score per taxel
tactile_zones132.3 Hzpalm 308, thumb 50, index 45, middle 60, ring 54, pinky 55, totaling 572
emf_poses132.3 Hzposes of length 5, each with a pose (position plus orientation) and confidence
hand_joint_angles131.0 Hzfingers of length 5, each with angles (length 5, radians) and confidence
hand_skeleton131.0 Hzjoints of length 21, each with name, pose, and confidence. header.frame_id is r_wrist
tactile_point_cloud130.3 Hz526 points, point_stride of 16 bytes, field layout x(0) y(4) z(8) pressure(12)

Two Global Streams

StreamRatePer frame
tf871.7 Hz1 transform, waist → r_wrist
tf_static1.0 Hz2 transforms, r_wrist → r_hand_emf_tx and r_wrist → r_palm_imu_link

tf runs an order of magnitude faster than the device streams because several upstream sources drive it. Heavy work inside a callback shows up here first.

Why 744, 572, and 526 Add Up

-1.0 marks a taxel as invalid or masked. It isn’t a pressure reading. On this glove, 218 of the 744 channels report -1.0:

ArrayChannelsInvalidValid
tactile.data744218526
tactile_zones, six regions combined57246526
tactile_point_cloud points——526

All three converge on 526. tactile_zones includes only channels that map to a hand region, and the 172 unzoned channels happen to be invalid. tactile_point_cloud drops invalid channels entirely and emits only points that have coordinates.

Use the point cloud when you need to know where the force lands, zones when you need to know which finger, and tactile when you need raw channel alignment.

Skeleton Joint Names

The 21 joints in hand_skeleton follow the MediaPipe hand topology in a fixed order:

wrist
thumb_cmc         thumb_mcp         thumb_ip          thumb_tip
index_finger_mcp  index_finger_pip  index_finger_dip  index_finger_tip
middle_finger_mcp middle_finger_pip middle_finger_dip middle_finger_tip
ring_finger_mcp   ring_finger_pip   ring_finger_dip   ring_finger_tip
pinky_mcp         pinky_pip         pinky_dip         pinky_tip

joints[0] is wrist, and its pose is always the origin, because the whole skeleton chain uses the wrist as its reference frame. For where the wrist sits in the world, read the waist → r_wrist transform on the tf stream. The example printing wrist=[+0.000, +0.000, +0.000] is expected, not a data fault.

The Fifth Joint Angle

hand_joint_angles.fingers[i].angles always has length 5, but only the thumb uses all of it. Measured at rest:

Fingeranglesconfidence
Thumb0.785, -0.611, -0.028, -0.262, 1.6580.47
Index-0.102, -0.104, 0.065, 0.369, 0.0000.41
Middle0.030, -0.033, 0.048, 0.267, 0.0000.75
Ring-0.036, 0.167, 0.009, 0.553, 0.0000.74
Pinky-0.175, 0.235, 0.009, 0.236, 0.0000.47

For every finger but the thumb the fifth value stays 0 as alignment padding. confidence reflects solver quality and drifts between 0.4 and 0.75 at rest on an uncalibrated glove.

Integration Steps

These excerpts show the integration order. See the examples for argument parsing, signal handling, and full error handling.

1. Connect to the Target Glove

Replace serial_number with the target glove’s serial number. Reuse the connected device handle in later steps. C excerpts require wuji_sdk.h.

  • Python

  • C

from wuji_sdk import SdkManager
manager = SdkManager.instance()
glove = manager.connect(sn=serial_number, device_name="glove_0")
WujiInitOptions init = { .log_level = 3 };
if (wuji_init(&init) != WUJI_STATUS_OK) return 1;
WujiConnectTarget target = {
    .kind = WUJI_CONNECT_TARGET_KIND_SN, .value = serial_number,
};
struct WujiDevice *dev = NULL;
if (wuji_connect(&target, "glove_0", NULL, &dev) != WUJI_STATUS_OK) {
    wuji_shutdown();
    return 1;
}

2. Define a Frame Handler

This tactile example reads only the current frame and filters invalid channels represented by negative values. In C, check the frame kind first. The frame pointer is valid only during the callback.

  • Python

  • C

def on_tactile(frame):
    valid = [v for v in frame.data if v >= 0.0]
    print(frame.header.seq, max(valid, default=0.0))
static void on_tactile(WujiFrameKind kind, const WujiTactileFrame *f, void *ud) {
    if (kind != WUJI_FRAME_KIND_OK || !f) return;
    float peak = 0.0f;
    for (size_t i = 0; i < f->data_len; i++) {
        if (f->data[i] >= 0.0f && f->data[i] > peak) peak = f->data[i];
    }
    printf("seq=%u peak=%.3f\n", f->header.seq, peak);
}

3. Register the Callback and Keep the Subscription Handle

The SDK calls the frame handler on a background thread. Keep one handle per stream, and close each handle on exit. Subscribe to global streams such as tf through the manager or global C API, once per process.

  • Python

  • C

sub = glove.tactile().subscribe_with_callback(on_tactile)
struct WujiSub *sub = NULL;
WujiStatus st = wuji_glove_subscribe_tactile(dev, on_tactile, NULL, &sub);
/* On failure, report wuji_last_error(), clean up, and exit. */

4. Close the Subscription and Disconnect

Keep the main thread running to receive data. On exit or an exception, close the subscription before disconnecting the device so callbacks don’t access released resources.

  • Python

  • C

import time
try:
    time.sleep(10)
finally:
    sub.close()
    manager.disconnect_all()
wuji_sub_close(sub);
wuji_dev_disconnect(dev);
wuji_dev_release(dev);
wuji_shutdown();

Running It

  • Python

  • C

python examples/python/wuji_glove/0.subscribe_callback.py --sn WG1KA00XXXXXXXXX
./build/glove/0_subscribe_callback

On the Python side, all six streams interleave without throttling:

Found 1 device(s)
  SN=WG1KA00XXXXXXXXX, Type=DeviceType.WujiGlove, Address=192.168.1.101:50001
Connected: WG1KA00XXXXXXXXX (FW=0.11.2, right)
Subscribed to 6 streams. Ctrl+C to stop.

[glove_0][Tactile] max=0.3
[glove_0][Zones] palm=-0.1 thumb=-0.1
[glove_0][EmfPoses] thumb=[+0.067, +0.000, -0.059]
[glove_0][JointAngles] conf=['0.47', '0.41', '0.74', '0.71', '0.47']
[glove_0][Skeleton] wrist=[+0.000, +0.000, +0.000] joints=21
[glove_0][PointCloud] points=526 frame=r_wrist

Five seconds produced 3201 lines, about 640 per second, consistent with six streams at roughly 130 Hz each.

The C side throttles printing to every 30th frame and adds the two global streams:

Wuji SDK version: 2026.8.31
Found 6 device(s)
  SN=WG1KA00XXXXXXXXX  Type=WujiGlove  Address=192.168.1.101:50001
  ...
Connected: WG1KA00XXXXXXXXX as glove_0
Subscribed to 8 streams. Ctrl+C to stop.

[glove_0][Tactile] seq=343375 taxels=744 max=0.308 (#1)
[glove_0][Zones] seq=343375 palm=-1.000 (#1)
[glove_0][EmfPoses] seq=343375 pos=[+0.067,+0.000,-0.059] (#1)
[glove_0][JointAngles] seq=343378 conf=[0.47,0.41,0.74,0.71,0.47] (#1)
[glove_0][Skeleton] seq=343378 wrist=[+0.000,+0.000,+0.000] joints=21 (#1)
[glove_0][PointCloud] seq=343379 points=526 frame=r_wrist (#1)
[global][tf] count=1 first=waist->r_wrist xyz=[+0.100,-0.150,+0.050] (#1)
[global][tf_static] count=2 first=r_wrist->r_hand_emf_tx xyz=[+0.002,-0.048,-0.055] (#1)

This sample output shows a right-hand Wuji Glove resting on a desk, unworn, using its default IP address, 192.168.1.101. Use the address reported by the scan.

Notes

Branch pointBehaviorWhat to do
Device already in useRaises Session already exists on deviceWait for the previous session’s heartbeat to time out, or confirm no other client holds the device
Several gloves on the networkBoth examples iterate every glove and connect to eachPass --sn, described under Adapting It
Non-glove devices on the networkFiltered out by device_typeAlready handled. Hand 2 and unknown devices are excluded
Subscription backlogThe C callback receives WUJI_FRAME_KIND_LAGKeep callbacks light
Stream ends or errorsThe C side receives WUJI_FRAME_KIND_END or WUJI_FRAME_KIND_ERROROn ERROR, call wuji_last_error() for the reason
--hand-model-path without a named userThe setting is rejectedA custom hand model needs an SDK user first. See SDK User Management

C callbacks fire on dedicated worker threads, and the frame pointer is valid only for the duration of the callback. Heap arrays such as data, joints, and poses are freed as soon as it returns, so never retain the pointer.

Adapting It

The Example’s avg() Goes Negative on Invalid Channels

This is the one thing worth watching in this example. The Python example averages each zone with avg():

def avg(lst: list[float]) -> float:
    return sum(lst) / len(lst) if lst else 0.0

-1.0 lands straight in the numerator. With 20 of the palm’s 308 channels invalid, the output reads palm=-0.1 — a negative reading from a glove under no load at all. The C example prints palm[0], so when that first channel happens to be invalid it shows -1.000, which is exactly what the measurement produced.

Filter negatives first on both sides:

def avg(lst: list[float]) -> float:
    valid = [v for v in lst if v >= 0.0]
    return sum(valid) / len(valid) if valid else 0.0

After filtering, the same frame gives a palm mean of +0.015. The share of invalid channels varies by glove, so don’t hard-code a count.

Name an SN When Several Devices Share a Network

Both examples iterate every glove they find and connect to each one. On a shared network, limit that with --sn:

python examples/python/wuji_glove/0.subscribe_callback.py --sn WG1KA00XXXXXXXXX

The C example takes no command-line arguments, so compare list[i].serial_number in the source before connecting.

Subscribe Only to What You Need

All six streams open means about 640 callbacks per second at rest. For tactile analysis keep tactile or tactile_point_cloud, for hand pose keep hand_skeleton, and callback pressure drops immediately.

Keep Callbacks Light

Each subscription gets its own worker thread. Callbacks within one subscription run serially and in order, while different subscriptions may run concurrently, so guard any state you share across them. Move file writes, algorithms, and lock waits to a consumer thread, and copy only the minimum inside the callback.

Switch to Async

If callbacks don’t suit you, use subscribe() with recv() and control the pacing yourself. See Subscribing Asynchronously.