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

Adjusting Stream Rate

SDK Practice

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

Overview

The glove’s six data streams push at around 130 Hz by default. Most applications can’t use that much, and Subscription.set_rate(hz) lowers the rate at the device itself, saving bandwidth and CPU compared with decimating frames in your application.

One hard requirement: the device firmware must support rate control. The Wuji Glove needs v0.11.4 or later. The glove used here runs firmware 0.11.2, so what this page documents in full is the behavior when that requirement isn’t met, with the success path described from the API contract.

Matching examples: examples/python/wuji_glove/6.set_stream_rate.py and examples/c/wuji_glove/1_set_stream_rate.c.

For API and field definitions, see Hand Tracking.

The success path on this page isn’t verified on hardware, because the available device’s firmware doesn’t meet the requirement. The failure paths, evaluation order, parameter bounds, and the difference in behavior between the two bindings are all measured.

Prerequisites

Hardware

ItemRequirement
Wuji Glove1, firmware v0.11.4 or later
ConnectionEthernet

Check the firmware version:

print(glove.version().get())

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 1_set_stream_rate

Before You Start

Read Subscribing With Callbacks first. set_rate is a method on a subscription handle, so a subscription is required before calling it.

Data Flow

The checks run in order, and that order decides which error you see.

Which Streams Are Adjustable

CategoryStreamsset_rate
Rate entry pointsemf_poses, tactile, tactile_zones, imu_raw/{palm,thumb,index,middle,ring,pinky}Supported
Derived streamshand_joint_angles, hand_skeleton, tip_poses, imu_data/*, tactile_point_cloudRejected, they follow their source
Global streamstf, tf_staticNot supported

Lowering a source stream lowers every stream derived from it. Drop emf_poses to 30 Hz and hand_joint_angles, hand_skeleton, and tip_poses all follow, sparing you a proportional share of the solver cost.

Stream Rate Scope

This is the most commonly misread part of set_rate:

  • The setting applies to every subscriber of that stream, including other processes, Wuji Studio, and recorders
  • When the last subscription to that stream closes, the rate returns to the device default
  • A device reconnect resets it as well

So keeping a lower rate in effect means holding that subscription handle open. The example says as much in a comment: Keep the handle open for as long as you want its rate to stay in effect.

When several clients share a device, the rate you lower is one everyone feels. Coordinate before changing it in a shared setup.

Failure Paths

The device runs firmware 0.11.2, below the required v0.11.4.

Rate Entry Streams Stop at the Firmware Gate

WujiException: Unsupported operation: device 'WG1KA00XXXXXXXXX' runs firmware without rate control

emf_poses, tactile, and tactile_zones all report the same thing. Passing 0 to restore the default hits the same wall, because the firmware gate comes before the parameter.

Derived Streams Stop at the Entry-Point Check, Regardless of Firmware

WujiException: Unsupported operation: 'hand_joint_angles' is not a rate-control entry point
WujiException: Unsupported operation: 'hand_skeleton' is not a rate-control entry point
WujiException: Unsupported operation: 'tactile_point_cloud' is not a rate-control entry point

This check runs before the firmware gate, so newer firmware reports exactly the same error. Seeing this means changing which stream you call, not upgrading firmware.

Global Streams Report an Internal Error

WujiException: Internal error: Device '' not registered

Both tf and tf_static report this. It isn’t a friendly message, so recognize it as “I called set_rate on a global stream.” Global streams don’t support rate control at all, since they aggregate across devices and no single device can receive the command.

Parameter Range: uint16

The C signature reads:

WujiStatus wuji_sub_set_rate(struct WujiSub *sub, uint16_t frequency_hz,
                             uint16_t *out_actual_hz);

Python uses the same 16-bit unsigned type. Measured bounds:

InputResult
-1OverflowError: out of range integral type conversion attempted
65535Reaches the firmware gate, so the parameter is valid
65536OverflowError

Negatives and anything above 65535 are stopped in the binding layer and never reach the device.

The Two Bindings Handle Errors Differently

Given the same firmware-gate error, the two examples react in opposite ways:

BehaviorConsequence
Python exampleset_rate raises WujiException with no tryThe program crashes out, receiving no frames at all
C exampleChecks the return code, prints the error, and continuesThe subscription keeps working and collects its full 2 seconds

Measured output from the C example:

wuji_sub_set_rate failed: Unsupported operation: device 'WG1KA00XXXXXXXXX' runs firmware without rate control
emf_poses frame: 5 poses
emf_poses frame: 5 poses
emf_poses frame: 5 poses
...

The C approach is the sturdier one. Rate control is an optional optimization and shouldn’t take down a capture pipeline because a device runs older firmware. When adapting the Python example, wrap set_rate in a try:

try:
    actual = sub.set_rate(30)
    print(f"lowered to {actual} Hz")
except WujiException as e:
    print(f"rate control unavailable, running at the default rate: {e}")

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. Subscribe to a Rate-Control Entry Stream

Create an emf_poses subscription to get the handle used for rate control. See C example for the C on_emf_poses callback.

  • Python

  • C

sub = glove.emf_poses().subscribe()
struct WujiSub *sub = NULL;
WujiStatus st = wuji_glove_subscribe_emf_poses(dev, on_emf_poses, NULL, &sub);
/* If subscription fails, clean up the device and exit. */

3. Set the Rate and Read the Applied Value

Keep the subscription handle alive. The device may quantize the request, so use the returned rate. Handle a failed request before continuing, and don’t treat the requested rate as applied.

  • Python

  • C

actual = sub.set_rate(30)
print(f"Applied rate: {actual} Hz")
uint16_t actual = 0;
if (wuji_sub_set_rate(sub, 30, &actual) == WUJI_STATUS_OK) {
    printf("actual=%u Hz\n", actual);
} else {
    fprintf(stderr, "%s\n", wuji_last_error());
}

4. Receive Data and Restore the Default Rate

Python awaits frames in an async function. C receives them through a callback. Pass 0 to restore the default rate, then close the subscription and connection.

  • Python

  • C

try:
    for _ in range(5):
        frame = await sub.recv_async()
        print(len(frame.poses))
    print(sub.set_rate(0))
finally:
    sub.close()
    manager.disconnect_all()
uint16_t restored = 0;
wuji_sub_set_rate(sub, 0, &restored);
wuji_sub_close(sub);
wuji_dev_disconnect(dev);
wuji_dev_release(dev);
wuji_shutdown();

Running It

  • Python

  • C

python examples/python/wuji_glove/6.set_stream_rate.py
./build/glove/1_set_stream_rate

Python output on firmware 0.11.2, where the program stops at set_rate:

Connected: WG1KA00XXXXXXXXX
Traceback (most recent call last):
  ...
  File "6.set_stream_rate.py", line 43, in main
    actual = sub.set_rate(REQUESTED_HZ)
wuji_sdk.WujiException: Unsupported operation: device 'WG1KA00XXXXXXXXX' runs firmware without rate control

C output on the same device, which keeps receiving frames after the error:

  SN=WG1KA00XXXXXXXXX  Type=WujiGlove  Address=192.168.1.100:50001
wuji_sub_set_rate failed: Unsupported operation: device 'WG1KA00XXXXXXXXX' runs firmware without rate control
emf_poses frame: 5 poses
emf_poses frame: 5 poses
...

Measured on a right-hand Wuji Glove running firmware 0.11.2.

With firmware that meets the requirement, Python output looks like this:

Connected: <serial number>
emf_poses: requested 30 Hz, device applied 30 Hz
emf_poses frame at 30 Hz: 5 poses
...
restored to the device default: 0 Hz

device applied may differ from the request, because the device quantizes to a rate it supports.

Notes

Branch pointBehaviorWhat to do
Firmware below v0.11.4runs firmware without rate controlUpgrade the firmware, or catch the exception and run at the default rate
Called on a derived streamis not a rate-control entry pointCall its source stream instead, and derived streams follow
Called on a global streamInternal error: Device '' not registeredGlobal streams don’t support rate control
Negative or above 65535OverflowErrorThe parameter is a uint16
Device doesn’t support the requested rateReturns the quantized value it appliedUse the return value, don’t assume it equals the request
Last subscription to the stream closesThe rate returns to the device defaultHold the handle to keep the lower rate
Device reconnectsThe rate resetsCall it again after reconnecting
Another client subscribes to the same streamIt gets the lowered rate tooCoordinate before changing rates on a shared device

The older emf_poses_rate_divider API (Python glove.emf_poses_rate_divider(), C wuji_glove_get/set_emf_poses_rate_divider) has been removed. Upgrade to firmware v0.11.4 or later and use set_rate on an emf_poses subscription. One practical difference: the old divider persisted across reconnects, while set_rate doesn’t.

Adapting It

Use the Return Value, Not the Request

set_rate returns the rate the device actually applied. Sizing timeouts, buffers, or sampling windows from the requested value goes wrong as soon as quantization kicks in.

How Much Lowering the Rate Saves

It scales linearly. From 130 Hz to 30 Hz, both network traffic and callback count fall to about 23%. Recording benefits most directly: all eight streams with zstd measured about 72 MB per minute, which becomes roughly 17 MB per minute at 30 Hz.

Lowering emf_poses also saves hand-solver CPU, because derived streams follow.

Lowering the Rate Doesn’t Change Queue Capacity

The subscription queue still holds 1024 frames. At a lower rate those same 1024 frames cover a longer window, so a slow consumer actually gains headroom. See Subscribing Asynchronously.

Keeping a Low Rate Without Holding a Handle

There’s no mechanism that persists across connections today. The old emf_poses_rate_divider had that property and has been removed. The current approach is to hold the subscription handle inside your long-running service.