UDP接收-关节位姿
本文档说明 HumanHand 上位机和 SDK 演示程序发出的关节 UDP。点序、轴向与 SDK 读到的数组相同。接入自己的程序时,也可以不经 UDP,直接读 SDK。
1. 功能概述
| 输出格式 | 原点 | 每手点数 | 协议 | 单包大小 |
|---|---|---|---|---|
| 关节位姿(相对手腕) | 掌根 | 21 | FSNH 版本 4 | 612 字节 |
| 关节位姿(相对小臂) | 小臂模块 | 22 | FSNH 版本 4 | 640 字节 |
| 项目 | 说明 |
|---|---|
| 数据来源 | 手套关节位姿;小臂档需接小臂模块 |
| 传输协议 | UDP 单播,无握手 |
| 输出频率 | 默认 120 Hz(每只手各自按解算节拍发) |
| 手别 | 一包一只手。hand=1 左,hand=2 右 |
| 双手 | 同一目的 IP 和端口,左右各发一包。接收端按 hand 分到两只手。两包不必同时到达 |
上位机路径:用户设置 → 数据发送 → 输出格式。SDK 演示菜单:相对手腕 / 相对小臂发关节。
2. 发送配置
| 参数 | 默认值 | 说明 |
|---|---|---|
| 目标 IP | 127.0.0.1 | 上位机在「数据发送」中修改;SDK 演示启动时询问 |
| 目标端口 | 15001 | 双手共用这一端口 |
| 字节序 | 小端(Little-Endian) | 所有多字节字段 |
3. 坐标系说明
| 项目 | 说明 |
|---|---|
| 相对手腕 | 原点在掌根(与 SDK hh_sdk_poll 相同) |
| 相对小臂 | 原点在小臂(与 SDK hh_sdk_forearm_root 相同);轴与手腕档相同 |
| 位置单位 | 米(m) |
| 旋转 | 四元数 w, x, y, z |
| 轴向 | 左手:右、前、上;右手:左、后、上 |
| 轴 | 左手 | 右手 |
|---|---|---|
| X | 右 | 左 |
| Y | 前(沿手指) | 后 |
| Z | 上(手背方向) | 上 |
载荷里只有这一只手的关节,按 hand 选用。
4. 关节索引
4.1 相对手腕(21)
| 载荷索引 | 部位 |
|---|---|
| 0 | 掌根(位置约 0) |
| 1–4 | 拇指:掌指、近节、远节、指尖 |
| 5–8 | 食指 |
| 9–12 | 中指 |
| 13–16 | 无名指 |
| 17–20 | 小指 |
指尖:4、8、12、16、20。
4.2 相对小臂(22)
| 载荷索引 | 部位 |
|---|---|
| 0 | 小臂(位置恒为 0,姿态为单位四元数) |
| 1 | 手腕(即上面的掌根) |
| 2–21 | 上面的 1–20(拇指掌指 … 小指指尖) |
指尖:5、9、13、17、21。
5. 数据包结构
包头 24 字节,随后本手全部关节。每关节 28 字节:xyz(12)+ wxyz(16)。
text
相对手腕:24 + 21×28 = 612
相对小臂:24 + 22×28 = 640发送字节数必须等于上表。不要按最大结构体长度截取或补零。
5.1 包头(24 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|---|---|---|---|---|
| 0 | 4 | uint32 | magic | 0x464E4853("FSNH") |
| 4 | 1 | uint8 | version | 4 |
| 5 | 1 | uint8 | hand | 1 左 / 2 右 |
| 6 | 1 | uint8 | joint_count | 21 相对手腕,22 相对小臂 |
| 7 | 1 | uint8 | status | 见 5.2 |
| 8 | 4 | uint32 | frame_index | 该手自己的帧序号 |
| 12 | 8 | uint64 | timestamp_ns | 主机单调时钟纳秒 |
| 20 | 4 | uint32 | quat_order | 0x77787A79(wxyz) |
包长也可区分:612=手腕,640=小臂。须与 joint_count 一致。
5.2 status
| 位 | 掩码 | 含义 |
|---|---|---|
| 0 | 0x01 | 1=本包是新解算;0=重复上一拍 |
| 1 | 0x02 | 仅 22 点:1=本拍小臂原点有效;0=沿用上次有效小臂。21 点时为 0 |
| 2–7 | 必须为 0 |
5.3 单关节(28 字节)
| 相对偏移 | 字段 | 类型 |
|---|---|---|
| 0 | position.x/y/z | float32×3 |
| 12 | quaternion.w/x/y/z | float32×4 |
从偏移 24 起连续 joint_count 个。
6. 双手怎么收
- 只绑定一个 UDP 端口(默认 15001)。
- 每收到一包,读
hand:1 写入左手缓存,2 写入右手缓存。 - 左右的
frame_index/timestamp_ns各自独立,不要拿来互相对齐成「同一瞬间」。 - 某一侧暂时没有新包,继续用该侧上一包;不要用另一侧的数据填。
7. 时间戳与发送频率
| 字段 | 含义 |
|---|---|
| timestamp_ns | 主机单调时钟纳秒 |
| frame_index | 该手解算序号;停滞时不再增加 |
默认约 8.33 ms 一包(每只手 120 Hz)。双手时该端口上大约每秒 240 个数据报。
8. 接收端检查清单
magic == 0x464E4853且version == 4- 长度 612 且
joint_count==21,或长度 640 且joint_count==22 hand为 1 或 2quat_order == 0x77787A79status高 6 位为 0- 只解析本包
joint_count个关节
9. C 结构体参考
接入时按下列结构体组包或拆包。
c
#pragma pack(push, 1)
#define FSNH_MAGIC 0x464E4853u
#define FSNH_VERSION 4u
#define FSNH_HAND_LEFT 1u
#define FSNH_HAND_RIGHT 2u
#define FSNH_JOINTS_WRIST 21u
#define FSNH_JOINTS_FOREARM 22u
#define FSNH_STATUS_NEW 0x01u
#define FSNH_STATUS_FOREARM_LIVE 0x02u
#define FSNH_PKT_WRIST 612u
#define FSNH_PKT_FOREARM 640u
#define FSNH_QUAT_ORDER_WXYZ 0x77787A79u
typedef struct {
float x, y, z;
float qw, qx, qy, qz;
} FsnhJointPose;
typedef struct {
uint32_t magic;
uint8_t version;
uint8_t hand;
uint8_t joint_count;
uint8_t status;
uint32_t frame_index;
uint64_t timestamp_ns;
uint32_t quat_order;
} FsnhHeader;
typedef struct {
FsnhHeader header;
FsnhJointPose joints[FSNH_JOINTS_WRIST];
} FsnhPacketWrist;
typedef struct {
FsnhHeader header;
FsnhJointPose joints[FSNH_JOINTS_FOREARM];
} FsnhPacketForearm;
#pragma pack(pop)发送手腕包时只发出 sizeof(FsnhPacketWrist)(612)字节。
10. 参考接收脚本
站点提供 udp_receiver.py,上位机默认端口 15001:
bash
python3 udp_receiver.py --port 15001按包长显示当前格式:612/640 关节,240 线圈+IMU,1860 Pico 全身。双手同一端口左右各一包,脚本按手各刷一块表,大约每秒原地刷新。
11. 与「EMF + 发射源 IMU」对比
| 对比项 | 关节位姿 | EMF + 发射源 IMU |
|---|---|---|
| 协议 | FSNH 版本 4 | FNIM 版本 2 |
| 包长 | 612 B(腕)/ 640 B(小臂) | 240 B |
| 内容 | 21 或 22 关节/手 | 6 槽位姿 + 发射源 IMU |
| 位置单位 | 米 | 毫米 |
| 坐标系 | 左 RFU / 右 LBU | 磁源跟踪系 |
| 双手 | 同一端口,左右各一包 | 左右各一包 |
| 时间戳 | 单调时钟纳秒 | 单调时钟纳秒 |
