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

# 数据订阅机制

Wuji SDK 通过订阅机制接收设备数据。每个数据流对应一种传感器或计算结果，通过语义化 API 访问。

## 订阅模式

### 异步接收

```python theme={null}
sub = glove.tactile().subscribe()
frame = await sub.recv_async()  # 等待下一帧数据
```

### 同步非阻塞接收

```python theme={null}
sub = glove.tactile().subscribe()
frame = sub.recv()  # 无数据时返回 None
```

### 回调接收

<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)
    # 后台自动接收，不阻塞主线程
    ```
  </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);
    // 后台线程自动接收，不阻塞主线程
    ```
  </Tab>
</Tabs>

C 的回调每帧带一个 `WujiFrameKind`：`WUJI_FRAME_KIND_OK` 表示帧可读，`_LAG` 表示消费端落后并携带丢帧数，`_END` 与 `_ERROR` 是终止帧。帧指针仅在回调期间有效，需要保留的数据必须在回调内拷贝。

## 关闭订阅

回调订阅需要手动关闭：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sub = glove.tactile().subscribe_with_callback(callback=on_data)
    # ... 使用一段时间后
    sub.close()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // 在订阅自身的回调之外关闭，close 会 join 后台线程
    wuji_sub_close(sub);
    ```
  </Tab>
</Tabs>

断开设备连接时，该设备上的所有订阅会自动关闭。收到终止帧后流不再有新数据，订阅句柄仍需关闭。

## 调节订阅流输出率

调节订阅流的固件推送频率，返回设备实际生效的输出率：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sub = glove.emf_poses().subscribe()

    actual = sub.set_rate(30)   # 设备量化后返回实际生效值
    print(f"requested 30 Hz, applied {actual} Hz")

    sub.set_rate(0)             # 传 0 恢复设备默认满速
    ```
  </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);   // 设备量化后写回实际生效值
    printf("requested 30 Hz, applied %u Hz\n", actual);

    wuji_sub_set_rate(sub, 0, &actual);    // 传 0 恢复设备默认满速
    ```
  </Tab>
</Tabs>

* 需要在未关闭的订阅句柄上调用，订阅存续期间设置持续生效。
* 只能调低，无法超过原生输出率。设备把请求量化到支持的档位（原生输出率的整数分频），以实际生效值为准。C 侧不需要实际生效值时，`out_actual_hz` 传 `NULL` 即可。
* 输出率属于流而非句柄：同一流的所有订阅共享该设置，后写生效。
* 设置不持久化：该流的最后一个订阅关闭或设备重连后，输出率恢复设备默认满速。
* 仅设备直接产出的流可调。派生流跟随源流自动降频，`tf_static` 等全局资源由 SDK 聚合，对二者调节时 Python 抛 `WujiException`，C 返回 `WUJI_STATUS_ERR_UNSUPPORTED`（-8）。
* 需要支持输出率调节的固件。固件不支持时，Python 抛 `WujiException`，C 返回 `WUJI_STATUS_ERR_UNSUPPORTED`（-8）。

各设备的可调流与跟随的派生流：

| 设备 | 可调流 | 跟随的派生流 |
| - | - | - |
| 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` | — |

Wuji Hand 2 的 `joint_states` 与 `joint_diagnostics` 共享同一条设备流，调节任意一个，两者同时生效。

完整示例见 `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` 与 `examples/c/wuji_hand_2/4_set_stream_rate.c`。

## 跨设备合并订阅

全局资源会自动聚合所有已连接设备的数据。当前版本的全局资源：

| 资源 | 类型 | 说明 |
| - | - | - |
| `tf_static` | `FrameTransforms` | 静态坐标变换（如 wrist → emf\_tx） |
| `tf` | `FrameTransforms` | 动态坐标变换（如 waist → wrist） |

运行时查询完整的全局资源列表并订阅：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    manager = SdkManager.instance()

    # 查看所有全局资源
    for topic in manager.global_topics():
        print(f"{topic.path} (sub={topic.can_sub})")

    # 订阅全局资源——数据来自所有已连接设备的合并
    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);
        }
    }

    // 查看所有全局资源
    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);

    // 订阅全局资源——数据来自所有已连接设备的合并
    WujiSub *sub = NULL;
    wuji_subscribe_tf_static(on_tf, NULL, &sub);
    ```
  </Tab>
</Tabs>

全局资源与设备资源的区别：

| 维度 | 设备资源 | 全局资源 |
| - | - | - |
| Python 访问方式 | `glove.tactile().subscribe()` | `manager.tf_static().subscribe()` |
| C 访问方式 | `wuji_glove_subscribe_tactile(dev, ...)` | `wuji_subscribe_tf_static(...)`，不需要设备句柄 |
| 数据来源 | 单个设备 | 所有已连接设备的合并 |
| 查询 API | `device.topics()` / `wuji_dev_list_topics()` | `manager.global_topics()` / `wuji_list_global_topics()` |

## 可用数据流

### 按产品索引

| 产品 | 数据流与订阅接口 |
| - | - |
| Wuji Hand 2.2 (Beta) | [指尖触觉](/docs/wuji-sdk/wuji-hand-2-tactile/)及 [Wuji Hand 2 SDK 接口参考](/docs/wuji-hand/latest/sdk-reference/) |
| Wuji Hand 2.1 (Beta) | [Wuji Hand 2 SDK 接口参考](/docs/wuji-hand/latest/sdk-reference/) |
| Wuji Glove | [Wuji Glove 数据流概览](/docs/wuji-glove/sdk-data-reference/) |

## 接口清单

| 操作 | Python | C |
| - | - | - |
| 打开 typed 订阅 | `resource.subscribe()`、`resource.subscribe_with_callback(callback=...)` | `wuji_<device>_subscribe_<stream>()` |
| 按路径订阅任意流 | `device.subscribe(path)` | `wuji_sub_open()` |
| 等待下一帧 | `await sub.recv_async()` | 无对应接口，数据由回调推送 |
| 非阻塞取帧 | `sub.recv()` | 无对应接口，数据由回调推送 |
| 关闭订阅 | `sub.close()` | `wuji_sub_close()` |
| 调节输出率 | `sub.set_rate(frequency_hz)` | `wuji_sub_set_rate()` |
| 列出设备流 | `device.topics()` | `wuji_dev_list_topics()` |
| 列出全局流 | `manager.global_topics()` | `wuji_list_global_topics()` |
| 订阅全局坐标变换 | `manager.tf().subscribe()`、`manager.tf_static().subscribe()` | `wuji_subscribe_tf()`、`wuji_subscribe_tf_static()` |

Python 提供异步、同步非阻塞与回调三种取帧方式，C 只提供回调方式，帧指针仅在回调期间有效。C 侧的内存归属与回调约束见 [C 接口参考](/docs/wuji-sdk/data-reference/)。


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