ROS2中并发连接蓝牙手柄排错过程
需求:给一个定制的VR手柄编写ROS 2驱动程序,将其实时的姿态和按钮数据发布到自定义的msg上。其实这是个很简单的需求,只要利用手柄的SDK,读取手柄通过蓝牙传来的数据,处理后发布在ros msg上即可。但是我被傻逼联发科坑了😅,这个之后再说。
一、修复电脑驱动
笔者的电脑是翼龙15pro,使用的系统是Ubuntu22.04,不知道怎的,系统缺少了蓝牙驱动,在Github上经过一番搏斗后才修好。链接
二、事件循环自动关闭
节点启动了,日志显示手柄“连接成功”,但 ros2 topic echo 里的数据却一动不动,或者只更新一次就静止了。
经过排查,我们发现了问题的根源:
我使用了 Python 的 asyncio 和 bleak 库来处理蓝牙通信,并将这些操作放在一个独立的后台线程中,以避免阻塞 ROS 2 的主循环。但最初的实现有一个致命缺陷:当异步的连接任务完成后,这个后台线程就认为自己的工作结束并退出了,它所创建的 asyncio 事件循环也随之销毁。虽然蓝牙连接在物理上还存在,但已经没有程序在“监听”后续发来的数据了。
官方的 PyQt6 示例给了我启示。它采用了一个后台常驻线程 + run_forever()的架构。主线程负责下达指令,而后台线程的事件循环则永远运行,专门负责接收数据。
三、随机卡顿的数据流
最阴间的一部分,程序可以稳定地连接两个手柄,并且数据也能持续传来。
但是出现了非常诡异的现象!两个手柄的姿态数据、以及其中一个手柄的按钮数据,都能以很高的频率(30-100Hz)更新,但另一个手柄的按钮数据流,频率却低到令人发指的个位数(1-5Hz),而且“中招”的手柄是随机的!
首先让我们排除代码逻辑问题(因为代码对左右手是完全对称的)。最终,通过一个隔离实验(只连接单个手柄测试),我们得到了结论:
单个手柄工作时,它的所有数据流(姿态和按钮)频率都非常高,完全正常。问题只在两个手柄同时连接时出现。
我尝试过降低消息传输频率,也怀疑过驱动问题(因为现在电脑里安装的蓝牙驱动是社区编写的)
尝试降频
降频前:
[vr_controller_node-1] [INFO] [1749612450.691984173] [vr_controller_node]: Data Rates (Hz) | L-Pose: 47.0, R-Pose: 98.5, L-Button: 0.5, R-Button: 29.0
降频后:
[vr_controller_node-1] [INFO] [1749611456.634736371] [vr_controller_node]: Data Rates (Hz) | L-Pose: 33.0, R-Pose: 35.0, L-Button: 0.5, R-Button: 23.0
更换电脑
在 R9000P 上运行:
[vr_controller_node-1] [INFO] [1749610890.625920798] [vr_controller_node]: Data Rates (Hz) | L-Pose: 100.0, R-Pose: 78.5, L-Button: 29.5, R-Button: 0.5
mentor和leader都建议我把代码拆成两份,两个程序分别控制左手和右手,最终还是失败了🤣。
最后是ai大人救我于水火之中(不是哥们,你还有存在的必要吗,菜完了)
ai坚信是我网卡的问题,一开始我是不大信的,总不能两台电脑的网卡都有问题吧?查询后发现:
翼龙15pro:MT7922
R9000P:MT7921
好小子,联发科你这里阴我是吧,你个狗😡
最后在树莓派上成功跑通。
What can I say,别支持联发科喵,谢谢喵。