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

### 回调接收

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

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

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

## 关闭订阅

回调订阅需要手动关闭：

> [tab] Python
>
> ```python theme={null}
> sub = glove.tactile().subscribe_with_callback(callback=on_data)
> # ... 使用一段时间后
> sub.close()
> ```

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

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

## 调节订阅流输出率

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

> [tab] 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] 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 恢复设备默认满速
> ```

* 需要在未关闭的订阅句柄上调用，订阅存续期间设置持续生效。
* 只能调低，无法超过原生输出率。设备把请求量化到支持的档位（原生输出率的整数分频），以实际生效值为准。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/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` | — |

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

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

> [tab] 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] 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);
> ```

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

| 维度 | 设备资源 | 全局资源 |
| - | - | - |
| 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) | [Wuji Hand 2 SDK 接口参考](https://docs.wuji.tech/docs/zh/wuji-hand/latest/sdk-reference.md) |
| Wuji Hand 2.1 (Beta) | [Wuji Hand 2 SDK 接口参考](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/sdk-reference.md) |
| Wuji Glove | [Wuji Glove 数据流概览](https://docs.wuji.tech/docs/zh/wuji-glove/latest/sdk-data-reference/index.md) |

配备指尖触觉传感器的设备，其订阅与解码方法见 [Wuji Hand 2 指尖触觉](https://docs.wuji.tech/docs/zh/wuji-sdk/latest/wuji-hand-2-tactile.md)。接入前读取连接设备的传感器元数据，确认接口可用。

## 接口清单

| 操作 | 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 接口参考](https://docs.wuji.tech/docs/zh/wuji-sdk/latest/data-reference.md)。


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