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

# 输出率调节

## 概述

本篇实践介绍如何通过 `Subscription.set_rate()` 调节 Wuji Glove 数据流的推送频率。应用场景包括匹配应用采样频率和降低网络及数据处理开销。

对应的 Python 示例为 [6.set\_stream\_rate.py](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/6.set_stream_rate.py)，C 示例为 [1\_set\_stream\_rate.c](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/1_set_stream_rate.c)。 可调流的清单与接口定义见[手部追踪](/docs/wuji-glove/sdk-data-reference/hand-tracking)，本页说明集成流程和运行时行为。

<Warning>
  当前测试设备的固件版本不满足要求，因此本文说明失败路径、判定顺序、参数边界和两侧行为差异。支持版本的成功路径以接口契约为准。
</Warning>

## 前置条件

### 硬件

| 项目 | 要求 |
| - | - |
| Wuji Glove | 1 只，**固件 v0.11.4 及以上** |
| 连接方式 | 以太网 |

查固件版本：

```python theme={null}
print(glove.version().get())
```

### 软件

| 项目 | 要求 |
| - | - |
| 操作系统 | Ubuntu 22.04 LTS 及以上版本 |
| Wuji SDK | v2026.8.31 或更高版本 |
| Python | 3.10 及以上版本 |
| C 工具链 | CMake 3.16 及以上版本，以及解压后的 SDK 包 |

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install wuji-sdk
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    cmake -S examples/c/wuji_glove -B build/glove \
      -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
      -DWUJI_SDK_LIB="${SDK}/lib/libwuji_sdk_c.so"
    cmake --build build/glove --target 1_set_stream_rate
    ```
  </Tab>
</Tabs>

### 前序步骤

先看懂[数据订阅：回调](/docs/wuji-glove/sdk-practice/subscribe-callback)。`set_rate` 是订阅句柄上的方法，得先有订阅。

## 数据流

```mermaid theme={null}
sequenceDiagram
    participant A as 应用
    participant S as Subscription
    participant D as 设备
    participant P as 派生流

    A->>S: stream.subscribe()
    A->>S: set_rate(hz)
    S->>S: 检查入口流、固件、设备和活跃订阅
    S->>D: 请求入口流速率
    D-->>S: 实际生效速率
    S-->>A: 返回 actual_hz
    D-->>P: 按源流速率推送派生流
    Note over A,S: 保持订阅句柄，速率才持续生效
    A->>S: close() 或设备重连
    S-->>D: 恢复设备默认速率
```

判定按固定顺序执行，该顺序决定返回的错误信息。

## 哪些流能调

| 类别 | 流 | `set_rate` |
| - | - | - |
| 速率入口 | `emf_poses`、`tactile`、`tactile_zones`、`imu_raw/{palm,thumb,index,middle,ring,pinky}` | 支持 |
| 派生流 | `hand_joint_angles`、`hand_skeleton`、`tip_poses`、`imu_data/*`、`tactile_point_cloud` | 拒绝，跟随源流 |
| 全局流 | `tf`、`tf_static` | 不支持 |

**降低源流会连带降低所有派生流。** 把 `emf_poses` 降到 30 Hz，`hand_joint_angles`、`hand_skeleton`、`tip_poses` 自动跟着变成 30 Hz，同时省掉相应比例的求解开销。

## 流的速率范围

这是 `set_rate` 最容易误解的一点：

* 设置对**该流的每一个订阅者**生效，包括别的进程、Wuji Studio、录制器
* 该流的**最后一个订阅关闭**时，速率重置为设备默认值
* **设备重连**后同样重置

所以想让降频持续生效，就得一直握着那个订阅句柄。示例的注释写得很直白：`Keep the handle open for as long as you want its rate to stay in effect.`

多个客户端同时连一台设备时，降低的速率会影响其他订阅者。共享环境中修改速率前应先协调。

## 失败路径实测

设备固件 0.11.2，低于要求的 v0.11.4。

### 速率入口流：卡在固件门

```text theme={null}
WujiException: Unsupported operation: device 'WG1KA00XXXXXXXXX' runs firmware without rate control
```

`emf_poses`、`tactile`、`tactile_zones` 三条全部一样。**传 0（恢复默认）也会被同一条拦下**，固件门在参数之前。

### 派生流：卡在入口点判定，与固件无关

```text theme={null}
WujiException: Unsupported operation: 'hand_joint_angles' is not a rate-control entry point
WujiException: Unsupported operation: 'hand_skeleton' is not a rate-control entry point
WujiException: Unsupported operation: 'tactile_point_cloud' is not a rate-control entry point
```

这条判定排在固件门**前面**，所以换成支持速率控制的固件，报的还是同一条。看到这条错误不要去升固件，要去改调用的流。

### 全局流：报的是内部错误

```text theme={null}
WujiException: Internal error: Device '' not registered
```

`tf` 与 `tf_static` 都是这条。这不是一条对使用者友好的提示，看到它的时候要意识到「我在对全局流调 `set_rate`」。全局流本来就不支持速率控制，它是跨设备聚合出来的，没有单一设备可以下发降频指令。

### 参数范围：uint16

C 侧签名是：

```c theme={null}
WujiStatus wuji_sub_set_rate(struct WujiSub *sub, uint16_t frequency_hz,
                             uint16_t *out_actual_hz);
```

Python 侧同样是 16 位无符号。实测边界：

| 传入 | 结果 |
| - | - |
| `-1` | `OverflowError: out of range integral type conversion attempted` |
| `65535` | 进入固件门（参数合法） |
| `65536` | `OverflowError` |

负数和超过 65535 的值在绑定层就被挡下，不会到达设备。

## 两侧错误处理不一样

同一条固件门错误，两个示例的反应完全不同：

| | 表现 | 后果 |
| - | - | - |
| Python 示例 | `set_rate` 抛 `WujiException`，没有 try | **整个程序崩溃退出**，一帧数据都收不到 |
| C 示例 | 检查返回码，打印错误后继续 | 订阅照常工作，正常收完 2 秒数据才退出 |

C 示例的实测输出：

```text theme={null}
wuji_sub_set_rate failed: Unsupported operation: device 'WG1KA00XXXXXXXXX' runs firmware without rate control
emf_poses frame: 5 poses
emf_poses frame: 5 poses
emf_poses frame: 5 poses
...
```

**C 侧的做法更稳。** 速率控制是可选优化，不该因为设备固件旧就让整个采集流程挂掉。照 Python 示例写自己的代码时，把 `set_rate` 包进 try：

```python theme={null}
try:
    actual = sub.set_rate(30)
    print(f"降频到 {actual} Hz")
except WujiException as e:
    print(f"降频不可用，按默认速率运行: {e}")
```

## 集成步骤

以下片段按接入顺序展示关键操作，参数解析、信号处理和完整错误分支见示例。

### 1. 连接目标手套

将 `serial_number` 替换为目标手套的序列号。连接成功后，后续步骤复用同一个设备句柄。C 片段需包含 `wuji_sdk.h`。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from wuji_sdk import SdkManager
    manager = SdkManager.instance()
    glove = manager.connect(sn=serial_number, device_name="glove_0")
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiInitOptions init = { .log_level = 3 };
    if (wuji_init(&init) != WUJI_STATUS_OK) return 1;
    WujiConnectTarget target = {
        .kind = WUJI_CONNECT_TARGET_KIND_SN, .value = serial_number,
    };
    struct WujiDevice *dev = NULL;
    if (wuji_connect(&target, "glove_0", NULL, &dev) != WUJI_STATUS_OK) {
        wuji_shutdown();
        return 1;
    }
    ```
  </Tab>
</Tabs>

### 2. 订阅可调速的入口流

先建立 `emf_poses` 订阅，获得设置频率所需的句柄。C 的 `on_emf_poses` 回调见 [C 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/1_set_stream_rate.c)。

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

  <Tab title="C">
    ```c theme={null}
    struct WujiSub *sub = NULL;
    WujiStatus st = wuji_glove_subscribe_emf_poses(dev, on_emf_poses, NULL, &sub);
    /* 订阅失败时清理设备并退出 */
    ```
  </Tab>
</Tabs>

### 3. 设置频率并读取实际值

保持订阅句柄存活。设备可能量化请求值，后续处理使用返回的实际频率。设置失败时先处理错误，不把请求值视为已生效。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    actual = sub.set_rate(30)
    print(f"实际频率：{actual} Hz")
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    uint16_t actual = 0;
    if (wuji_sub_set_rate(sub, 30, &actual) == WUJI_STATUS_OK) {
        printf("actual=%u Hz\n", actual);
    } else {
        fprintf(stderr, "%s\n", wuji_last_error());
    }
    ```
  </Tab>
</Tabs>

### 4. 接收数据并恢复默认频率

Python 在异步函数中等待数据，C 由回调接收。完成后传入 `0` 恢复默认频率，再关闭订阅和连接。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    try:
        for _ in range(5):
            frame = await sub.recv_async()
            print(len(frame.poses))
        print(sub.set_rate(0))
    finally:
        sub.close()
        manager.disconnect_all()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    uint16_t restored = 0;
    wuji_sub_set_rate(sub, 0, &restored);
    wuji_sub_close(sub);
    wuji_dev_disconnect(dev);
    wuji_dev_release(dev);
    wuji_shutdown();
    ```
  </Tab>
</Tabs>

## 运行与输出

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    python examples/python/wuji_glove/6.set_stream_rate.py
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    ./build/glove/1_set_stream_rate
    ```
  </Tab>
</Tabs>

固件 0.11.2 上的 Python 实测输出，程序在 `set_rate` 处直接终止：

```text theme={null}
Connected: WG1KA00XXXXXXXXX
Traceback (most recent call last):
  ...
  File "6.set_stream_rate.py", line 43, in main
    actual = sub.set_rate(REQUESTED_HZ)
wuji_sdk.WujiException: Unsupported operation: device 'WG1KA00XXXXXXXXX' runs firmware without rate control
```

同一台设备上的 C 实测输出，报错之后继续正常收帧：

```text theme={null}
  SN=WG1KA00XXXXXXXXX  Type=WujiGlove  Address=192.168.1.100:50001
wuji_sub_set_rate failed: Unsupported operation: device 'WG1KA00XXXXXXXXX' runs firmware without rate control
emf_poses frame: 5 poses
emf_poses frame: 5 poses
...
```

以上为实机运行结果，设备是右手 Wuji Glove，固件 0.11.2，SDK v2026.8.31。

固件满足要求时，Python 侧的输出形如：

```text theme={null}
Connected: <serial number>
emf_poses: requested 30 Hz, device applied 30 Hz
emf_poses frame at 30 Hz: 5 poses
...
restored to the device default: 0 Hz
```

`device applied` 可能与请求值不同，设备会量化到它支持的档位。

## 注意事项

| 分支点 | 表现 | 处理 |
| - | - | - |
| 固件低于 v0.11.4 | `runs firmware without rate control` | 升级固件，或捕获异常后按默认速率跑 |
| 对派生流调用 | `is not a rate-control entry point` | 改调它的源流，派生流会自动跟随 |
| 对全局流调用 | `Internal error: Device '' not registered` | 全局流不支持速率控制 |
| 传负数或大于 65535 | `OverflowError` | 参数是 uint16 |
| 请求的频率设备不支持 | 返回**量化后**的实际值 | 用返回值，不要假设等于请求值 |
| 该流的最后一个订阅关闭 | 速率重置为设备默认 | 想让降频持续就一直握着句柄 |
| 设备重连 | 速率重置 | 重连后重新调用 |
| 别的客户端也订着同一条流 | 它也会被降频 | 共享设备上改速率前先协调 |

<Info>
  旧的 `emf_poses_rate_divider` 接口（Python 的 `glove.emf_poses_rate_divider()`、C 的 `wuji_glove_get/set_emf_poses_rate_divider`）已经移除。升级固件到 v0.11.4 或更高，改用 `emf_poses` 订阅上的 `set_rate`。两者的一个实际差异是：旧的分频器跨重连保持，`set_rate` 不保持。
</Info>

## 扩展用法

### 用返回值，别用请求值

`set_rate` 返回设备实际应用的频率。按请求值去算超时、缓冲区大小或采样窗口，遇到量化就会算错。

### 降频能省多少

按比例算。130 Hz 降到 30 Hz，网络流量和回调次数都降到约 23%。录制场景的收益更直接，八条流全录、zstd 压缩实测每分钟约 72 MB，降到 30 Hz 之后差不多是每分钟 17 MB。

降 `emf_poses` 还会连带省掉手部求解的 CPU，因为派生流跟着降。

### 降频不改队列容量

订阅队列还是 1024 帧。频率降下来之后，同样的 1024 帧能覆盖更长的时间窗口，慢消费者的容错空间反而变大了，见[数据订阅：异步](/docs/wuji-glove/sdk-practice/subscribe-async)。

### 想要稳定的低频又不想握着句柄

目前没有跨连接保持的机制。旧的 `emf_poses_rate_divider` 有这个特性但已经移除。当前做法是在长连接服务中持续持有该订阅句柄。

## 相关内容

* [数据订阅：回调](/docs/wuji-glove/sdk-practice/subscribe-callback)：六条流的输出率实测
* [数据订阅：异步](/docs/wuji-glove/sdk-practice/subscribe-async)：订阅队列的 1024 帧边界
* [数据录制](/docs/wuji-glove/sdk-practice/data-recording)：降频对文件体积的影响
* [Wuji Hand 2 输出率调节](/docs/wuji-hand/latest/sdk-practice/stream-rate/)：灵巧手侧的同一套接口
* [数据订阅机制](/docs/wuji-sdk/data-subscription/)：订阅模型与生命周期


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