Skip to main content

概述

本篇实践介绍如何通过 async/await 订阅 Wuji Glove 数据流,并在协程中读取数据帧。应用场景包括接入基于 asyncio 的应用、并发读取多条数据流和协调其他异步 I/O 任务。 对应的 Python 示例为 1.subscribe_async.py。 字段定义见 手部追踪、触觉 和 坐标变换,本页说明集成流程和运行时行为。
C SDK 的手套示例只提供回调式订阅,没有对应的异步版本。本页介绍 Python 用法。

前置条件

硬件

本示例只读取,不写任何参数。

软件

前序步骤

先看懂数据订阅:回调。六条流的帧结构、-1.0 无效通道、744/572/526 三个数字的关系都在那里,本页不重复这些内容。

数据流

队列在 SDK 内部,由固件推送填充,与消费协程解耦。这就是丢帧风险的来源。

订阅队列实测

队列容量是 1024 帧

订阅建立后即使一帧不取,队列也在填。实测在 132 Hz 的 tactile 流上: 前两行落后量等于产生量,一帧没丢。第三行卡在 1023,超出的部分已经被丢弃。队列容量是 1024 帧,与 SDK 内部广播通道的配置一致。

丢的是最老的帧,不抽帧

三组实测里,首帧之后连续收 400 帧,header.seq 的步长集合恒为 [1]。队列溢出时丢弃队首最老的一批,保留的部分仍然连续,不会中间挖洞。

丢帧不抛异常,只有一条 WARN 日志

积压 12 秒后开始取帧,SDK 打出:
recv_async() 本身正常返回下一帧,不抛异常,返回值里也没有任何丢帧标记。应用代码对这 549 帧的丢失完全无感知。 要发现它,只能靠日志。 跑对数据完整性有要求的任务时,把 SDK 日志级别开到 WARN 以上并接进自己的日志系统,不要直接输出到终端。

快消费不丢帧

反过来,只要消费跟得上就没有任何损失。实测连续 await recv_async() 取 200 帧用时 1.52 秒,等效 131 Hz,与流本身的输出率一致,seq 步长全是 1。

集成步骤

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

1. 连接目标手套

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

2. 在协程中创建订阅并取帧

把订阅创建放在消费循环之前。每次 recv_async() 等待下一帧,任务结束或取消时通过 finally 关闭订阅。

3. 启动消费任务并清理连接

单条流直接等待消费协程。需要并发消费时,按 Python 示例 为各条流建立协程,再交给 asyncio.gather()。

运行与输出

以上为实机运行结果,设备是右手 Wuji Glove,手套静置无人佩戴。 5 秒实测输出 3031 行,各流帧数分别是 506、506、505、502、502、501,六条流彼此相差不超过 5 帧。事件循环没有偏袒任何一条,gather 的调度是公平的。 Zones 那行的 palm=-0.1 是示例 avg() 把 -1.0 无效通道算进去的结果,不是手套受了反向的力。原因与修法见数据订阅:回调。

注意事项

示例的 while True 循环没有退出条件,靠 KeyboardInterrupt 停。finally 里只调了 manager.disconnect_all(),订阅句柄随进程退出一起回收。写长期运行的服务要自己补 sub.close()。

扩展用法

回调还是异步

两者队列语义相同,1024 帧的边界对回调一样成立。回调不会占用应用侧事件循环,异步订阅可以与 HTTP 请求、数据库写入等协程组合使用。

避免在协程中执行阻塞调用

事件循环是单线程的。协程里调 time.sleep()、同步写文件、同步 HTTP 请求,六条流会一起堆积。阻塞操作交给线程池:

只订阅需要的流

六条流全开,静置状态下每秒约 640 次 recv_async() 唤醒。做触觉分析只留 tactile 或 tactile_point_cloud,事件循环压力立刻降下来。

降低输出率

130 Hz 对多数应用偏高。订阅句柄支持降频,见输出率调节。降频之后队列里 1024 帧能覆盖的时间窗口随之变长,抗抖动能力更好。

相关内容