Skip to main content

设备连接与筛选

本节介绍如何在 wujihandpy 中连接灵巧手设备,以及在存在多台设备时进行筛选的方法。

连接与筛选接口概述

连接设备

调用 wujihandpy.Hand() 可创建一个 Hand 对象并连接灵巧手:
若系统中仅连接一台灵巧手,则会自动连接该设备。

筛选设备

当系统中连接了多台灵巧手时,使用无参构造函数将导致错误。例如:
为解决该问题,有两种方式指定要连接的设备。

按序列号指定

通过 serial_number 参数明确指定设备:
这样可确保程序始终连接到正确的设备实例。

按左右手指定

当系统中恰好连接一只左手和一只右手时,可直接用 side 参数选择,无需记忆序列号:
SDK 会自动枚举所有匹配设备、读取每只手的手性标识,再连接对应一只。
side 与 serial_number 互斥两者只能选一个传入。同时指定会抛 ValueError。side 仅接受字符串 "left" 或 "right",其他取值会抛 ValueError。匹配失败的常见情形:
  • 未接入对应侧的手 → 抛 ConnectionError,提示 No <side> hand found
  • 同侧接入了两只 → 抛 ConnectionError,提示改用 serial_number

查询序列号

灵巧手有两种序列号:
  • USB 序列号:USB 硬件描述符中的标识符,用于连接时区分多台设备。构造函数的 serial_number 参数使用的是此序列号。
  • 产品序列号:固件中存储的产品追踪编号,用于售后和设备管理。

查询 USB 序列号

在命令行中执行以下命令,查看系统中连接的所有灵巧手:
输出示例:
标注的数字即为 USB 序列号,可直接传递给构造函数的 serial_number 参数。

查询产品序列号

连接设备后,调用 get_product_sn() 获取产品序列号:

读取数据

本节介绍如何以同步模式读取数据。

读取接口概述

调用 read_* 系列函数会向灵巧手发送同步读请求,并阻塞当前线程直到设备返回结果。函数返回则表示读取已成功完成。
非实时接口的延迟特性每次同步请求会导致约 5~10 ms 的阻塞,适用于非实时任务,请勿在实时控制环路或高实时性循环中使用此类 API。对实时控制场景,请考虑:

单值读取

当读取层级与对象层级一致时,函数返回单个标量值。
示例 使用 hand 读取灵巧手的上电运行时间(Hand 层):
使用 joint 读取食指近端关节的当前位置(Joint 层):

批量读取

当读取层级低于对象层级时,函数返回包含多个数据的矩阵。
示例 使用 hand 请求整手所有关节(共 20 个)的当前位置数据(Joint 层):
函数返回一个 np.NDArray[np.float64],每行对应一个手指、每列对应一个关节:
同样,可使用 finger 请求单个手指的所有关节位置:

更多读取示例

写入数据

本节介绍如何以同步模式写入数据。

写入接口概述

调用 write_* 系列函数会向灵巧手发送同步写请求,并阻塞当前线程直到设备返回结果。函数返回则表示写入已成功完成。
非实时接口的延迟特性每次同步请求会导致约 5~10 ms 的阻塞,适用于非实时任务,请勿在实时控制环路或高实时性循环中使用此类 API。对实时控制场景,请考虑:

单值写入

当写入层级与对象层级一致时,函数使用单个标量值。
示例 使能食指近端关节(Joint 层):

批量写入

当写入层级低于对象层级时,函数既可使用单个标量值,也可使用包含多个数据的矩阵。
示例 使用 hand 使能整手所有关节(Joint 层):
或使能食指,失能其余手指:
同理可向整手所有关节写入目标控制角度:

非法数值写入

通常,非法数值写入会被自动处理,例如:
  • 超出合法范围的角度会被自动限幅至上下界。
但这并非绝对,当数值存在逻辑错误时,例如:
  • 将关节的 effort limit 设为非法值(过大或过小)
会导致操作失败,抛出 TimeoutError。

更多写入示例

实时控制

本节介绍在 wujihandpy 中以实时模式控制灵巧手的方法。

实时控制接口概述

调用 Hand.realtime_controller() 可使灵巧手进入实时控制模式,并返回一个实时控制器对象 IController。 进入实时控制模式后,电机侧会启动一个 16 kHz 实时滤波器,将控制量滤波为平滑的 16 kHz 信号。

滤波器

实时控制器需要一个滤波器对象 (filter.IFilter) 来平滑地调整目标值。
滤波器的初值会自动设置为灵巧手的实际位置,避免初始过渡问题。
wujihandpy 提供以下常用滤波器:
  • filter.LowPass(cutoff_freq: float):一阶低通滤波器,截止频率可调。当设置截止频率 >= 1 kHz 时,滤波器被关闭。
由于滤波器逻辑运行于电机驱动板上,不可使用自定义滤波器。

上下文管理器

IController 可作为上下文管理器使用:
当 with 块结束时,会自动调用 controller.close(),确保灵巧手正确退出实时控制模式。

自动析构

若未使用上下文管理器,IController 在析构时也会自动调用 close(),由于 Python 的垃圾回收存在延迟,灵巧手可能不会立即退出实时模式。若不需精确控制退出时机,可直接依赖析构行为。

实时写入

set_joint_target_position() 函数会非阻塞地将目标控制量尽可能快地发送至电机驱动板。

实时读取

电机以 1 kHz 的频率自动上报当前位置和 effort。以下函数均为非阻塞调用,会立即返回缓存中最新的读取结果。
什么是 Effort?Effort 是电流空间的执行器作用量,经过滤波处理后输出。它不是实际测量的电流值,应将其理解为相对驱动强度。典型应用场景:
  • 碰撞检测:effort 突增表示关节受阻
  • 负载监控:可通过 effort / effort_limit 计算当前输出百分比
示例

实时控制进阶示例

异步读/写

本节介绍在 wujihandpy 中以异步模式读/写数据的方法。

异步接口概述

读/写接口均提供异步版本,函数名以 _async 为后缀,用于在协程环境中执行非阻塞的 I/O 操作。
调用异步接口时需使用 await,在等待期间不会阻塞事件循环。函数返回则表示操作已成功完成。
非实时接口的延迟特性异步请求同样需要 5~10 ms 才能完成,其本质并不更快,仅是不阻塞事件循环。与同步接口相比,异步接口的唯一区别在于其允许在等待期间执行其他协程任务。 因此请勿在实时控制环路或高实时性循环中调用此类 API。对实时控制场景,请考虑:
示例

异步进阶示例

多线程操作

本节介绍在 wujihandpy 中进行多线程操作的方法。

多线程接口概述

默认情况下,wujihandpy 会对 Hand 对象进行线程安全检查,以防止多线程并发访问导致的数据竞争问题。若需在多线程环境中使用同一个 Hand 对象,需先调用 Hand.disable_thread_safe_check() 禁用该检查。

多线程安全注意事项

重要:禁用线程安全检查后,用户需要自行保证线程安全。
  • 必须在多线程操作 Hand 对象之前调用 disable_thread_safe_check()
  • 用户需要自行使用互斥锁(threading.Lock)等同步机制保护对 Hand 对象的并发访问
  • 建议使用 with lock: 语句块包裹所有对 Hand 对象及其派生对象(如 IController)的操作
示例 以下示例展示了如何在多线程环境中使用实时控制器:

预期效果

运行示例后,终端将输出类似以下内容:
  • 首行显示线程安全检查已禁用
  • [Reader] 打印当前关节位置矩阵(5×4,对应 5 个手指各 4 个关节)
  • [Writer] 打印当前目标位置(余弦波,从 0 逐渐增大)
  • 两个线程交替输出,顺序可能交错,这是多线程的正常现象
  • F1(拇指)保持不动,F2(食指)、F3(中指)、F4(无名指)、F5(小指)会做周期性弯曲-伸展动作

多线程进阶示例

USB 断连处理

设备运行期间,灵巧手与电脑之间的 USB 连接可能中断,例如数据线被拔出。本节介绍 wujihandpy 在断连时的行为,以及捕获断连异常的方法。

断连时的行为

USB 连接中断时,wujihandpy 会抛出异常。捕获该异常即可在断连后恢复,进程不会崩溃。 断连后再次发起的操作会立即抛出 Python 内置的 ConnectionError,不再等待 500 ms 超时,涉及接口包括:
  • 同步读 / 写(read_* / write_*)
  • 异步读 / 写(read_*_async / write_*_async)
断连瞬间已在等待中的异步请求是个例外:它会以 TimeoutError 结束,而非 ConnectionError。若代码中存在并发的异步操作,建议捕获两者的共同基类 OSError,即可同时覆盖断连与超时。

捕获断连异常

将设备操作放入 try 块,捕获 ConnectionError 即可处理断连:
运行脚本后,在读取过程中拔出 USB 线缆,循环会立即被 ConnectionError 中断,程序正常退出而不会崩溃。

实时控制模式下的断连检测

实时控制器的非阻塞接口(set_joint_target_position()、get_joint_actual_position()、get_joint_actual_effort())在断连时不会抛出 ConnectionError。
实时控制环路依赖 PDO 高速数据,断连时这些接口仅停止更新缓存,不抛出异常。若需在实时控制场景中检测断连,请在旁路低速循环中周期性调用一次同步读取(如 read_input_voltage())作为探测,并捕获 ConnectionError。 实时控制器以上下文管理器使用时,with 块结束会自动调用 close() 完成清理。断连状态下 close() 也会安全返回,无需额外处理。

更多断连处理示例