概述
本篇实践介绍如何通过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()。
运行与输出
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,事件循环压力立刻降下来。