HumansXDocumentation Center 日本語Website ↗
Knowledge Base › Software › Wuji SDK

Wuji Hand 2 Fingertip Tactile

SDK Practice

Overview

Wuji Hand 2 fingertip tactile sensors describe their data format through metadata. Read each finger’s info metadata before subscribing, then use its format JSON to decode the data frame. The field describes the contact layout, units, and mounting pose.

The application must read the metadata before decoding data frames. When firmware changes the point count, units, or mounting pose, the decoder can use the updated metadata.

This capability depends on the hardware configuration. Read the sensor metadata to confirm that the connected device provides the required interfaces. The test device used for this page does not provide the capability. This page documents capability detection, failure behavior, and handling. For supported devices, follow the interface definition and product specification.

Matching examples: examples/python/wuji_hand_2/3.fingertip_typed.py and examples/c/wuji_hand_2/3_fingertip.c.

For API and field definitions, see Wuji Hand 2 Fingertip Tactile.

Prerequisites

Hardware

ItemRequirement
Wuji Hand 21, equipped with fingertip tactile sensors and the corresponding metadata and data interfaces
Firmwarev2.1.0 or later
ConnectionEthernet

Read the contact count, fields, units, and output rate from the connected device’s sensor metadata. Don’t infer them from the hardware model.

Software

ItemRequirement
Operating systemLinux or a Mac with Apple silicon, meeting the system requirements
Wuji SDKv2026.7.21 or later
Python3.8 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_hand_2 -B build/hand2 \
  -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
  -DWUJI_SDK_LIB="${SDK}/lib/${LIB}"
cmake --build build/hand2 --target 3_fingertip

Before You Start

Confirm the device has this capability. The next section shows how.

Data Flow

Read the sensor metadata before subscribing to the data stream. The data frame does not contain a complete format description, so it cannot be decoded correctly without the metadata.

Detect Device Capability

The most reliable runtime check reads the resource list. Don’t infer it from a version number:

has_fingertip = any("fingertip" in t.path for t in hand.topics())
print("fingertip tactile:", "available" if has_fingertip else "not provided by this device")

Measured on a device without it, topics() holds no fingertip entries at all. A device with it lists five fingertip/<finger>/data topics.

This check needs no try/except and doesn’t depend on mapping hardware or firmware versions.

The format Field

info.format is a JSON string that describes the data layout. The decoder is driven by its fields. The key fields:

FieldPurpose
vContract version. The example accepts only 1
encodingThe example accepts only point_array
point_countContact count. 40 on the thumb, 34 elsewhere
point_strideBytes per contact
point_fieldsPer-contact field definitions: name, type, offset, scale
aggregate_strideBytes in the aggregate section
aggregate_fieldsAggregate fields. fx, fy, fz, and temperature are always present
positionsEach contact’s position in the sensor module’s own frame
point_rpyEach contact’s force-axis orientation
base_xyz / base_rpyThe sensor module’s pose relative to the finger’s tip_sensor_frame
aggregate_xyz / aggregate_rpyThe aggregate application point and force-axis orientation

The field-type to unpack-format mapping is defined in one table. Unknown types raise an error:

FIELD_FMT = {"i8": "<b", "u8": "<B", "i16": "<h", "u16": "<H",
             "i32": "<i", "u32": "<I", "f32": "<f"}

Unknown field types raise an error. An incorrect field mapping causes the frame to fail decoding.

Self-Checking the Data Length

Compute the expected length before decoding and fail outright on a mismatch:

expect = point_count * point_stride + aggregate_stride
if len(data) != expect:
    raise ValueError(f"data length {len(data)} != expected {expect}")

This check surfaces an info-to-data version mismatch on the very first frame.

Units Change With Firmware, So Read Them From format

Starting with firmware v2.4.0, per-contact force is normalized to the sensor’s full scale: 0 to 1 on the normal axis, −1 to 1 on the tangential axes, clipped beyond that. Earlier firmware reports newtons. The aggregate force and temperature stay in newtons and degrees Celsius on both.

So pick your contact threshold from the declared unit rather than hard-coding it:

CONTACT_BY_UNIT = {"normalized": 0.02, "N": 0.2}

After a firmware upgrade, re-read info so the decoder picks up the new scale and unit.

Placing Contacts on the Hand Model

positions and point_rpy live in the sensor module’s own frame, and base_xyz with base_rpy give that frame’s pose relative to the finger’s tip_sensor_frame. So contact i sits in finger coordinates at:

R(base_rpy) @ positions[i] + base_xyz

Its force-axis rotation is R(base_rpy) @ R(point_rpy[i]).

The aggregate can be recovered as a true wrench: the force is R(base_rpy) @ R(aggregate_rpy) @ (fx, fy, fz), the application point is R(base_rpy) @ aggregate_xyz + base_xyz, and the moment about the link origin is p × f.

These three mounting-pose keys were added to the contract together. The example branches on that: all three missing means older firmware where position information isn’t available, while only some missing means the metadata itself is broken, which raises. Firmware without aggregate_xyz reports neither an application point nor a moment. Do not calculate these values when the field is missing.

The device reports poses for its own handedness, so the same code is correct on left and right hands.

SDK Version Compatibility

This change affects interface compatibility.

SDK versionHow to read info
v2026.9.22 and laterhand.fingertip_thumb_info().get(), one accessor per finger, no argument
v2026.8.31 and earlierhand.get_fingertip_info(finger), where finger is an integer 0 to 4

The C side matches, where wuji_hand_2_get_fingertip_thumb_info(dev, &out) replaces wuji_hand_2_get_fingertip_info(dev, finger, &out).

There’s no compatibility shim. Measured, calling the new interface on v2026.8.31:

AttributeError: 'builtins.WujiHand2' object has no attribute 'fingertip_thumb_info'

On the C side it fails at link time on the removed symbol. The struct layout didn’t change, so an old binary reports the missing symbol during linking.

The old interface takes an integer, and the binding layer rejects a string:

TypeError: argument 'finger': 'str' object cannot be interpreted as an integer

Where 0=thumb, 1=index, 2=middle, 3=ring, 4=pinky.

The examples in the repository follow the newest interface. Running them against an older SDK raises AttributeError while reading info. That isn’t a device problem.

Measured Behavior on Unsupported Devices

Device details: firmware_version = 1.3.0 and hw_version = HwVersion(major=0, minor=2, patch=0).

All four paths fail, with errors from different layers:

CallMeasured error
hand.get_fingertip_info(0)RuntimeError: sdk error: Path not found: fingertip.info_req
hand.fingertip_thumb_data().subscribe()WujiException: Resource not found: fingertip/thumb/data
hand.tactile_calibrate()RuntimeError: sdk error: Path not found: thumb/tactile/tactile_calibrate
hand.tactile_status('thumb')RuntimeError: sdk error: Path not found: thumb/tactile/tactile_query_status

All five fingers report the same error from get_fingertip_info, and topics() holds no fingertip entries either.

Note that the Python object does have fingertip_thumb_data, get_fingertip_info, tactile_calibrate, and tactile_status. A method existing doesn’t mean the device supports it, because the SDK’s interface surface is defined per product while what works depends on the device’s resource list.

Integration Steps

The following thumb example connects, reads the format, receives data, and releases resources. See Python example and C example for complete five-finger implementations.

The excerpts reuse helpers and types from Python example and C example: Python’s fetch_format and make_decoder, and C’s finger_ctx_t, parse_format, on_fingertip_data, snapshot, and status_line. These helpers parse formats, validate frames, and display results. They aren’t SDK APIs.

1. Connect to a Specific Device

Follow callback subscription practice for SDK initialization and connection. Select the target SN when several devices share the network. The Python excerpts use the connected hand and manager. C excerpts use the connected dev.

2. Read the Thumb Format and Prepare the Decoder

Read info, save its digest, and build a decoder from the field types, offsets, and scales declared by format. Subscribe to that finger only after this succeeds. On failure, skip the finger, or exit the single-finger program and disconnect.

  • Python

  • C

sub = None
try:
    fmt, digest = fetch_format(hand, 0)
    decode = make_decoder(fmt)
except Exception:
    manager.disconnect_all()
    raise
struct WujiSub *sub = NULL;
finger_ctx_t ctx = {0};
ctx.name = "thumb";
pthread_mutex_init(&ctx.lock, NULL);
ctx.lock_ready = 1;
WujiFingertipSensorInfo info = {0};
if (wuji_hand_2_get_fingertip_thumb_info(dev, &info) != WUJI_STATUS_OK) {
    fprintf(stderr, "%s\n", wuji_last_error());
    goto cleanup;
}
ctx.digest = info.digest;
if (parse_format(info.format, &ctx) != 0) goto cleanup;
ctx.has_info = 1;

fetch_format(hand, 0) calls hand.fingertip_thumb_info().get(). C’s parse_format also prepares the length, units, and fields used by later callbacks. Both bindings read the layout from the format instead of hard-coding contact counts or byte offsets.

3. Subscribe and Process Frames

Python reads the subscription queue in the main loop. C receives frames through a background callback. Check info_digest against the saved digest before decoding. Don’t decode a zero digest. If the digest changes, reread info and rebuild the decoder. The frame length must match the format declaration.

  • Python

  • C

import time

try:
    sub = hand.fingertip_thumb_data().subscribe()
    end = time.monotonic() + 15
    while time.monotonic() < end:
        frame = sub.recv()
        if frame is None:
            time.sleep(0.01)
            continue
        if frame.info_digest == 0:
            continue
        if frame.info_digest != digest:
            fmt, digest = fetch_format(hand, 0)
            decode = make_decoder(fmt)
            continue
        try:
            points, aggregate = decode(bytes(frame.data))
        except ValueError:
            continue
        print(aggregate["fx"], aggregate["fy"], aggregate["fz"])
finally:
    if sub is not None:
        sub.close()
    manager.disconnect_all()
if (wuji_hand_2_subscribe_fingertip_thumb_data(
        dev, on_fingertip_data, &ctx, &sub) != WUJI_STATUS_OK) {
    fprintf(stderr, "%s\n", wuji_last_error());
    goto cleanup;
}
while (!g_stop) {
    snapshot_t snap;
    snapshot(&ctx, &snap);
    status_line(&ctx, &snap);
    usleep(REFRESH_US);
}

The C excerpt reuses g_stop, signal handling, and REFRESH_US from C example. on_fingertip_data checks frame status, digest, and length, then copies data under a mutex. The main thread reads it through snapshot. If the digest changes, C example discards the frame and displays status. Close the subscription, reread the format, and subscribe again.

4. Close the Subscription and Disconnect

Close the subscription and wait for its callback worker to finish before freeing the callback context and device. Python handles normal exit, exceptions, and interruption in the previous step’s finally block. C uses the following cleanup block on success and failure.

cleanup:
if (sub) wuji_sub_close(sub);
if (ctx.lock_ready) pthread_mutex_destroy(&ctx.lock);
wuji_dev_disconnect(dev);
wuji_dev_release(dev);
wuji_shutdown();

Running It

  • Python

  • C

python examples/python/wuji_hand_2/3.fingertip_typed.py
./build/hand2/3_fingertip

With the capability present, the Python example redraws in place at 100 Hz: one status line per finger (aggregate force, temperature, contact count, and the strongest contact’s position), followed by a table of fx / fy / fz per contact. Each round drains the subscription queue first so only the newest frame shows. It runs 15 seconds by default.

On a device without the capability, all four paths fail:

get_fingertip_info(0) [thumb]: RuntimeError: sdk error: Path not found: fingertip.info_req
get_fingertip_info(1) [index]: RuntimeError: sdk error: Path not found: fingertip.info_req
get_fingertip_info(2) [middle]: RuntimeError: sdk error: Path not found: fingertip.info_req
get_fingertip_info(3) [ring]: RuntimeError: sdk error: Path not found: fingertip.info_req
get_fingertip_info(4) [pinky]: RuntimeError: sdk error: Path not found: fingertip.info_req

fingertip_thumb_data().subscribe(): WujiException: Resource not found: fingertip/thumb/data
tactile_calibrate(): RuntimeError: sdk error: Path not found: thumb/tactile/tactile_calibrate
tactile_status('thumb'): RuntimeError: sdk error: Path not found: thumb/tactile/tactile_query_status

fingertip entries in topics(): none

These are real results, on firmware 1.3.0, hardware version 0.2.0, and SDK v2026.8.31.

Notes

Branch pointBehaviorWhat to do
Device without fingertip tactile sensorsEvery fingertip path reports Path not found or Resource not foundThis hardware doesn’t provide the capability
Firmware older than v2.1.0SameUpgrade the firmware
SDK older than v2026.7.21The interface doesn’t existUpgrade the SDK
SDK and example versions mismatchedAttributeError: ... has no attribute 'fingertip_thumb_info'See the version table above
One finger’s info can’t be readThat finger is skipped, the rest continueNo sensor installed, older sensor firmware, or still gathering info after power-on
No finger’s info can be readThe example exitsDetect the capability with topics() first
Firmware older than v2.4.0Per-contact force is in newtons, not normalizedRead the unit from format instead of assuming
Firmware missing aggregate_xyzNo application point and no moment reportedDo not calculate an application point
format has only some mounting-pose keysRaisesThe metadata itself is broken, so don’t degrade gracefully

One finger failing to report info is normal, not a fault. The example skips it and decodes the rest, stopping only when all five fail.

Recalibration and Status Queries

On a device with the capability, hand.tactile_calibrate() resets all five fingertips to a fresh zero baseline. The C side is wuji_hand_2_tactile_calibrate(dev).

The sensor surfaces must be unloaded during calibration. Nothing touching them, nothing pressing against them. Afterward, hand.tactile_status(finger) reports each sensor’s model and calibration state, which confirms it finished.

Every frame summary on the joint_diagnostics stream carries a fingertip tactile online bitmap, so you can monitor which fingertips are online without polling.

Configuration and Adaptation

Lower the Stream Rate

Five contact arrays at 100 Hz add up. fingertip/*/data is a rate-control entry point, so a subscription handle’s set_rate() lowers it, which needs firmware v2.3.0 or later. See Wuji Hand 2 Adjusting Stream Rate.

Re-Read info After a Firmware Upgrade

scale, unit, and the mounting poses in format can all change with firmware. Skip the re-read and your decoder parses new data against the old contract, giving wrong numbers with no error. Read info right after every connection and don’t cache it to disk.

When You Only Care Whether Something Is Touched

If you don’t need three-axis force per contact, the aggregate section’s fx/fy/fz plus a threshold is enough, with no need to decode individual contacts. The aggregate is in newtons on every firmware and isn’t affected by normalization.

Tolerate Per-Finger Failure

All five fingers report info independently. In production code, handle failure per finger so one finger’s problem doesn’t break the whole pipeline. That’s how the example was changed.