> ## 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.

# 运行 MIT 关节扫描

## 概述

使能灵巧手，向关节 0 发送一段固定的余弦位置序列，然后失能断开。整个过程 2 秒。

这是两个运动示例里幅度最小的一个，用途是**验证指令链路通不通**，不是观察手指动作。关节 0 的余弦幅度只有 0.02 rad，约合 1.15 度，实测在静摩擦下几乎不产生可见位移。

**但它的实际运动幅度取决于开始时的手部姿态。** 每帧发送的是完整的 20 维指令，关节 0 之外的 19 个目标位置全部是 0。手若原本是弯曲的，第一帧就会把这 19 个关节一起拉向零位，而且**不做插值缓冲**。实测中一次运行把 nid 19 从 0.6498 rad 拉到 0.0143 rad，实际动了约 36 度，远超关节 0 那 1.15 度。

对应的 Python 示例为 [7.mit\_sweep.py](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_hand_2/7.mit_sweep.py)，C 示例为 [7\_mit\_sweep.c](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_hand_2/7_mit_sweep.c)。

MIT 控制模式与参数安全边界见[控制指南](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/control-guide)，力矩与 MIT 参数的接口见[力矩与 MIT 参数](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/sdk-reference/effort-and-mit)，本页只讲怎么跑起来与实测结果。

> [!NOTE]
>
> 本示例会使能电机。开始前清空手部周围的人员、线缆与台面物品，全程有人监护，并保持随时断电的条件。
>
> 手部原本不在零位时，第一帧就会把 20 个关节一起拉向零位，没有插值缓冲。需要柔和归零请先运行[发布控制指令](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/sdk-practice/publish-command)的平滑归零流程。

## 前置条件

### 硬件

| 项目 | 要求 |
| - | - |
| Wuji Hand 2 | 1 只，**20 个关节必须全部在线**，否则示例直接报错退出 |
| 连接方式 | 以太网 |
| 网络 | **网络上只能有这一台设备**，原因见下方运行时分支 |

### 软件

| 项目 | 要求 |
| - | - |
| 操作系统 | Ubuntu 22.04 LTS 及以上版本 |
| Wuji SDK | v2026.8.31 或更高版本 |
| Python | 3.10 及以上版本 |
| C 工具链 | CMake 3.16 及以上版本，以及解压后的 SDK 包 |

本示例不接受任何命令行参数。

### 前序步骤

确认设备的 MIT 参数与力矩上限处于出厂值。实测值：

```text theme={null}
mit_params:    kp=3, kd=0.05   （20 个关节相同）
effort_limit:  1.5             （20 个关节相同）
```

`kp` 被改小会让本就微弱的位移进一步消失，改大则超出本示例的安全设定。

## 数据流

```mermaid theme={null}
flowchart TD
    GEN["generate_sweep()<br/>本地生成 101 帧余弦扫描"]
    PUB["joint_command().publish()<br/>发布器"]
    SEND["每帧 send() 20 个 JointCommand<br/>间隔 20 ms"]
    FW["MIT 阻抗控制<br/>kp=3，kd=0.05，力矩上限 1.5"]
    J["关节 0 走余弦<br/>其余 19 关节被拉向零位"]

    GEN -->|"101 帧，仅关节 0 非零"| PUB
    PUB --> SEND
    SEND -->|"约 50 Hz"| FW
    FW -->|"力矩 = kp × 位置偏差"| J
```

序列在本地一次生成完毕再逐帧发送，不做插值也不做重规划。每条 `JointCommand` 是「位置 + 速度 + 力矩」三元组，本示例只填位置，速度与力矩都是 0。

注意每帧都是完整的 20 维指令。关节 0 之外的 19 个位置写死为 0，等于持续命令它们回到零位，这部分运动不受 0.02 rad 这个幅度约束。

## 运行时分支

| 分支点 | 表现 | 处理 |
| - | - | - |
| **网络上有多台设备** | 示例用 `auto_connect()`，实测抛 `Found 6 devices, please specify SN to connect`，**直接退出，不会误连** | 共用网络必须改成指定 SN，见调整与适配 |
| 在线关节不足 20 | 抛 `expected 20/20 online joints, got N/20`，不使能 | 先排查离线关节 |
| 使能失败 | 抛 `enable failed: ...`，不发任何指令 | 查故障码，必要时 `clear_fault()` |
| Ctrl+C | 退出码 130，停止后续指令，**不做回位动作** | 手停在当前位置，随后被失能 |
| 运行时异常 | 退出码 1 | 同样进入清理流程 |
| 正常结束 | 退出码 0 | 发完最后一条即失能 |

无论哪条路径，清理流程都会执行失能、关闭发布器、断开设备。异常与中断都**不会**添加恢复动作，手不会自动回到初始姿态。

## 调用顺序

> [tab] Python
>
> ```python theme={null}
> import math, time, wuji_sdk
>
> manager = wuji_sdk.SdkManager.instance()
>
> # 1. 连接。多设备环境改用 connect(sn=...)
> hand = manager.auto_connect(device_name="hand_2_mit_sweep")
>
> # 2. 门控：20 关节必须全在线
> if int(hand.online_joints_count().get()) != 20:
>     raise RuntimeError("expected 20/20 online joints")
>
> # 3. 使能
> hand.enable()
>
> # 4. 取发布器
> publisher = hand.joint_command().publish()
>
> # 5. 逐帧发送，20 ms 一条
> for i in range(101):
>     phase = 2.0 * math.pi * i / 100
>     position = [0.0] * 20
>     position[0] = 0.02 * 0.5 * (1.0 - math.cos(phase))
>     publisher.send([wuji_sdk.JointCommand(v, 0.0, 0.0) for v in position])
>     if i < 100:
>         time.sleep(0.02)
>
> # 6. 清理：失能 → 关发布器 → 断开
> hand.disable()
> publisher.close()
> manager.disconnect_all()
> ```

> [tab] C
>
> ```c theme={null}
> #include "wuji_sdk.h"
>
> /* 1. 初始化并连接 */
> wuji_init(&opts);
> wuji_scan(&list, &n);
> wuji_connect(&tgt, "hand2", &copts, &dev);
>
> /* 2. 门控：20 关节必须全在线 */
> uint32_t online = 0;
> wuji_hand_2_online_joints_count(dev, &online);
> if (online != 20) { /* 报错退出，不使能 */ }
>
> /* 3. 使能 */
> wuji_hand_2_enable(dev);
>
> /* 4. 取发布器 */
> struct WujiPub *pub = NULL;
> wuji_hand_2_joint_command_publisher(dev, &pub);
>
> /* 5. 逐帧发送 */
> WujiJointCommand cmd[20] = {0};
> for (int i = 0; i <= 100; i++) {
>     double phase = 2.0 * M_PI * i / 100.0;
>     cmd[0].position = 0.02 * 0.5 * (1.0 - cos(phase));
>     wuji_hand_2_publish_joint_command(pub, cmd, 20);
>     if (i < 100) usleep(20000);
> }
>
> /* 6. 清理 */
> wuji_hand_2_disable(dev);
> wuji_pub_close(pub);
> wuji_dev_disconnect(dev);
> wuji_dev_release(dev);
> wuji_shutdown();
> ```

顺序不可换。门控在使能之前，失能在关发布器之前。

## 运行与输出

> [tab] Python
>
> ```bash theme={null}
> python examples/python/wuji_hand_2/7.mit_sweep.py
> ```

> [tab] C
>
> ```bash theme={null}
> ./build/hand2/7_mit_sweep
> ```

**正常结束时没有任何标准输出**，退出码 0。只有出错才打印到标准错误。安静跑完两秒就是成功。

### 实测数据

在 20 关节全在线的右手 Wuji Hand 2 上实跑，同步订阅 `joint_states` 记录关节 0 的响应：

| 指令位置 | 实际位置 | 实际力矩 |
| - | - | - |
| +0.0000 | +0.0050 | −0.0030 |
| +0.0100 | +0.0046 | +0.0245 |
| +0.0200 | +0.0045 | **+0.0803** |
| +0.0100 | +0.0046 | +0.0392 |
| +0.0000 | +0.0045 | −0.0219 |

| 指标 | 实测 |
| - | - |
| 指令条数与耗时 | 101 条，2.016 秒 |
| 实际发送频率 | 49.9 Hz |
| 指令峰值 | 0.0200 rad |
| **关节实际位移范围** | **0.0004 rad** |
| 力矩变化范围 | 0.1384 |
| `enable()` 耗时 | 0.001 秒 |

**关节 0 几乎不动，这是正常的。** 力矩严格跟随指令上升与回落，说明指令已经下发、电机已按 MIT 阻抗响应。但 0.02 rad 的目标偏差在 `kp=3` 下只产生约 0.06 的力矩，克服不了关节静摩擦，实际位移只有 0.0004 rad，约合 0.023 度，肉眼不可见。

上表是手已处于零位附近时的数据。**另一次运行的对照**：手部原本弯曲时跑同一个示例，其余 19 个关节被拉向零位，实测位移如下。

| nid | 运行前 | 运行后 | 位移 |
| - | - | - | - |
| 7 | +0.5018 | −0.0018 | −0.5036 |
| 9 | +0.6070 | +0.0382 | −0.5688 |
| 19 | +0.6498 | +0.0143 | **−0.6355** |
| 22 | +0.5375 | +0.0652 | −0.4723 |

nid 19 动了 0.6355 rad，约合 36 度。所以「幅度最小」只在手已经归零时成立。

判断本示例成功的依据是**进程安静退出且退出码为 0**，以及订阅 `joint_states` 能看到力矩响应，而不是看到手指摆动。要观察真实运动请看[回放拇指对指动作](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/sdk-practice/opposition-replay)。

## 调整与适配

### 多设备网络下必须指定 SN

`auto_connect()` 的语义是「连接总线上发现的唯一设备」。网络上不止一台时它拒绝连接并列出全部 SN，不会盲选。实测报错：

```text theme={null}
Internal error: Found 6 devices, please specify SN to connect.
Available: WG1KA00XXXXXXXXX, WH2KA00XXXXXXXXX, DF1XE00XXXXXXXXX, ...
```

这个设计避免了误动他人设备，代价是共用网络里示例无法直接运行。替换第 1 步：

```python theme={null}
hand = manager.connect(sn="WH2KA00XXXXXXXXX", device_name="hand_2_mit_sweep")
```

### 想看到关节真的动

本示例不适合观察运动。要看幅度，用[回放拇指对指动作](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/sdk-practice/opposition-replay)的完整运动序列。自行加大本示例幅度会越过其安全设定，不建议。

### 读取关节状态的方式

`joint_states` 是订阅类资源，**没有 `get()` 方法**。想在发指令的同时读实际位置，要先 `subscribe_with_callback()` 持续接收，再从最新帧取值。直接调用 `hand.joint_states().get()` 会抛 `AttributeError`。

### 改变指令频率

命令间隔写死 20 ms。缩短间隔会提高指令率，但本示例的固定序列共 101 帧，缩短只是让整段更快走完，不改变轨迹形状。

## 相关内容

* [拇指对掌回放](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/sdk-practice/opposition-replay)：幅度更大的完整四指运动
* [发布控制指令](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/sdk-practice/publish-command)：`joint_command` 的通用用法
* [数据订阅：回调](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/sdk-practice/subscribe-callback)：本文实测数据的采集方式
* [数据类型参考](https://docs.wuji.tech/docs/zh/wuji-sdk/latest/data-reference.md)：`JointCommand` 与关节状态结构


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