概述
本篇实践介绍如何使能 Wuji Hand 2 并向关节发送固定的余弦位置序列。应用场景包括验证控制指令链路和检查 MIT 参数配置。 对应的 Python 示例为 7.mit_sweep.py,C 示例为 7_mit_sweep.c。 指令字段见 JointCommand,控制参数见 MitParam。本示例会使能电机。开始前清空手部周围的人员、线缆与台面物品,全程有人监护,并保持随时断电的条件。手部原本不在零位时,第一帧就会把 20 个关节一起拉向零位,没有插值缓冲。需要柔和归零请先运行发布控制指令的平滑归零流程。
前置条件
硬件
软件
本示例不接受任何命令行参数。
前序步骤
确认设备的 MIT 参数与力矩上限处于出厂值。实测值:kp 被改小会让本就微弱的位移进一步消失,改大则超出本示例的安全设定。
数据流
序列在本地一次生成完毕再逐帧发送,不做插值也不做重规划。每条JointCommand 是「位置 + 速度 + 力矩」三元组,本示例只填位置,速度与力矩都是 0。
注意每帧都是完整的 20 维指令。关节 0 之外的 19 个位置写死为 0,等于持续命令它们回到零位,这部分运动不受 0.02 rad 这个幅度约束。
集成步骤
1. 连接并检查关节
扫描并连接 Wuji Hand 2 后,读取在线关节数量。示例要求 20 个关节全部在线,检查失败时不使能设备。wuji_scan()、wuji_connect() 和 wuji_hand_2_online_joints_count() 完成同样的检查。
2. 使能并取得起始姿态
调用enable() 后创建 joint_command 发布器。示例同时订阅 joint_states,通过 nid_to_joint_index 将完整帧整理为固定的 20 关节顺序,再从当前姿态平滑过渡到扫掠起点。
wuji_hand_2_enable(hand, NULL)、wuji_hand_2_joint_command_publish(),并通过回调复制一帧完整状态。
3. 按固定周期发送扫掠指令
扫掠只改变关节 0,幅度为 0.02 弧度,共发送 101 帧,帧间隔 20 ms。每帧创建 20 个JointCommand,其余关节保持零位。发送循环应响应 Ctrl+C。
capture_start_positions()、ramp_frames()、generate_sweep() 和 send_sweep() 是 Python 示例 中的辅助函数。保留完整帧校验、1 秒起始过渡和停止检查,不能直接从未知姿态发送扫掠首帧。
C 使用对应的 capture_start_positions()、send_ramp() 和 send_sweep() 辅助函数,发送最终调用 wuji_joint_command_publisher_send(publisher, commands)。
4. 停止运动并清理
结束或中断时先失能,再关闭发布器,最后断开并释放设备。C 示例使用wuji_hand_2_disable(hand, NULL) 和 wuji_joint_command_publisher_close(publisher),并在所有错误路径执行相同清理顺序。
运行与输出
- Python
- C
实测数据
在 20 关节全在线的右手 Wuji Hand 2 上实跑,同步订阅joint_states 记录关节 0 的响应:
关节 0 几乎不动,这是正常的。 力矩严格跟随指令上升与回落,说明指令已经下发、电机已按 MIT 阻抗响应。但 0.02 rad 的目标偏差在
kp=3 下只产生约 0.06 的力矩,克服不了关节静摩擦,实际位移只有 0.0004 rad,约合 0.023 度,肉眼不可见。
上表是手已处于零位附近时的数据。另一次运行的对照:手部原本弯曲时跑同一个示例,其余 19 个关节被拉向零位,实测位移如下。
nid 19 动了 0.6355 rad,约合 36 度。所以「幅度最小」只在手已经归零时成立。
判断本示例成功的依据是进程安静退出且退出码为 0,以及订阅
joint_states 能看到力矩响应,而不是看到手指摆动。要观察真实运动请看回放拇指对指动作。
注意事项
无论哪条路径,清理流程都会执行失能、关闭发布器、断开设备。异常与中断都不会添加恢复动作,手不会自动回到初始姿态。
扩展用法
多设备网络下必须指定 SN
auto_connect() 的语义是「连接总线上发现的唯一设备」。网络上不止一台时它拒绝连接并列出全部 SN,不会自动选择。实测报错:
想看到关节真的动
本示例不适合观察运动。要看幅度,用回放拇指对指动作的完整运动序列。自行加大本示例幅度会越过其安全设定,不建议。读取关节状态的方式
joint_states 是订阅类资源,没有 get() 方法。想在发指令的同时读实际位置,要先 subscribe_with_callback() 持续接收,再从最新帧取值。直接调用 hand.joint_states().get() 会抛 AttributeError。