> ## 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 用户与标定数据

设备参数常见的有标识类（序列号、固件版本、手别）和网络类（IP 地址、数据端口）。凡经 Wuji SDK 连接的设备（Wuji Hand 2、Wuji Glove 等）都用统一的资源式 API 访问这些参数。

本页覆盖四套跨设备通用机制：参数读写、SDK 用户管理与数据导入导出、标定数据的本地存放与用户隔离、资源内省。各产品支持的参数清单与设备操作见对应产品文档，页内各节给出指引。

## 参数读取

通过语义化 API 读取设备属性：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sn = glove.sn().get()                # 序列号
    version = glove.version().get()      # 固件版本
    hand_side = glove.hand_side().get()  # 手别（"left" 或 "right"）
    ip = glove.ip().get()                # IP 地址
    port = glove.port().get()            # 数据端口
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // 字符串参数用两步查询：先探长度，再分配缓冲填充
    size_t needed = 0;
    wuji_glove_get_sn(glove, NULL, 0, &needed);
    char *sn = malloc(needed);
    wuji_glove_get_sn(glove, sn, needed, NULL);   // 序列号

    uint16_t port = 0;
    wuji_glove_get_port(glove, &port);            // 数据端口

    // 固件版本、手别与 IP 地址同样是两步查询
    // wuji_glove_get_version / wuji_glove_get_hand_side / wuji_glove_get_ip

    free(sn);
    ```
  </Tab>
</Tabs>

## 参数写入

部分参数支持写入：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    glove.ip().set("192.168.1.200")     # 修改 IP 地址
    glove.port().set(50001)             # 修改数据端口
    ```

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

  <Tab title="C">
    ```c theme={null}
    wuji_glove_set_ip(glove, "192.168.1.200");   // 修改 IP 地址
    wuji_glove_set_port(glove, 50001);           // 修改数据端口
    ```
  </Tab>
</Tabs>

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

<Warning>
  修改网络参数（IP 地址、端口）后可能需要重新连接设备。
</Warning>

## 设备操作

参数持久化、重启等设备操作因产品而异：

| 产品 | 设备操作 |
| - | - |
| Wuji Hand 2 | [Wuji Hand 2 SDK 接口参考 — reboot](/docs/wuji-hand/latest/sdk-reference/device-operations/#reboot（exec）) |
| Wuji Glove | [Wuji Glove 设备操作](/docs/wuji-glove/sdk-data-reference/device-operations/) |
| Wuji Hand | 用通用 API 执行，如 `hand.execute("reboot")`，操作项以 [`resources()`](#查看所有资源) 枚举为准（产品文档已归档） |

## SDK 用户管理

不同操作者共用同一台设备时，各自的标定参数和本地参数会互相覆盖。SDK 支持本机用户隔离，按 `~/.wuji/sdk/users/<user_id>/` 分目录存放每位用户的参数与标定文件。

不创建用户时，参数文件与触觉标定直接保存到 `~/.wuji/sdk/params/`、`~/.wuji/sdk/tactile/`，位姿标定则完全不可用。需要按操作者隔离、或需要位姿标定时，创建具名用户：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from wuji_sdk import SdkManager

    manager = SdkManager.instance()

    # 创建用户
    alice = manager.create_user("Alice", description="右手操作员")

    # 切换到指定用户
    manager.switch_user(alice["user_id"])

    # 查看当前用户
    print(manager.current_user())

    # 列出所有用户
    for user in manager.list_users():
        marker = "*" if user["is_default"] else " "
        print(f"{marker} {user['display_name']} ({user['user_id']})")

    # 切回默认用户
    manager.switch_to_default_user()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // 创建用户
    WujiUserInfo alice = {0};
    wuji_create_user("Alice", "右手操作员", /*external_id=*/NULL, &alice);

    // 切换到指定用户
    WujiUserInfo current = {0};
    wuji_switch_user(alice.user_id, &current);

    // 列出所有用户
    WujiUserInfo *users = NULL;
    size_t n = 0;
    wuji_list_users(&users, &n);
    for (size_t i = 0; i < n; i++) {
        printf("%c %s (%s)\n", users[i].is_default ? '*' : ' ',
               users[i].display_name, users[i].user_id);
    }
    wuji_user_info_array_free(users, n);

    // 切回默认用户
    WujiUserInfo def = {0};
    wuji_switch_to_default_user(&def);

    wuji_user_info_free(&alice);
    wuji_user_info_free(&current);
    wuji_user_info_free(&def);
    ```
  </Tab>
</Tabs>

切换用户后，已连接的设备立即重新加载该用户的标定参数，无需重连。删除当前用户会自动切回默认用户。

用户创建、切换、重命名和删除的实测边界，以及跨进程与跨语言的持久化表现见 [SDK 本地用户管理](/docs/wuji-sdk/sdk-practice/user-management)。

## 用户数据导入导出

一位 SDK 用户的全部数据可以打包成一个 zip 文件，用于备份或在主机之间迁移。包内含该用户的手部标定模型、每台设备的触觉模型，以及最新一次完整的触觉标定数据。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # 导出：把该用户的全部数据写入一个 zip 文件
    report = manager.export_user_data(alice["user_id"], "alice.zip")
    print(report["out_path"], report["device_sns"])

    # 预览：只校验包内容，不写盘
    preview = manager.preview_user_data("alice.zip")
    print(preview["owner_display_name"], preview["created_at"])
    for component in preview["components"]:
        print(component["name"], component["state"], component.get("reason"))

    # 导入：恢复到包内记录的所属用户
    result = manager.import_user_data("alice.zip")
    print(result["owner_user_id"], result["imported_at"])
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // 导出：user_id 传 "" 表示默认用户，路径必须以 .zip 结尾
    if (wuji_user_data_export(alice.user_id, "alice.zip") != WUJI_STATUS_OK) {
        fprintf(stderr, "export failed: %s\n", wuji_last_error());
    }

    // 预览：跑完整校验但不写盘，返回 OK 表示这个包可以导入
    if (wuji_user_data_preview("alice.zip") != WUJI_STATUS_OK) {
        fprintf(stderr, "bundle rejected: %s\n", wuji_last_error());
    }

    // 导入：恢复到包内记录的所属用户
    wuji_user_data_import("alice.zip");
    ```
  </Tab>
</Tabs>

Python 的三个方法都返回报告字典，`components` 逐项列出组件状态：

| 字段 | 说明 |
| - | - |
| `name` | 组件名，如 `hand_model.left`、`tactile.model`、`tactile.run` |
| `state` | `present` 组件已包含并写入，`missing` 用户没有这项数据，`skipped` 包里有但本次未写入 |
| `reason` | `missing` 或 `skipped` 时给出原因 |
| `device_sn` | 设备级组件对应的设备序列号 |
| `path` | 组件在包内或落盘后的路径 |

导入行为：

* 包内记录的所属用户不存在时自动创建，当前用户不变
* 除被跳过的组件外，每个 `present` 组件都会写入，覆盖该用户已有的同名文件
* 跳过优先于写入：被跳过的组件不落盘、不覆盖已有文件，报告里的 `state` 为 `skipped`。所属用户是默认用户时，手部模型即按此跳过（默认用户不加载本地手部标定），其余组件照常写入
* 校验（路径安全、大小上限、sha256、数据格式、所属用户）在写盘前全部完成，任一项失败直接抛异常，不写入任何文件

<Info>
  导出路径必须以 `.zip` 结尾，父目录不存在时自动创建。C 的三个接口只返回状态码，逐组件明细不跨 FFI 传递，需要按组件查看结果时改用 Python 接口。包文件不存在返回 `WUJI_STATUS_ERR_NOT_FOUND`，校验不通过返回 `WUJI_STATUS_ERR_INVALID_DATA`。
</Info>

### 触觉数据路径

触觉模型由 [Wuji CLI 触觉接触标定](/docs/wuji-cli/tactile-calibration/) 训练产出，与标定数据一起按 SDK 用户和设备序列号存放：

| SDK 用户 | 触觉目录 |
| - | - |
| 默认用户 | `~/.wuji/sdk/tactile/<sn>/` |
| 具名用户 | `~/.wuji/sdk/users/<user_id>/tactile/<sn>/` |

目录下 `model/` 存训练好的触觉模型（`contact.safetensors` 与 `contact.npz`），`calibration-data/` 存原始标定数据，每次标定一个子目录。一次完整的标定数据包含 4 段引导动作各一个数据文件，导出只取最新一次完整的标定数据。

```python theme={null}
sn = "WG1JA00XXXXXXXXX"

print(manager.tactile_dir(alice["user_id"], sn))
print(manager.tactile_model_paths(alice["user_id"], sn))  # dir / safetensors / npz
print(manager.tactile_run_dir(alice["user_id"], sn))
```

## 标定

标定是把 SDK 算法适配到具体物理设备的过程，结果落盘到 SDK 本地参数与模型文件，设备订阅时自动加载。

标定文件先按当前 SDK 用户隔离，再分三类存放：

| SDK 用户 | 参数文件（按设备序列号） | 触觉目录（按设备序列号） | 位姿手部模型（按左右手） |
| - | - | - | - |
| 默认用户 | `~/.wuji/sdk/params/<sn>.toml` | `~/.wuji/sdk/tactile/<sn>/` | 不保存、不加载，使用内置默认模型 |
| 具名用户 | `~/.wuji/sdk/users/<user_id>/params/<sn>.toml` | `~/.wuji/sdk/users/<user_id>/tactile/<sn>/` | `~/.wuji/sdk/users/<user_id>/models/` |

参数文件与触觉目录仍按设备序列号细分。触觉目录下 `model/` 存训练好的接触模型，`calibration-data/` 存最新一次标定数据。

位姿手部 URDF 只按 SDK 用户与左右手保存（如 `left_hand.urdf` / `right_hand.urdf`），不再包含设备序列号与手型信息，同一用户下任意同侧设备共享同一模型。默认用户没有位姿手部模型：标定与导入都不会写入，实时 IK 始终回退到内置默认模型。Wuji Glove 的具体行为见 [Wuji Glove 标定](/docs/wuji-glove/sdk-data-reference/calibration/)。

切换 SDK 用户时已连接设备自动重载对应文件，无需重连。

<Warning>
  默认用户下标定能力受限，多人共用同一台设备时务必先切换到具名用户：

  * 默认用户调用 `calibrate` 直接失败，返回 `ExecuteError(operation="calibrate", code=0x4108)`。先 `SdkManager.create_user(...)` + `switch_user(...)` 切换到具名用户后再标定，生成的 URDF 才会按用户隔离保存
  * 默认用户设置自定义手部 URDF 直接失败：语义 API `glove.hand_model_path().set(path)` 与通用写入口（`device.set` / `manager.set`）写 `calibration.hand_model_path` 都返回 `UnsupportedOperation`，参数存储保持不变，按错误提示切换到具名用户后再设置。语义 API 还会校验路径，文件不可读时返回 `InvalidConfig` 且不保存
  * 默认用户下，IK 衍生流（`hand_skeleton` / `hand_joint_angles` / `tip_poses`）始终使用 SDK 内置默认 URDF，不读取本地已保存的 `calibration.hand_model_path`，避免未显式切换用户时意外复用他人标定结果
</Warning>

<Info>
  * **何时需要标定**：首次使用一台设备、更换穿戴者（针对可穿戴设备）、上层模型或算法代际切换、标定精度明显退化。具体触发条件因产品而异
  * **失败行为**：标定失败时 SDK 保留上一次成功的结果，本轮新文件自动回滚，可直接重试。常见失败场景：超时、算法不收敛、文件持久化失败、中途切换 SDK 用户。具体异常类型见各产品文档
  * **备份与迁移**：用 [用户数据导入导出](#用户数据导入导出) 打包整位用户的标定结果，导入时 SDK 按目标主机的用户目录重建路径。也可以整目录拷贝，但参数表里可能存有指向 `~/.wuji/sdk/` 下其他文件的绝对路径（如模型文件），跨机器迁移到不同 home 路径时需同步更新或重新标定
</Info>

各产品标定流程见产品文档：

* Wuji Hand 2 指尖触觉标定（设备侧执行，不写入本节所述 SDK 本地标定文件）见 [Wuji Hand 2 指尖触觉 — 零基线标定与状态查询](/docs/wuji-sdk/wuji-hand-2-tactile/#零基线标定与状态查询)
* Wuji Glove 标定流程和 API 见 [Wuji Glove 标定](/docs/wuji-glove/sdk-data-reference/calibration/)

## 可用参数

| 产品 | 参数表 |
| - | - |
| Wuji Hand 2 | [Wuji Hand 2 SDK 接口参考 — 设备操作](/docs/wuji-hand/latest/sdk-reference/device-operations/) |
| Wuji Glove | [Wuji Glove 可用参数](/docs/wuji-glove/sdk-data-reference/device-operations/#可用参数) |
| Wuji Hand | 用本页 [`params()`](#查看所有资源) 枚举（产品文档已归档） |

## 查看所有资源

列出设备上所有可用的资源、参数和 Topic：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # 所有资源
    for res in glove.resources():
        print(res.path)

    # 可读写参数
    for param in glove.params():
        print(param.path)

    # 可订阅 Topic
    for topic in glove.topics():
        print(topic.path)
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // 可读写参数
    WujiParam *params = NULL;
    size_t n = 0;
    wuji_dev_list_params(glove, &params, &n);
    for (size_t i = 0; i < n; i++) printf("%s\n", params[i].path);
    wuji_param_array_free(params, n);

    // 可订阅 Topic
    WujiTopic *topics = NULL;
    size_t m = 0;
    wuji_dev_list_topics(glove, &topics, &m);
    for (size_t i = 0; i < m; i++) printf("%s\n", topics[i].path);
    wuji_topic_array_free(topics, m);
    ```
  </Tab>
</Tabs>

## 接口清单

| 操作 | Python | C |
| - | - | - |
| 读取 Wuji Glove 身份与网络参数 | `glove.sn().get()`、`glove.version().get()`、`glove.hand_side().get()`、`glove.ip().get()`、`glove.port().get()` | `wuji_glove_get_sn()`、`wuji_glove_get_version()`、`wuji_glove_get_hand_side()`、`wuji_glove_get_ip()`、`wuji_glove_get_port()` |
| 写入 Wuji Glove 网络参数 | `glove.ip().set(...)`、`glove.port().set(...)` | `wuji_glove_set_ip()`、`wuji_glove_set_port()` |
| 按路径读写任意参数 | `device.get(path)`、`device.set(path, value)` | `wuji_dev_get()`、`wuji_dev_set()`，收发原始协议字节，读出的缓冲用 `wuji_buffer_free()` 释放 |
| 执行设备操作 | `device.execute(path)` | `wuji_dev_execute()`，重启另有 `wuji_glove_reboot()` 与 `wuji_hand_2_reboot()` |
| 创建与删除用户 | `manager.create_user(...)`、`manager.delete_user(user_id)` | `wuji_create_user()`、`wuji_delete_user()` |
| 切换与查询用户 | `manager.switch_user(...)`、`manager.switch_to_default_user()`、`manager.current_user()`、`manager.list_users()` | `wuji_switch_user()`、`wuji_switch_to_default_user()`、`wuji_current_user()`、`wuji_list_users()` |
| 更新用户信息 | `manager.update_user(...)` | `wuji_update_user()`，经 `WujiUserUpdate` 只传要改的字段 |
| 导出、预览与导入用户数据 | `manager.export_user_data(...)`、`manager.preview_user_data(...)`、`manager.import_user_data(...)` | `wuji_user_data_export()`、`wuji_user_data_preview()`、`wuji_user_data_import()` |
| 查询触觉数据路径 | `manager.tactile_dir(...)`、`manager.tactile_model_paths(...)`、`manager.tactile_run_dir(...)` | 无对应接口，按本页给出的目录结构自行拼接 |
| 读写自定义手部模型路径 | `glove.hand_model_path().get()`、`glove.hand_model_path().set(path)` | `wuji_glove_get_hand_model_path()`、`wuji_glove_set_hand_model_path()` |
| 枚举资源、参数与 Topic | `device.resources()`、`device.params()`、`device.topics()` | 参数与 Topic 分别用 `wuji_dev_list_params()` 与 `wuji_dev_list_topics()` 枚举，没有合并列出全部资源的接口 |

C 的字符串读取接口统一采用两步查询：先传 `buf = NULL`、`buf_len = 0` 拿到所需字节数，再分配缓冲第二次调用填充。Python 返回报告字典的接口在 C 只返回 `WujiStatus`，逐组件明细不跨 FFI 传递。`WujiUserInfo` 与 `WujiUserUpdate` 的字段、内存归属与释放函数见 [C 接口参考](/docs/wuji-sdk/data-reference/)。


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