> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK 接口

<Info>
  Wuji Hand 2 的 Python 语义化 API。统一模式：`hand.{resource}().{action}()`。所有操作都针对整只手（20 关节）。
</Info>

本页聚焦 Wuji Hand 2 专属的语义化 API。SDK 基于通用的 **Wuji SDK**（`wuji_sdk`，`pip install wuji-sdk`）——通用的安装、设备连接与数据订阅模型见 [Wuji SDK 文档](/docs/wuji-sdk/)，源码见 [wuji-sdk 仓库](https://github.com/wuji-technology/wuji-sdk)。

## 连接

<Info>
  **网络连接前提**：Wuji Hand 2 使用静态 IP，不使用 DHCP。设备按左右手出厂分配固定地址——左手 `192.168.1.110`，右手 `192.168.1.111`，网关 `192.168.1.1`，子网掩码 `255.255.255.0`。首次连接前将电脑对应网卡设置为同一网段（如 `192.168.1.50`），再按地址连接。出厂 IP 可后续修改，见 [ip（SET/GET）](#ip（set/get）)。
</Info>

### 通过 SdkManager 连接（推荐）

```python theme={null}
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.1.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` 指定。

### ConnectOptions

```python theme={null}
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` 切换成独占单客户端模式。

### 实例属性

| 属性 | 类型 | 说明 |
| - | - | - |
| `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` 表示未在工厂端写入。

## Hand 级资源

本节覆盖整手 API（20 关节）。单关节的读取与动作通过 `hand.joint(k)` / `hand.joints()` 返回的 `JointHandle` 进行（见 [关节遍历](#关节遍历)）。设备默认运行在 MIT 控制模式，控制模式不通过 Python API 设置。

### handedness（GET）

获取**左右手识别**结果（left / right）。

```python theme={null}
side = hand.handedness().get()   # → "left" 或 "right"
```

### online\_joints\_count（GET）

获取**在线关节数**（0–20）。

```python theme={null}
n = hand.online_joints_count().get()   # → int，0–20
```

### joint\_diagnostics（SUB）

**关节诊断流**：订阅逐关节的状态字 / 电流 / 母线电压 / 温度 / 错误码。帧变长，仅含在线关节，用每条的 `nid` 定位关节。本流与 `joint_states` 共用同一条设备流，调低其中一个的推送频率，另一个会同步降低，见 [joint\_states（SUB）](#joint_states（sub）)。

```python theme={null}
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, f"0x{j.error_code_current:04X}")
sub.close()
```

`error_code_current` 用静态方法 `WujiHand2.describe_error(code)` 解码，返回对象含错误名、说明、`severity`（`Warning` / `DeferredStop` / `ImmediateStop` / `Fatal`）与 `clear_policy`（`AutoClear` / `ManualClear` / `NonClearable`），未知错误码返回 `None`。返回对象的 `code` 字段是整数，与传入的错误码相同。直接打印诊断条目对象时，错误码按 16 进制显示。

<Info>
  关节角请用 `joint_states` 流的 `position`，诊断流不含关节角。
</Info>

### comm\_diag（GET，1 Hz）

**通信诊断**：每秒返回 5 根手指的吞吐 / 错误率。

```python theme={null}
diag = hand.comm_diag().get()           # → HandCommunicationDiagnostics
for finger in diag.fingers:             # 5 根手指各自的吞吐 / 错误率
    print(finger.tx_kbps, finger.rx_kbps, finger.error_per_sec)
```

用于排查关节与控制板之间的通信问题。

### effort\_limit（SET/GET）

**力矩限幅**：读写各关节的力矩上限（A）。

```python theme={null}
hand.effort_limit().set(1.5)            # 全部关节设为 1.5 A
limits = hand.effort_limit().get()      # → list[Optional[float]]
```

写入拒绝 NaN / Inf / 负值与超出设备上限的值，整手与单关节写入同样校验。写入被拒时报错，当前生效值保持不变。上限由固件设定，常见异常与处理见 [Wuji SDK 故障排查](/docs/wuji-sdk/troubleshooting/)。

### mit\_params（SET/GET）

**MIT 阻抗参数**：读写各关节的 kp / kd。

```python theme={null}
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 / 负值。

### clear\_fault（EXEC）

**清除故障**：直接动作，无参数时作用于整手。

```python theme={null}
hand.clear_fault()                  # 清除全部关节故障
hand.clear_fault(joints=mask)       # 可选：长度 20 的 0/1 掩码，只清除掩码位为 1 的关节
```

### 用户零点（origin）

把当前物理位置标定为关节侧零位。关节正在运动时延迟到下次 IDLE 才生效。

```python theme={null}
hand.set_origin()                   # 整手：所有关节刷新用户零点
hand.clear_origin()                 # 整手：清除所有关节的用户零点
hand.set_origin(joints=mask)        # 可选：长度 20 的 0/1 掩码，只作用于掩码位为 1 的关节
```

### 使能 / 失能 / 急停

```python theme={null}
hand.enable()               # 使能全部关节
hand.disable()              # 失能全部关节
hand.enable(joints=mask)    # 可选：长度 20 的 0/1 掩码，作用于掩码位为 1 的关节
hand.emergency_stop()       # 急停：整手动作，不接受掩码
```

### joint\_states（SUB）

**关节状态流**：订阅 20 关节的实时状态帧，原生输出率 1000 Hz。可通过订阅句柄调低推送频率：`sub.set_rate(hz)` 返回实际生效值，传 `0` 恢复原生输出率，完整用法见 [Wuji SDK 数据订阅](/docs/wuji-sdk/data-subscription/#调节订阅流输出率)。本流与 `joint_diagnostics` 共用同一条设备流，调低其中一个的推送频率，另一个会同步降低。

<Info>
  推送频率调节适用于 `joint_states`、`joint_diagnostics`、指尖触觉与 `imu` 流。固件与 Wuji SDK 需同批升级到支持该能力的版本，版本要求见对应发布记录。
</Info>

```python theme={null}
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()
```

回调模式：

```python theme={null}
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()
```

<Info>
  `position` 为关节侧角度（rad），`velocity` 为关节侧角速度（rad/s），这是获取关节角与关节速度的唯一推荐路径。帧变长、仅含在线关节，用每条的 `nid` 定位关节。`joint_diagnostics` 流不包含关节角。
</Info>

### joint\_command（PUB）

**关节命令**：发布 20 关节的位置 / 速度 / 力矩前馈。每次发送恰好 20 个 `JointCommand`，每个携带该关节的 `position` / `velocity` / `effort`。

```python theme={null}
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 的[手部重定向](/docs/wuji-sdk/retargeting/)。

### 指尖触觉数据流（SUB）

<Info>
  指尖触觉需要配备指尖触觉传感器的 Wuji Hand 2.2 (Beta) 硬件，以及固件 v2.1.0 与 SDK v2026.7.21 及以上版本配套。
</Info>

**自描述的按指传感器数据流**：先获取该指的 `FingertipSensorInfo` 元数据（其 `format` JSON 描述数据帧布局），再订阅该指的 `FingertipSensorData` 流并按 format 解码。拇指 40 个触点，其余手指 34 个，原生输出率 100 Hz，可通过订阅句柄调低。

<Warning>
  固件 v2.4.0 起，每个触点的分点力改为按传感器满量程归一化：法向轴取 0 到 1，两个切向轴取 -1 到 1，超出满量程的读数截断到边界。v2.4.0 之前的固件把分点力按牛顿上报。三轴合力仍以牛顿为单位，温度仍以摄氏度为单位，不受此变更影响。

  解码时从 `info.format` 读取 `scale` 与 `unit`，不要写死其中一种。固件升级后 `info` 的 digest 会变，示例中的 digest 比对会重建解码器，新量程随之生效。归一化需要 Wuji SDK v2026.8.17 及以上。
</Warning>

```python theme={null}
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/`](https://github.com/wuji-technology/wuji-sdk/tree/main/examples/python/wuji_hand_2) 下的 `3.fingertip_typed.py` 示例。

### 指尖触觉标定（EXEC）与状态查询（GET）

**零基线重标定**：一次调用对全部 5 个指尖做零基线重标定。

```python theme={null}
hand.tactile_calibrate()      # 整手动作，遇到离线手指立即报错
```

<Warning>
  调用时必须保持所有传感器表面无负载（无接触力），否则标定出的零基线不正确。
</Warning>

调用成功表示标定命令已下发到每个手指，不代表标定已完成。用状态查询轮询确认：

```python theme={null}
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` 即标定完成。

### Flash 日志导出（诊断）

```python theme={null}
# 该调用会阻塞，直至导出完成
result = hand.export_flash_logs("./logs")
print(result["path"])      # str：导出 JSONL 文件的绝对路径
print(result["frames"])    # int：导出的日志记录数
```

SDK 会在 `out_dir` 中写入一个 `flash_<serial>_<YYYY-MM-DD_HHMMSS>.jsonl` 文件。省略 `out_dir` 时，文件保存到 `~/.wuji/logs`。

SDK 先写入临时文件，导出完成后再将其保存为 JSONL 文件。导出失败时，SDK 会自动清理临时文件，输出目录中不会出现残缺的导出结果。

<Info>
  多个会话同时导出同一设备的日志时，SDK 会自动重试。若重试后仍无法完成，本次导出会报设备忙。SDK 会清除临时文件，不会保留未完成的日志。
</Info>

<Warning>
  日志导出要求设备固件和 Wuji SDK 使用兼容版本。版本不匹配时，导出会报错且不会生成日志文件。请同步升级设备固件和 Wuji SDK 后重试。
</Warning>

### ip（SET/GET）

**读写设备静态 IP 地址**。`set` 把新 IP 写入设备 flash，固件不会热切换正在运行的以太网栈，新 IP 下次重启才生效。`set` 之后、重启之前，`get` 仍返回当前生效的 IP。

```python theme={null}
hand.ip().get()                    # 读当前 IP，如 "192.168.1.110"
hand.ip().set("192.168.1.201")     # 写入 flash，重启后生效
```

IP 在同网段内必须唯一，避开其他设备与电脑网卡。

修改 IP 分四步：`set` 写入新地址、`reboot` 重启设备、按新 IP 重连、`get` 确认。

```python theme={null}
hand.ip().set("192.168.1.201")
hand.reboot()
manager.disconnect_all()
# 等设备重启且以太网就绪，约 8 秒
hand = manager.connect(address="192.168.1.201:50001", device_name="wuji_hand_2")
hand.ip().get()                    # → "192.168.1.201"
```

<Warning>
  新 IP 重启后才生效。`set` 只写入 flash，不改变正在运行的连接。重启前 `get` 返回的仍是当前 IP。
</Warning>

### reboot（EXEC）

**重启设备**。设备会断连，重启后需重新连接。常与 `ip().set()` 搭配，让写入 flash 的新 IP 生效。

```python theme={null}
hand.reboot()
```

### imu（SUB）

**板载 IMU 流**：以 100 Hz 的原生输出率推送加速度与角速度，可通过订阅句柄调低。设备不做板载姿态融合，`orientation` 字段不可用，按 ROS `sensor_msgs/Imu` 约定以 `orientation_covariance[0] = -1` 标记朝向无效。数据按 IMU 传感器自身坐标系输出，各轴与手部结构的对应关系将随手部模型后续更新明确。

```python theme={null}
sub = hand.imu().subscribe()                 # → Subscription[ImuData]
s = await sub.recv_async()
a, g = s.linear_acceleration, s.angular_velocity   # m/s² 与 rad/s
print(s.header.seq, a.x, a.y, a.z, g.x, g.y, g.z)
sub.close()
```

`ImuData` 是跨设备通用类型，字段定义见 [Wuji SDK 数据类型参考](/docs/wuji-sdk/data-reference/#imudata)。

## 关节遍历

`JointHandle` 提供 `label` / `index`（用于与 `effort_limit()` / `mit_params()` 等 20 元素返回数组做下标对应），以及单关节资源与动作。`FingerHandle` 用于按手指遍历关节。

| 方法 | 返回类型 | 说明 |
| - | - | - |
| `hand.joints()` | `list[JointHandle]` | 全部 20 个关节 |
| `hand.fingers()` | `list[FingerHandle]` | 全部 5 根手指 |

```python theme={null}
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", ...
```

### 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()` 解码。返回整数，直接打印时自行按 `f"0x{code:04X}"` 格式化 |
| `status_word()` | 资源（GET） | 单关节状态字 |
| `enable()` / `disable()` | 动作 | 单关节使能 / 失能 |
| `clear_fault()` | 动作 | 清除该关节故障 |
| `set_origin()` / `clear_origin()` | 动作 | 设置 / 清除该关节用户零点 |

### FingerHandle

**手指句柄**：遍历该手指的 4 个关节。

| 方法 | 返回类型 | 说明 |
| - | - | - |
| `joints()` | `list[JointHandle]` | 该手指 4 个关节，按 S1..S4 顺序 |

## 数据类型

### JointDiagnosticsFrame / JointDiagnosticsEntry

**关节诊断帧**：`hand.joint_diagnostics().subscribe()` 流的帧类型。变长，仅含在线关节。

```python theme={null}
class JointDiagnosticsFrame:
    header: FrameHeader                    # seq / timestamp_us / frame_id
    num_joints: int
    joints: list[JointDiagnosticsEntry]
    comm: Hand2CommSummary                 # 帧级通信健康摘要，1 Hz 刷新（见 Hand2CommSummary）

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                # 当前错误码（整数值，打印条目对象时按 16 进制显示），用 describe_error() 解码
    comm_response_rate_pct: int            # 该关节最近 1 秒的总线响应率，0–100
    comm_timeout_total: int                # 该关节累计总线超时次数
```

### MitParam

**MIT 参数**：`mit_params().get()` 返回元素。

```python theme={null}
class MitParam:
    kp: float
    kd: float
```

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

### JointStateFrame

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

| 属性 | 类型 | 说明 |
| - | - | - |
| `header` | `FrameHeader` | `seq` / `timestamp_us` / `frame_id` |
| `num_joints` | `int` | 本帧关节数量 |
| `joints` | `list[JointStateEntry]` | 关节状态数组 |

### JointStateEntry

**单关节状态**。

| 属性 | 类型 | 说明 |
| - | - | - |
| `nid` | `int` | 节点编号，跨帧定位关节 |
| `position` | `float` | 位置（rad，关节侧） |
| `velocity` | `float` | 速度（rad/s） |
| `effort` | `float` | 力矩 (A) |

### JointCommand

**关节命令**：`joint_command().publish().send()` 入参元素。

```python theme={null}
class JointCommand:
    position: float    # 位置 (rad)
    velocity: float    # 速度 (rad/s)
    effort: float      # 力矩前馈 (A)
```

### HandCommunicationDiagnostics

**通信诊断数据**。

```python theme={null}
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
```

### FingertipSensorInfo / FingertipSensorData

**指尖传感器元数据与数据帧**：`get_fingertip_info(finger)` 返回值与 `fingertip_{finger}_data` 订阅帧。

```python theme={null}
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 解释
```

### TactileStatus / TactileType / TactileState

**触觉状态**：`hand.tactile_status(finger)` 返回值。

```python theme={null}
class TactileStatus:
    model: TactileType     # Standard（34 点标准指）或 Thumb（40 点拇指）
    state: TactileState    # Ready（就绪）或 Calibrating（标定中）
```

枚举成员与固件整数等值比较，如 `TactileState.Calibrating == 1`。

### StatusWord

**状态字**：[JointDiagnosticsEntry](#jointdiagnosticsframe-/-jointdiagnosticsentry) 的 `status_word` 字段类型，SDK 暴露的公开状态视图。

| 字段 | 类型 | 说明 |
| - | - | - |
| `ext_state` | `int` | 扩展状态值（如 Init / Ready / Enabled / Stopped） |
| `ext_state_name` | `str` | `ext_state` 的语义名 |
| `position_limit_active` | `bool` | 位置限幅触发 |
| `velocity_limit_active` | `bool` | 速度限幅触发 |
| `current_limit_active` | `bool` | 电流限幅触发 |

### Hand2CommSummary

**帧级通信健康摘要**：[JointDiagnosticsFrame](#jointdiagnosticsframe-/-jointdiagnosticsentry) 的 `comm` 字段类型，同时覆盖设备内部总线与 SDK 本地端到端（以太网）两段。设备内部快照由 SDK 常驻任务以 1 Hz 自动刷新，无需手动轮询。

| 字段 | 类型 | 说明 |
| - | - | - |
| `age_ms` | `int` | 设备内部快照距今毫秒数。`65535` = 从未成功采样（设备内部字段均为 0）。`65534` = 已饱和，快照距今 ≥ 65.5 秒 |
| `tactile_online_mask` | `int` | 指尖触觉在线位图，bit0 = 拇指 … bit4 = 小指 |
| `e2e_received` | `int` | 全部订阅流累计收帧数 |
| `e2e_lost` | `int` | 以太网段经序号缺口检测出的累计丢帧数 |
| `e2e_reordered` | `int` | 累计乱序/迟到帧数 |
| `e2e_duplicates` | `int` | 累计重复帧数 |
| `e2e_window_loss_x100` | `int` | 最近 1 秒窗口丢包率，0.01% 单位 |
| `rpc_total` | `int` | 累计请求数。当前连接不提供 RPC 统计时，此字段为 `0`。此时，`rpc_retries` 和 `rpc_timeouts` 不表示实际重试或超时次数 |
| `rpc_retries` | `int` | 累计请求重传次数 |
| `rpc_timeouts` | `int` | 累计最终超时的请求数 |
| `comm_get_failures` | `int` | 累计内部快照刷新失败次数 |
| `tactile_response_rate_pct` | `list[int]` | 每指触觉（node 5）总线响应率，0–100，索引 0 = 拇指 … 4 = 小指 |
| `tactile_timeout_total` | `list[int]` | 每指触觉累计总线超时次数，同索引序 |
| `sdk_dropped` | `int` | SDK 内部某一跳落后而丢弃的帧数（累计）——可能是应用侧的订阅消费端，也可能是 SDK 内部的流 handler。与网络丢帧 `e2e_lost` 语义不同 |

<Info>
  **设备内部字段的判读**：`age_ms`、`tactile_*` 以及每关节的 `comm_*` 字段同属一份快照，由 SDK 以 1 Hz 刷新。固件升级或触觉标定进行期间会**有意跳过**这次刷新，因此短暂的陈旧值属预期状态而非故障。用 `comm_get_failures` 区分两种情况：`age_ms` 增大而 `comm_get_failures` 不变 = 正在有意跳过，`comm_get_failures` 持续增加 = 刷新本身在失败。
</Info>

<Warning>
  **计数器饱和，不回绕**：本表所有计数达到上限后即停在上限（16 位的 `e2e_reordered`、`e2e_duplicates`、`rpc_retries`、`rpc_timeouts`、`comm_get_failures` 停在 65535，32 位的停在 4294967295），不会归零重新计数。1 kHz 流长时间运行时确实可能触顶，因此应按**两次采样之间的增量**判读，而非当作全生命周期的绝对累计值。
</Warning>

## 关节编号

| 手指 | 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`）。

## 异常处理

所有 SDK 操作错误统一抛 `WujiException`，消息中携带错误类型前缀（`Disconnected`、`Timeout`、`PathNotFound`、`SchemaMismatch`、`SerializeError`、…）。业务侧按需 `try / except` 即可。

```python theme={null}
from wuji_sdk import WujiException

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.