SDK 接口
Wuji Hand 2 的 Python 语义化 API。统一模式:hand.{resource}().{action}()。所有操作都针对整只手(20 关节)。
本页聚焦 Wuji Hand 2 专属的语义化 API。SDK 基于通用的 Wuji SDK(wuji_sdk,pip install wuji-sdk)——通用的安装、设备连接与数据订阅模型见 Wuji SDK 文档,源码见 wuji-sdk 仓库。
1. 连接
网络连接前提:Wuji Hand 2 使用静态 IP,不使用 DHCP。设备按左右手出厂分配固定地址——左手 192.168.1.110,右手 192.168.1.111,网关 192.168.1.1,子网掩码 255.255.255.0。首次连接前把电脑上对应网卡设到同一网段(如 192.168.1.100),再按地址连接。出厂 IP 可后续修改,见 ip(SET/GET)。
1.1 通过 SdkManager 连接(推荐)
from wuji_sdk import SdkManager, Handedness
manager = SdkManager.instance()
# 自动发现并连接局域网内首个 Wuji Hand 2
hand = manager.auto_connect(device_name="wuji_hand_2")
# 已知序列号或地址时显式连接
hand = manager.connect(sn=<device_sn>, device_name="wuji_hand_2")
hand = manager.connect(address="192.168.3.110:50001", device_name="wuji_hand_2")
# 或按左右手直接连接,无需序列号
hand = manager.connect(handedness=Handedness.Right, device_name="wuji_hand_2")auto_connect 和 connect 的 device_name="wuji_hand_2" 重载会直接返回 WujiHand2 实例(带类型提示)。handedness 与 sn、address 互斥。多只同手性 Hand 2 在网时会抛 AmbiguousHandedness,请改用 sn 指定。
1.2 ConnectOptions
from wuji_sdk import SdkManager, ConnectOptions
opts = ConnectOptions(
timeout_ms=1000,
retry_count=3,
enable_bridge=True, # 默认 True:允许多个客户端(Wuji Studio + 录制脚本 + 业务程序)同时连接
)
hand = manager.connect(sn=<device_sn>, device_name="wuji_hand_2", options=opts)设 enable_bridge=False 切换成独占单客户端模式。
1.3 实例属性
| 属性 | 类型 | 说明 |
|---|---|---|
serial_number | str | 设备序列号 |
device_name | str | 连接时指定的名字(默认 "wuji_hand_2") |
info | Optional[DeviceInfo] | 设备信息:serial_number、firmware_version |
is_connected | bool | 连接状态 |
hand.hw_version().get() 返回出厂硬件版本 HwVersion(major, minor, patch)。0.0.0 表示未在工厂端写入。
2. Hand 级资源
本节覆盖整手 API(20 关节)。单关节的读取与动作通过 hand.joint(k) / hand.joints() 返回的 JointHandle 进行(见 第 3 节 关节遍历)。设备默认运行在 MIT 控制模式,控制模式不通过 Python API 设置。
2.1 handedness(GET)
获取左右手识别结果(left / right)。
side = hand.handedness().get() # → "left" 或 "right"2.2 online_joints_count(GET)
获取在线关节数(0–20)。
n = hand.online_joints_count().get() # → int,0–202.3 joint_diagnostics(SUB)
关节诊断流:订阅逐关节的状态字 / 电流 / 母线电压 / 温度 / 错误码。帧变长,仅含在线关节,用每条的 nid 定位关节。
sub = hand.joint_diagnostics().subscribe() # → Subscription[JointDiagnosticsFrame]
frame = await sub.recv_async()
for j in frame.joints:
print(j.nid, j.vbus_v_fb, j.mcu_temp_c_fb, j.error_code_current)
sub.close()error_code_current 用静态方法 WujiHand2.describe_error(code) 解码出错误名与说明。
关节角请用 joint_states 流的 position,诊断流不含关节角。
2.4 comm_diag(GET,1 Hz)
通信诊断:每秒返回 5 根手指的吞吐 / 错误率。
diag = hand.comm_diag().get() # → HandCommunicationDiagnostics
for finger in diag.fingers: # 5 根手指各自的吞吐 / 错误率
print(finger.tx_kbps, finger.rx_kbps, finger.error_per_sec)用于排查关节与控制板之间的通信问题。
2.5 effort_limit(SET/GET)
力矩限幅:读写各关节的力矩上限(A)。
hand.effort_limit().set(1.5) # 全部关节设为 1.5 A
limits = hand.effort_limit().get() # → list[Optional[float]]写入拒绝 NaN / Inf / 负值与超出设备上限的值,整手与单关节写入同样校验。写入被拒时报错,当前生效值保持不变。上限由固件设定,常见异常与处理见 Wuji SDK 故障排查。
2.6 mit_params(SET/GET)
MIT 阻抗参数:读写各关节的 kp / kd。
hand.mit_params().set((1.0, 0.05)) # 全部关节统一 kp / kd
hand.mit_params().set([(1.0, 0.05)] * 20) # 长度 20 的列表逐关节设置
mp = hand.mit_params().get() # → list[Optional[MitParam]],离线关节为 None写入拒绝 NaN / Inf / 负值。
2.7 clear_fault(EXEC)
清除故障:直接动作,无参数时作用于整手。
hand.clear_fault() # 清除全部关节故障
hand.clear_fault(joints=mask) # 可选:长度 20 的 0/1 掩码,只清除掩码位为 1 的关节2.8 用户零点(origin)
把当前物理位置标定为关节侧零位。关节正在运动时延迟到下次 IDLE 才生效。
hand.set_origin() # 整手:所有关节刷新用户零点
hand.clear_origin() # 整手:清除所有关节的用户零点
hand.set_origin(joints=mask) # 可选:长度 20 的 0/1 掩码,只作用于掩码位为 1 的关节2.9 使能 / 失能 / 急停
hand.enable() # 使能全部关节
hand.disable() # 失能全部关节
hand.enable(joints=mask) # 可选:长度 20 的 0/1 掩码,作用于掩码位为 1 的关节
hand.emergency_stop() # 急停:整手动作,不接受掩码2.10 joint_states(SUB)
关节状态流:订阅 20 关节的实时状态帧。
sub = hand.joint_states().subscribe() # → Subscription[JointStateFrame]
frame = sub.recv() # 同步非阻塞,None = 暂无数据
frame = await sub.recv_async() # 异步等待
print(frame.header.seq, frame.header.timestamp_us)
for j in frame.joints:
print(j.nid, j.position, j.velocity, j.effort)
sub.close()回调模式:
def on_state(frame):
print(frame.header.seq, [j.position for j in frame.joints[:4]])
cb = hand.joint_states().subscribe_with_callback(on_state)
# ...
cb.close()position 为关节侧角度(rad),velocity 为关节侧角速度(rad/s),这是获取关节角与关节速度的唯一推荐路径。帧变长、仅含在线关节,用每条的 nid 定位关节。joint_diagnostics 流不包含关节角。
2.11 joint_command(PUB)
关节命令:发布 20 关节的位置 / 速度 / 力矩前馈。每次发送恰好 20 个 JointCommand,每个携带该关节的 position / velocity / effort。
from wuji_sdk import JointCommand
pub = hand.joint_command().publish() # → JointCommandPublisher
pub.send([JointCommand(position=p, velocity=0.0, effort=0.0) for p in positions])
pub.close()要把人手关键点转换成这里的 20 维关节命令,用 Wuji SDK 的手部重定向。
2.12 指尖触觉数据流(SUB)
指尖触觉需要配备指尖触觉传感器的 Beta 2 硬件,以及固件 v2.1.0 与 SDK v2026.7.21 及以上版本配套。
自描述的按指传感器数据流:先获取该指的 FingertipSensorInfo 元数据(其 format JSON 描述数据帧布局),再订阅该指的 FingertipSensorData 流并按 format 解码。拇指 40 个触点,其余手指 34 个,发布频率 100 Hz。
import json
info = hand.get_fingertip_info(0) # 0=thumb … 4=pinky → FingertipSensorInfo
fmt = json.loads(info.format) # 帧布局完全由 format 描述,不要硬编码
sub = hand.fingertip_thumb_data().subscribe() # 每指一个资源:fingertip_{thumb,index,middle,ring,pinky}_data
frame = await sub.recv_async() # → FingertipSensorData
if frame.info_digest != info.digest: # digest 不匹配说明 info 已变化
info = hand.get_fingertip_info(0) # 重新获取并重建解码器
# 按 fmt 的 point_fields / aggregate_fields 解码 frame.data
sub.close()完整的消费端参考(含解码器构建)见 examples/python/wuji_hand_2/ 下的 3.fingertip_typed.py 示例。
2.13 指尖触觉标定(EXEC)与状态查询(GET)
零基线重标定:一次调用对全部 5 个指尖做零基线重标定。
hand.tactile_calibrate() # 整手动作,遇到离线手指立即报错调用时必须保持所有传感器表面无负载(无接触力),否则标定出的零基线不正确。
调用成功表示标定命令已下发到每个手指,不代表标定已完成。用状态查询轮询确认:
from wuji_sdk import TactileState
status = hand.tactile_status("thumb") # "thumb" / "index" / "middle" / "ring" / "pinky"
print(status.model) # TactileType.Thumb(40 点)或 TactileType.Standard(34 点)
print(status.state) # TactileState.Ready / TactileState.Calibratingtactile_status 为阻塞查询(最长约 1 s)。轮询到 state == TactileState.Ready 即标定完成。
2.14 Flash 日志导出(诊断)
# 导出当前固件的运行日志(绝大多数排查场景用这个)
result = await hand.dump_hand_logs(bank="current", out_dir="./logs")bank | 含义 | 何时用 |
|---|---|---|
"current" | 当前正在运行的固件写的日志 | 默认使用,排查现网问题 |
"other" | 升级或回滚之前那个固件版本残留的日志 | 仅当需要追溯升级 / 回滚前的现象时再导 |
每次调用在 out_dir 下生成一个独立的会话目录 <sn>-<unix_ts>/,里面包含每个关节的 joint{0..19}.log 加一个 sboard.log(JSONL:每行一条 {"timestamp_ms", "level", "target", "message"})。out_dir 可省略,默认为 ~/.wuji/hand_logs。返回一个 dict:
result = await hand.dump_hand_logs(bank="current")
print(result["session_dir"]) # str: 本次会话目录绝对路径
print(result["files"]) # list[str]: 已写入的 .log 文件路径2.15 ip(SET/GET)
读写设备静态 IP 地址。set 把新 IP 写入设备 flash,固件不会热切换正在运行的以太网栈,新 IP 下次重启才生效。set 之后、重启之前,get 仍返回当前生效的 IP。
hand.ip().get() # 读当前 IP,如 "192.168.3.110"
hand.ip().set("192.168.2.111") # 写入 flash,重启后生效改 IP 的完整往返:set → reboot → 按新 IP 重连 → get 确认。
hand.ip().set("192.168.2.111")
hand.reboot()
manager.disconnect_all()
# 等设备重启且以太网就绪,约 8 秒
hand = manager.connect(address="192.168.2.111:50001", device_name="wuji_hand_2")
hand.ip().get() # → "192.168.2.111"新 IP 重启后才生效。set 只写入 flash,不改变正在运行的连接。重启前 get 返回的仍是当前 IP。
2.16 reboot(EXEC)
重启设备。设备会断连,重启后需重新连接。常与 ip().set() 搭配,让写入 flash 的新 IP 生效。
hand.reboot()3. 关节遍历
JointHandle 提供 label / index(用于与 effort_limit() / mit_params() 等 20 元素返回数组做下标对应),以及单关节资源与动作。FingerHandle 用于按手指遍历关节。
| 方法 | 返回类型 | 说明 |
|---|---|---|
hand.joints() | list[JointHandle] | 全部 20 个关节 |
hand.fingers() | list[FingerHandle] | 全部 5 根手指 |
for joint in hand.joints():
print(joint.label, joint.index) # e.g. "thumb_S1" 0
for finger in hand.fingers():
for joint in finger.joints(): # 每根手指 4 个关节,按 S1..S4 顺序
print(joint.label) # "thumb_S1", "thumb_S2", ...3.1 JointHandle
关节句柄:提供 label / index、单关节资源与单关节动作。
| 成员 | 类型 | 说明 |
|---|---|---|
label | str | 属性。关节标签,格式 {finger}_S{1..4},如 "thumb_S1"、"pinky_S4" |
index | int | 属性。全局索引(0–19) |
effort_limit() | 资源(SET/GET) | 单关节力矩限幅(A) |
error_code() | 资源(GET) | 单关节当前错误码,用 describe_error() 解码 |
status_word() | 资源(GET) | 单关节状态字 |
enable() / disable() | 动作 | 单关节使能 / 失能 |
clear_fault() | 动作 | 清除该关节故障 |
set_origin() / clear_origin() | 动作 | 设置 / 清除该关节用户零点 |
3.2 FingerHandle
手指句柄:遍历该手指的 4 个关节。
| 方法 | 返回类型 | 说明 |
|---|---|---|
joints() | list[JointHandle] | 该手指 4 个关节,按 S1..S4 顺序 |
4. 数据类型
4.1 JointDiagnosticsFrame / JointDiagnosticsEntry
关节诊断帧:hand.joint_diagnostics().subscribe() 流的帧类型。变长,仅含在线关节。
class JointDiagnosticsFrame:
header: FrameHeader # seq / timestamp_us / frame_id
num_joints: int
joints: list[JointDiagnosticsEntry]
class JointDiagnosticsEntry:
nid: int # 节点编号,跨帧定位关节
status_word: StatusWord # 状态字
current: float # 电流 (A)
vbus_v_fb: float # 母线电压 (V)
mcu_temp_c_fb: float # MCU 温度 (°C)
error_code_current: int # 当前错误码,用 describe_error() 解码4.2 MitParam
MIT 参数:mit_params().get() 返回元素。
class MitParam:
kp: float
kd: floatset 接受单个 (kp, kd)(应用到全部关节)或长度 20 的列表(逐关节)。get 返回 20 个元素,离线关节为 None。
4.3 JointStateFrame
关节状态帧:hand.joint_states().subscribe().recv() 返回值。变长,仅含在线关节。
| 属性 | 类型 | 说明 |
|---|---|---|
header | FrameHeader | seq / timestamp_us / frame_id |
num_joints | int | 本帧关节数量 |
joints | list[JointStateEntry] | 关节状态数组 |
4.4 JointStateEntry
单关节状态。
| 属性 | 类型 | 说明 |
|---|---|---|
nid | int | 节点编号,跨帧定位关节 |
position | float | 位置(rad,关节侧) |
velocity | float | 速度(rad/s) |
effort | float | 力矩 (A) |
4.5 JointCommand
关节命令:joint_command().publish().send() 入参元素。
class JointCommand:
position: float # 位置 (rad)
velocity: float # 速度 (rad/s)
effort: float # 力矩前馈 (A)4.6 HandCommunicationDiagnostics
通信诊断数据。
class HandCommunicationDiagnostics:
fingers: list[FingerCommunicationDiagnostics] # 长度 5
class FingerCommunicationDiagnostics:
tx_frame_total: int
rx_frame_total: int
tx_kbps: int
rx_kbps: int
error_per_sec: int
crc_error_total: int
frame_format_error_total: int
uart_hw_error_total: int
transfer_stats: list[TransferStats]
nodes: list[NodeDiagnostics] # per-node:online / ms_since_last_response / response_rate_pct4.7 FingertipSensorInfo / FingertipSensorData
指尖传感器元数据与数据帧:get_fingertip_info(finger) 返回值与 fingertip_{finger}_data 订阅帧。
class FingertipSensorInfo:
header: FrameHeader # seq / timestamp_us / frame_id
digest: int # info 内容 CRC32,供 data 帧绑定校验
model: str # 传感器型号字符串(可为空)
device_type: int # 传感器类型枚举
rate_hz: float # data 发布频率(Hz,当前 100)
format: str # JSON:数据帧布局(point_count / point_stride / point_fields / aggregate_fields / encoding)
class FingertipSensorData:
header: FrameHeader
info_digest: int # 绑定生效 info 的 digest,不匹配时重新获取 info
data: list[int] # 纯 value payload,按 info.format 解释4.8 TactileStatus / TactileType / TactileState
触觉状态:hand.tactile_status(finger) 返回值。
class TactileStatus:
model: TactileType # Standard(34 点标准指)或 Thumb(40 点拇指)
state: TactileState # Ready(就绪)或 Calibrating(标定中)枚举成员与固件整数等值比较,如 TactileState.Calibrating == 1。
5. 关节编号
| 手指 | S1 | S2 | S3 | S4 |
|---|---|---|---|---|
| thumb | 0 | 1 | 2 | 3 |
| index | 4 | 5 | 6 | 7 |
| middle | 8 | 9 | 10 | 11 |
| ring | 12 | 13 | 14 | 15 |
| pinky | 16 | 17 | 18 | 19 |
标签格式:{finger}_S{1..4}(如 thumb_S1、pinky_S4)。
6. 异常处理
所有 SDK 操作错误统一抛 WujiException,消息中携带错误类型前缀(Disconnected、Timeout、PathNotFound、SchemaMismatch、SerializeError、…)。业务侧按需 try / except 即可。
from wuji_sdk import WujiException
try:
hand.joint_states().subscribe()
except WujiException as e:
print(f"SDK error: {e}")