Data Subscription Mechanism
Developer Guide
Wuji SDK uses a subscription mechanism to receive device data. Each data stream corresponds to a sensor or computed result, accessed through semantic APIs.
Subscription Modes
Async Receive
sub = glove.tactile().subscribe()
frame = await sub.recv_async() # Wait for next data frame Sync Non-blocking Receive
sub = glove.tactile().subscribe()
frame = sub.recv() # Returns None when no data available Callback Receive
Python
C
def on_data(frame):
print(f"Max: {max(frame.data):.1f}")
sub = glove.tactile().subscribe_with_callback(callback=on_data)
# Receives automatically in background, non-blockingvoid on_data(WujiFrameKind kind, const WujiTactileFrame *frame, void *user) {
if (kind != WUJI_FRAME_KIND_OK) return;
float max = 0.0f;
for (size_t i = 0; i < frame->data_len; i++) {
if (frame->data[i] > max) max = frame->data[i];
}
printf("Max: %.1f\n", max);
}
WujiSub *sub = NULL;
wuji_glove_subscribe_tactile(glove, on_data, NULL, &sub);
// A background thread receives automatically, non-blocking Every C callback carries a WujiFrameKind. WUJI_FRAME_KIND_OK means the frame is readable, _LAG means the consumer fell behind and carries a lag count, and _END and _ERROR are terminal. The frame pointer is valid only for the duration of the callback, so copy anything you need to keep.
Closing Subscriptions
Callback subscriptions must be manually closed:
Python
C
sub = glove.tactile().subscribe_with_callback(callback=on_data)
# ... after some time
sub.close()// Close from outside the subscription's own callback — close joins the worker thread
wuji_sub_close(sub); All subscriptions on a device are automatically closed when the device is disconnected. After a terminal frame the stream delivers no more data, but the handle still needs closing.
Adjusting the Stream Output Rate
Adjust the firmware push rate of a subscribed stream. The call returns the rate the device applies:
Python
C
sub = glove.emf_poses().subscribe()
actual = sub.set_rate(30) # the device may quantize the request
print(f"requested 30 Hz, applied {actual} Hz")
sub.set_rate(0) # pass 0 to restore the device defaultWujiSub *sub = NULL;
wuji_glove_subscribe_emf_poses(glove, on_poses, NULL, &sub);
uint16_t actual = 0;
wuji_sub_set_rate(sub, 30, &actual); // the device may quantize the request
printf("requested 30 Hz, applied %u Hz\n", actual);
wuji_sub_set_rate(sub, 0, &actual); // pass 0 to restore the device default - Call it on an open subscription handle. The setting stays in effect for the life of the subscription.
- Rates can only go down, not above the native rate. The device quantizes the request to a supported rate (an integer division of the native rate), and the applied rate is what counts. In C, pass
NULLforout_actual_hzwhen the applied rate isn’t needed. - The rate belongs to the stream, not to the handle: all subscribers of the stream share the setting, and the last write wins.
- The setting doesn’t persist: the rate resets to the device default full rate when the last subscription to the stream is closed or the device reconnects.
- Only streams produced directly by the device support rate control. Derived streams follow their source rate automatically, and global resources such as
tf_staticare aggregated by the SDK. Adjusting either raisesWujiExceptionin Python and returnsWUJI_STATUS_ERR_UNSUPPORTED(-8) in C. - Requires firmware with stream rate control support. Firmware without it raises
WujiExceptionin Python and returnsWUJI_STATUS_ERR_UNSUPPORTED(-8) in C.
Rate-adjustable streams and the derived streams that follow them, per device:
| Device | Rate-adjustable streams | Derived streams that follow |
|---|---|---|
| Wuji Glove | emf_poses, tactile, tactile_zones, imu_raw/palm | hand_joint_angles, hand_skeleton, tip_poses, tactile_point_cloud, tactile_residual, tactile_binary, imu_data/palm |
| Wuji Hand 2 | joint_states, joint_diagnostics, imu, fingertip/*/data | — |
joint_states and joint_diagnostics on the Wuji Hand 2 share one device stream, so adjusting either changes both.
For complete examples, see examples/python/wuji_glove/6.set_stream_rate.py, examples/python/wuji_hand_2/4.set_stream_rate.py, examples/c/wuji_glove/1_set_stream_rate.c, and examples/c/wuji_hand_2/4_set_stream_rate.c.
Cross-device Merged Subscription
Global resources automatically aggregate data from all connected devices. Global resources in the current version:
| Resource | Type | Description |
|---|---|---|
tf_static | FrameTransforms | Static coordinate transforms (e.g., wrist → emf_tx) |
tf | FrameTransforms | Dynamic coordinate transforms (e.g., waist → wrist) |
Query the full list of global resources at runtime, then subscribe:
Python
C
manager = SdkManager.instance()
# List all global resources
for topic in manager.global_topics():
print(f"{topic.path} (sub={topic.can_sub})")
# Subscribe to a global resource — data is merged from all connected devices
sub = manager.tf_static().subscribe()
transforms = await sub.recv_async()void on_tf(WujiFrameKind kind, const WujiFrameTransforms *frames, void *user) {
if (kind != WUJI_FRAME_KIND_OK) return;
for (size_t i = 0; i < frames->transforms_len; i++) {
printf("%s -> %s\n", frames->transforms[i].parent_frame_id,
frames->transforms[i].child_frame_id);
}
}
// List all global resources
WujiTopic *topics = NULL;
size_t n = 0;
wuji_list_global_topics(&topics, &n);
for (size_t i = 0; i < n; i++) {
printf("%s\n", topics[i].path);
}
wuji_topic_array_free(topics, n);
// Subscribe to a global resource — data is merged from all connected devices
WujiSub *sub = NULL;
wuji_subscribe_tf_static(on_tf, NULL, &sub); Differences between device resources and global resources:
| Dimension | Device Resource | Global Resource |
|---|---|---|
| Python access | glove.tactile().subscribe() | manager.tf_static().subscribe() |
| C access | wuji_glove_subscribe_tactile(dev, ...) | wuji_subscribe_tf_static(...), no device handle needed |
| Data source | Single device | Merged from all connected devices |
| Query API | device.topics() / wuji_dev_list_topics() | manager.global_topics() / wuji_list_global_topics() |
Available Data Streams
Index by Product
| Product | Data streams and subscription APIs |
|---|---|
| Wuji Hand 2.2 (Beta) | Wuji Hand 2 SDK Reference |
| Wuji Hand 2.1 (Beta) | Wuji Hand 2 SDK Reference |
| Wuji Glove | Wuji Glove data streams overview |
For devices equipped with fingertip tactile sensors, see Wuji Hand 2 Fingertip Tactile for subscription and decoding. Read the connected device’s sensor metadata to confirm that the interfaces are available.
Interface List
| Operation | Python | C |
|---|---|---|
| Open a typed subscription | resource.subscribe(), resource.subscribe_with_callback(callback=...) | wuji_<device>_subscribe_<stream>() |
| Subscribe to any stream by path | device.subscribe(path) | wuji_sub_open() |
| Wait for the next frame | await sub.recv_async() | No equivalent — frames arrive through the callback |
| Poll without blocking | sub.recv() | No equivalent — frames arrive through the callback |
| Close a subscription | sub.close() | wuji_sub_close() |
| Adjust the output rate | sub.set_rate(frequency_hz) | wuji_sub_set_rate() |
| List device streams | device.topics() | wuji_dev_list_topics() |
| List global streams | manager.global_topics() | wuji_list_global_topics() |
| Subscribe to global transforms | manager.tf().subscribe(), manager.tf_static().subscribe() | wuji_subscribe_tf(), wuji_subscribe_tf_static() |
Python offers three ways to take frames: async, sync non-blocking, and callback. C offers callbacks only, and the frame pointer is valid only for the duration of the callback. For memory ownership and callback constraints in C, see the C Interface Reference.
ドキュメント