C 接口参考

Wuji SDK 提供 C 接口(libwuji_sdk_c.so + wuji_sdk.h),与 Python 接口语义一致——设备发现、连接、按设备类型的 typed 订阅回调、全局坐标变换。安装方式与 Python 并列见 产品介绍

完整 typed 回调与结构体定义参见 SDK tarball 解压后的 include/wuji_sdk.h,配套 CMake 工程示例与构建说明见 wuji-sdk 仓库 examples/c/ 目录

初始化与约定

初始化

进程级初始化一次,结束时释放:

#include "wuji_sdk.h"

if (wuji_init(NULL) != WUJI_STATUS_OK) {
    fprintf(stderr, "init failed: %s\n", wuji_last_error());
    return 1;
}

// ... 业务逻辑

wuji_shutdown();

返回值分类

  • WujiStatus — 多数操作(wuji_init / wuji_scan / wuji_connect / 订阅打开等),WUJI_STATUS_OK 表示成功,失败时通过 wuji_last_error() 取当前线程错误描述。
  • int32_t — 元信息读取(wuji_dev_serial_number / wuji_dev_device_name)返回写入字节数。
  • uint8_t — 状态查询 wuji_dev_is_connected
  • voidwuji_shutdown 与资源释放函数。

资源释放函数

  • wuji_discovered_freewuji_scan 后释放设备列表
  • wuji_dev_release — 与 wuji_connect 配套,断开后释放设备句柄
  • wuji_sub_close — 关闭订阅句柄

设备发现与连接

扫描局域网内的 Wuji 设备:

WujiDiscovered* devs = NULL;
size_t count = 0;
wuji_scan(&devs, &count);
for (size_t i = 0; i < count; i++) {
    printf("[%zu] %s @ %s\n", i, devs[i].serial_number, devs[i].address);
}
wuji_discovered_free(devs, count);

按 SN 或地址连接(target.kindWUJI_CONNECT_TARGET_KIND_SN_ADDR,本地别名作为 wuji_connect 第二参数传):

WujiConnectTarget target = {
    .kind = WUJI_CONNECT_TARGET_KIND_SN,
    .value = "WG1KA00260209001",
};
WujiConnectOptions opts = { .timeout_ms = 5000, .retry_count = 0 };

WujiDevice* dev = NULL;
if (wuji_connect(&target, "glove", &opts, &dev) != WUJI_STATUS_OK) {
    fprintf(stderr, "connect failed: %s\n", wuji_last_error());
    return 1;
}

char sn_buf[64];
wuji_dev_serial_number(dev, sn_buf, sizeof(sn_buf));
printf("connected: sn=%s\n", sn_buf);

// ... 业务

wuji_dev_disconnect(dev);
wuji_dev_release(dev);

device_name 与 Python 语义一致:本地别名,不参与设备匹配,不决定返回类型,要求非空且不含 /.。详见 设备连接

订阅设备数据流

每种 typed 数据流都有专用包装 wuji_<device>_subscribe_<name>,回调签名与数据 schema 绑定。以 Wuji Glove 触觉为例:

void on_tactile(WujiFrameKind kind, const WujiTactileFrame* frame, void* user) {
    if (kind != WUJI_FRAME_KIND_OK) return;
    // frame->data, frame->header.seq, frame->header.timestamp_us
}

WujiSub* sub = NULL;
wuji_glove_subscribe_tactile(dev, on_tactile, /*user=*/NULL, &sub);

// ... 回调在后台触发

wuji_sub_close(sub);

Wuji Glove 暴露的 typed 订阅入口:tactiletactile_zonestactile_binarytactile_residualtactile_point_cloudemf_poseshand_joint_angleshand_skeletontip_posesimu_palm 及五指 imu_<finger> / imu_data_<finger>。Wuji Hand 2 目前仅暴露 wuji_hand_2_subscribe_joint_state,更多 typed 入口规划中。

订阅全局资源

跨设备聚合的坐标变换通过 manager.tf() 在 Python 暴露,C 等价为 device-less 包装:

void on_tf(WujiFrameKind kind, const WujiFrameTransforms* frames, void* user) {
    if (kind != WUJI_FRAME_KIND_OK) return;
    for (size_t i = 0; i < frames->transforms_len; i++) {
        const WujiFrameTransform* t = &frames->transforms[i];
        printf("%s -> %s\n", t->parent_frame_id, t->child_frame_id);
    }
}

WujiSub* sub = NULL;
wuji_subscribe_tf(on_tf, NULL, &sub);
// 同样:wuji_subscribe_tf_static(...)

wuji_sub_close(sub);

使用约束

C SDK 用户必须遵守以下约束,否则会出现 use-after-free、死锁或读到失效错误描述:

  • typed 回调的 frame 指针仅在回调期间有效,返回后堆字段即被释放。需要保留的数据必须在回调内拷贝出来,不得保存指针。
  • 不要在订阅自身的回调里调 wuji_sub_closeclose 会 join worker 线程,在自身回调中调用必死锁。请在外部线程上 close。
  • wuji_last_error() 返回值仅在同线程下一次 wuji_* 调用前有效。需要保留的错误描述应立即拷贝。
  • END / ERROR 是终止帧,回调收到这两类帧后流不再有新数据,但仍需调用 wuji_sub_close 释放订阅句柄。

Python 接口对照

功能CPython
初始化wuji_init(NULL)SdkManager.instance()
扫描wuji_scan(&devs, &count)manager.scan()
连接wuji_connect(&target, alias, &opts, &dev)manager.connect(...) / manager.auto_connect(...)
设备订阅wuji_glove_subscribe_tactile(dev, cb, user, &sub)glove.tactile().subscribe_with_callback(...)
全局订阅wuji_subscribe_tf(cb, user, &sub)manager.tf().subscribe()
断开wuji_dev_disconnect + wuji_dev_releasedevice.disconnect()

CMake 工程示例

完整可运行 C 工程(含 CMakeLists.txt + 订阅回调示例)见 wuji-sdk 仓库的 examples/c/ 目录,构建步骤参见该目录 README。最小链接方式:

cc my_app.c -I "${SDK}/include" -L "${SDK}/lib" -lwuji_sdk_c -o my_app
LD_LIBRARY_PATH="${SDK}/lib" ./my_app

其中 ${SDK} 是解压后的 tarball 根目录。CMake 工程通过 -DWUJI_SDK_INCLUDE_DIR-DWUJI_SDK_LIB 指向同样路径。