> ## 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

<Tabs>
  <Tab title="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>

  <Tab title="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
    ```
  </Tab>
</Tabs>

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:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sub = glove.tactile().subscribe_with_callback(callback=on_data)
    # ... after some time
    sub.close()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // Close from outside the subscription's own callback — close joins the worker thread
    wuji_sub_close(sub);
    ```
  </Tab>
</Tabs>

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:

<Tabs>
  <Tab title="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>

  <Tab title="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
    ```
  </Tab>
</Tabs>

* 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/*` | `hand_joint_angles`, `hand_skeleton`, `tip_poses`, `tactile_point_cloud`, `tactile_residual`, `tactile_binary`, `imu_data/*` |
| 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:

<Tabs>
  <Tab title="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>

  <Tab title="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);
    ```
  </Tab>
</Tabs>

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) | [Fingertip Tactile](/en/docs/wuji-sdk/wuji-hand-2-tactile/) and [Wuji Hand 2 SDK Reference](/en/docs/wuji-hand/latest/sdk-reference/) |
| Wuji Hand 2.1 (Beta) | [Wuji Hand 2 SDK Reference](/en/docs/wuji-hand/latest/sdk-reference/) |
| Wuji Glove | [Wuji Glove data streams overview](/en/docs/wuji-glove/sdk-data-reference/) |

## 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](/en/docs/wuji-sdk/data-reference/).


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