> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Subscription Mechanism

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

```python theme={null}
sub = glove.tactile().subscribe()
frame = await sub.recv_async()  # Wait for next data frame
```

### Sync Non-blocking Receive

```python theme={null}
sub = glove.tactile().subscribe()
frame = sub.recv()  # Returns None when no data available
```

### Callback Receive

> [tab] Python
>
> ```python theme={null}
> 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-blocking
> ```

> [tab] C
>
> ```c theme={null}
> void 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:

> [tab] Python
>
> ```python theme={null}
> sub = glove.tactile().subscribe_with_callback(callback=on_data)
> # ... after some time
> sub.close()
> ```

> [tab] C
>
> ```c theme={null}
> // 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:

> [tab] Python
>
> ```python theme={null}
> 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 default
> ```

> [tab] C
>
> ```c theme={null}
> WujiSub *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 `NULL` for `out_actual_hz` when 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_static` are aggregated by the SDK. Adjusting either raises `WujiException` in Python and returns `WUJI_STATUS_ERR_UNSUPPORTED` (-8) in C.
* Requires firmware with stream rate control support. Firmware without it raises `WujiException` in Python and returns `WUJI_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:

> [tab] Python
>
> ```python theme={null}
> 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()
> ```

> [tab] C
>
> ```c theme={null}
> 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](https://docs.wuji.tech/docs/en/wuji-hand/latest/sdk-reference.md) |
| Wuji Hand 2.1 (Beta) | [Wuji Hand 2 SDK Reference](https://docs.wuji.tech/docs/en/wuji-hand/beta1/sdk-reference.md) |
| Wuji Glove | [Wuji Glove data streams overview](https://docs.wuji.tech/docs/en/wuji-glove/latest/sdk-data-reference/index.md) |

For devices equipped with fingertip tactile sensors, see [Wuji Hand 2 Fingertip Tactile](https://docs.wuji.tech/docs/en/wuji-sdk/latest/wuji-hand-2-tactile.md) 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](https://docs.wuji.tech/docs/en/wuji-sdk/latest/data-reference.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.