SDK 接口

Wuji Hand 2 的 Python 语义化 API。统一模式:hand.{resource}().{action}()。所有操作都针对整只手(20 关节)。

本页聚焦 Wuji Hand 2 专属的语义化 API。SDK 基于通用的 Wuji SDKwuji_sdkpip 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_connectconnectdevice_name="wuji_hand_2" 重载会直接返回 WujiHand2 实例(带类型提示)。handednesssnaddress 互斥。多只同手性 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_numberstr设备序列号
device_namestr连接时指定的名字(默认 "wuji_hand_2"
infoOptional[DeviceInfo]设备信息:serial_numberfirmware_version
is_connectedbool连接状态

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–20

2.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.Calibrating

tactile_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 的完整往返:setreboot → 按新 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、单关节资源与单关节动作。

成员类型说明
labelstr属性。关节标签,格式 {finger}_S{1..4},如 "thumb_S1""pinky_S4"
indexint属性。全局索引(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: float

set 接受单个 (kp, kd)(应用到全部关节)或长度 20 的列表(逐关节)。get 返回 20 个元素,离线关节为 None

4.3 JointStateFrame

关节状态帧hand.joint_states().subscribe().recv() 返回值。变长,仅含在线关节。

属性类型说明
headerFrameHeaderseq / timestamp_us / frame_id
num_jointsint本帧关节数量
jointslist[JointStateEntry]关节状态数组

4.4 JointStateEntry

单关节状态

属性类型说明
nidint节点编号,跨帧定位关节
positionfloat位置(rad,关节侧)
velocityfloat速度(rad/s)
effortfloat力矩 (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_pct

4.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. 关节编号

手指S1S2S3S4
thumb0123
index4567
middle891011
ring12131415
pinky16171819

标签格式:{finger}_S{1..4}(如 thumb_S1pinky_S4)。

6. 异常处理

所有 SDK 操作错误统一抛 WujiException,消息中携带错误类型前缀(DisconnectedTimeoutPathNotFoundSchemaMismatchSerializeError、…)。业务侧按需 try / except 即可。

from wuji_sdk import WujiException

try:
    hand.joint_states().subscribe()
except WujiException as e:
    print(f"SDK error: {e}")