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

# 设备发现与连接

连接流程分三步：扫描局域网与本机 USB 得到设备清单，按序列号、地址或手性定位目标，再由 SDK 持有连接直至显式断开。Python 与 C 两侧语义一致，差异集中在错误处理与句柄生命周期。

## 设备发现

扫描局域网内以太网连接与本机 USB 接入的 Wuji 设备：

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

    manager = SdkManager.instance()
    devices = manager.scan()

    for dev in devices:
        print(f"SN: {dev.sn}, 类型: {dev.device_type}, 地址: {dev.address}")
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    #include "wuji_sdk.h"

    wuji_init(NULL);

    WujiDiscovered *devs = NULL;
    size_t count = 0;
    wuji_scan(&devs, &count);

    for (size_t i = 0; i < count; i++) {
        printf("SN: %s, 类型: %s, 地址: %s\n",
               devs[i].serial_number, devs[i].model, devs[i].address);
    }
    wuji_discovered_free(devs, count);
    ```
  </Tab>
</Tabs>

返回的设备记录包含序列号、地址和设备类型，连接前即可判断设备类型。Python 的 `DiscoveredDevice.device_type` 是 `DeviceType` 枚举（`WujiGlove` / `WujiHand2` / `WujiHand`），C 的 `WujiDiscovered.device_id` 是 `WujiDeviceType` 枚举（`WUJI_DEVICE_TYPE_WUJI_GLOVE` / `_WUJI_HAND_2` / `_WUJI_HAND`）。`Unknown` 与 `WUJI_DEVICE_TYPE_UNKNOWN` 表示扫描时未获取到类型或当前 SDK 版本不识别。

<Info>
  **网络前提**：以太网设备使用静态 IP，不使用 DHCP。扫描或连接前，把电脑上对应网卡设到同一网段（如 `192.168.1.50`，避开各设备默认地址），否则无法发现或连接设备。

  * **Wuji Hand 2**：左手 `192.168.1.110`，右手 `192.168.1.111`，地址详情与修改方式见 [Wuji Hand 2 SDK 参考 — 设备操作](/docs/wuji-hand/latest/sdk-reference/device-operations/)
  * **Wuji Glove**：左手 `192.168.1.100`，右手 `192.168.1.101`，配置步骤见 [Wuji Glove 使用前准备](/docs/wuji-glove/getting-started/#step-network)
  * **Wuji Hand**：USB 直连，无需网络配置
</Info>

## 连接设备

<Info>
  所有连接 API 中的 `device_name` 是**本地别名**，只用于后续按名取设备、组织资源路径，不参与设备选择，也不决定返回的句柄类型。返回类型由实际连上的设备型号决定。要求非空且不含 `/`、`.`。
</Info>

### 选择连接方式

按当前发现的设备数量选择，各 API 详见[自动连接](#自动连接)、[指定 SN 连接](#指定-sn-连接)、[指定地址连接](#指定地址连接)、[按 USB 连接](#按-usb-连接)与[按手性连接](#按手性连接)：

```text theme={null}
scan() 当前能发现几台设备？
│
├─ 恰好一台 ──▶ auto_connect()——USB / 以太网都适用
│
└─ 多台 ▶ connect() 显式指定——sn / address / handedness 互斥，三选一
     ├─ sn=...——任何设备，最精确（USB 设备可先按 TransportType.Usb 筛出）
     ├─ address=...——已知设备 IP 时直连
     └─ handedness=...——按左右手（Wuji Hand 2 / Wuji Glove）
          └─ 多台同手性 ▶ AmbiguousHandedness ▶ 改用 sn
```

任一方式都可叠加[连接选项](#连接选项)配置超时、重试与设备共享。

### 自动连接

`auto_connect()` 连接当前发现的那台设备。若发现多台，会因目标无法唯一确定而失败，此时改用 `connect()` 显式指定：

```python theme={null}
glove = manager.auto_connect(device_name="glove")
print(f"已连接: {glove.serial_number}")
```

C 侧没有等价接口。先调用 `wuji_scan()`，确认清单只有一条记录后，取其 `serial_number` 走[指定 SN 连接](#指定-sn-连接)。

### 指定 SN 连接

通过设备序列号连接特定设备：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    glove = manager.connect(sn="WG1JA00XXXXXXXXX", device_name="glove")
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiConnectTarget target = {
        .kind = WUJI_CONNECT_TARGET_KIND_SN,
        .value = "WG1JA00XXXXXXXXX",
    };

    WujiDevice *glove = NULL;
    if (wuji_connect(&target, "glove", NULL, &glove) != WUJI_STATUS_OK) {
        fprintf(stderr, "connect failed: %s\n", wuji_last_error());
        return 1;
    }
    ```
  </Tab>
</Tabs>

### 指定地址连接

通过 IP 地址和端口手动连接：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    glove = manager.connect(address="192.168.1.100:50000", device_name="glove")
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiConnectTarget target = {
        .kind = WUJI_CONNECT_TARGET_KIND_ADDR,
        .value = "192.168.1.100:50000",
    };

    WujiDevice *glove = NULL;
    wuji_connect(&target, "glove", NULL, &glove);
    ```
  </Tab>
</Tabs>

地址必须包含端口。IPv4 使用 `IP:端口`，当前不支持 IPv6。仅传入 IP 地址时，SDK 返回缺少端口错误，不再将该地址识别为 USB 路径。串口路径也不支持直连，先扫描设备再按 SN 连接。

### 按 USB 连接

Wuji Hand 通过 USB 接入。从扫描结果中按传输类型筛选，再按序列号连接：

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

    manager = SdkManager.instance()
    usb_devices = [d for d in manager.scan() if d.transport_type == TransportType.Usb]

    hand = manager.connect(sn=usb_devices[0].sn, device_name="wuji_hand")
    print(f"已连接: {hand.serial_number} ({hand.handedness_name()})")
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiDiscovered *devs = NULL;
    size_t count = 0;
    wuji_scan(&devs, &count);

    WujiDevice *hand = NULL;
    for (size_t i = 0; i < count; i++) {
        if (devs[i].transport == WUJI_TRANSPORT_TYPE_USB) {
            if (wuji_hand_connect_sn(devs[i].serial_number, "wuji_hand", &hand) != WUJI_STATUS_OK) {
                fprintf(stderr, "connect failed: %s\n", wuji_last_error());
                hand = NULL;
            }
            break;
        }
    }
    wuji_discovered_free(devs, count);
    ```
  </Tab>
</Tabs>

`wuji_hand_connect_sn()` 是 `wuji_connect()` 走 SN 定位的简写，两者返回同一种设备句柄。

若 USB 总线上同时接入了触觉手套，且手性匹配，SDK 会自动配对接入。Python 用 `hand.is_tactile_attached()` 查询，C 用 `wuji_hand_is_tactile_attached()`。该状态反映当前在位情况，运行中拔出手套后转为假。拔出后触觉状态流的 `state` 变为 0（不在位），其余触觉读取与订阅返回携带 `tactile_not_present` 的错误，手本体不受影响继续工作。重新接入手套需重新连接手。

### 按手性连接

双手设备（如 Wuji Hand 2、Wuji Glove）可直接按左右手连接，无需事先查询序列号。SDK 自动扫描局域网，按序列号第 4 位（`J` 为左手、`K` 为右手）筛选唯一目标：

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

# 连接右手设备
hand = manager.connect(handedness=Handedness.Right, device_name="wuji_hand_2")

# 连接左手手套
glove = manager.connect(handedness=Handedness.Left, device_name="wuji_glove")
```

`handedness` 与 `sn`、`address` 互斥，三者只能指定其一。

C 侧没有等价接口。在 `wuji_scan()` 的结果里按 `serial_number[3]` 取 `'J'` 或 `'K'` 筛出唯一目标，再走[指定 SN 连接](#指定-sn-连接)。

<Info>
  Python 在网络中没有匹配手性的设备时抛出 `DeviceNotFound`，存在多个同手性设备时抛出 `AmbiguousHandedness` 并列出所有候选序列号，此时改用 `sn` 显式指定。
</Info>

### 连接选项

配置超时、重试与设备共享：

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

    options = ConnectOptions(
        timeout_ms=1000,   # 连接超时（毫秒）
        retry_count=3      # 重试次数
    )

    glove = manager.connect(
        sn="WG1JA00XXXXXXXXX",
        device_name="glove",
        options=options
    )
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiConnectOptions options = wuji_connect_options_default();
    options.timeout_ms = 1000;   // 连接超时（毫秒）
    options.retry_count = 3;     // 重试次数

    WujiConnectTarget target = {
        .kind = WUJI_CONNECT_TARGET_KIND_SN,
        .value = "WG1JA00XXXXXXXXX",
    };

    WujiDevice *glove = NULL;
    wuji_connect(&target, "glove", &options, &glove);
    ```
  </Tab>
</Tabs>

`enable_bridge`（默认开启）控制是否允许多个 SDK 实例（含其他主机上的进程）连接同一设备。开启时本进程把已连接的设备共享出去。关闭表示独占，其他实例无法经本进程连接该设备。

C 侧必须从 `wuji_connect_options_default()` 取默认值再覆盖需要的字段。零初始化整个结构体会把 `enable_bridge` 与后台时间同步一并置零，改变连接行为。不需要定制时向 `wuji_connect()` 传入 `NULL`。

## 多设备管理

SDK 支持同时连接多个设备，每个设备通过 `device_name` 唯一标识：

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    left = manager.connect(handedness=Handedness.Left, device_name="left_hand")
    right = manager.connect(handedness=Handedness.Right, device_name="right_hand")

    # 获取已连接设备列表
    all_devices = manager.get_connected_devices()
    for name, device in all_devices:
        print(f"{name}: {device.serial_number}")

    # 按名称获取设备
    left = manager.get_device(device_name="left_hand")
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiConnectTarget left_target = {
        .kind = WUJI_CONNECT_TARGET_KIND_SN,
        .value = "WG1JA00XXXXXXXXX",
    };
    WujiConnectTarget right_target = {
        .kind = WUJI_CONNECT_TARGET_KIND_SN,
        .value = "WG1KA00XXXXXXXXX",
    };

    WujiDevice *left = NULL, *right = NULL;
    wuji_connect(&left_target, "left_hand", NULL, &left);
    wuji_connect(&right_target, "right_hand", NULL, &right);

    // C 侧不提供按别名取回设备的注册表，句柄由调用方保存
    char name[64];
    wuji_dev_device_name(left, name, sizeof(name));
    printf("%s: connected=%u\n", name, wuji_dev_is_connected(left));
    ```
  </Tab>
</Tabs>

<Info>
  左右手用不同 `device_name` 即可同进程并存。Python 重复连接同一活跃设备会抛出 `SessionAlreadyExists`，C 返回失败状态码，两者都不会断开前一次连接，沿用既有句柄即可。
</Info>

## 断开连接

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # 断开指定设备
    manager.disconnect(device_name="glove")

    # 或通过设备对象断开
    glove.disconnect()

    # 断开当前管理器中的所有设备
    manager.disconnect_all()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    // 断开后释放句柄，两步都要调用
    wuji_dev_disconnect(glove);
    wuji_dev_release(glove);
    ```
  </Tab>
</Tabs>

<Info>
  断开连接后，该设备上的所有订阅会自动关闭。重新连接需要重新建立订阅。
</Info>

连接由 SDK 持有，不随设备对象或句柄变量的销毁而关闭。Python 侧删除或替换设备对象不会断开连接，连接仅在调用 `disconnect()`、`disconnect_all()` 或进程退出时关闭，长时间运行的程序应在 `finally` 块中显式断开。C 侧 `wuji_dev_release()` 只归还句柄、不断开连接，退出前先调用 `wuji_dev_disconnect()` 再释放。使用独占连接时，未关闭的连接会阻止其他客户端接入设备。

## 接口清单

| 操作 | Python | C |
| - | - | - |
| 初始化与释放 SDK | `SdkManager.instance()` | `wuji_init()`、`wuji_shutdown()` |
| 扫描设备 | `manager.scan()` | `wuji_scan()`、`wuji_discovered_free()` |
| 连接当前唯一设备 | `manager.auto_connect(device_name=...)` | 无对应接口，扫描后取序列号调用 `wuji_connect()` |
| 按序列号连接 | `manager.connect(sn=...)` | `wuji_connect()`，`target.kind` 取 `WUJI_CONNECT_TARGET_KIND_SN` |
| 按地址连接 | `manager.connect(address=...)` | `wuji_connect()`，`target.kind` 取 `WUJI_CONNECT_TARGET_KIND_ADDR` |
| 按 USB 连接 Wuji Hand | 先按 `TransportType.Usb` 筛选，再 `manager.connect(sn=...)` | `wuji_hand_connect_sn()` |
| 按手性连接 | `manager.connect(handedness=...)` | 无对应接口，按序列号第 4 位筛选扫描结果后调用 `wuji_connect()` |
| 配置连接参数 | `ConnectOptions(...)` | `wuji_connect_options_default()` 取默认值后覆盖 `WujiConnectOptions` 字段 |
| 读取序列号与别名 | `device.serial_number`、`device.device_name` | `wuji_dev_serial_number()`、`wuji_dev_device_name()` |
| 查询连接状态 | `device.is_connected` | `wuji_dev_is_connected()` |
| 按别名取回设备 | `manager.get_device(device_name=...)`、`manager.get_connected_devices()` | 无对应接口，由调用方自行保存设备句柄 |
| 断开单台设备 | `device.disconnect()`、`manager.disconnect(device_name=...)` | `wuji_dev_disconnect()`、`wuji_dev_release()` |
| 断开全部设备 | `manager.disconnect_all()` | 无对应接口，逐个调用 `wuji_dev_disconnect()` |

Python 用异常报告失败，C 用 `WujiStatus` 返回码报告失败，失败原因通过 `wuji_last_error()` 读取。编译、链接、内存管理与错误码取值见 [C 接口参考](/docs/wuji-sdk/data-reference/)。


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