IMU UDP 协议与时间模型

本文是根目录 PROTOCOL.md 的中文公开文档入口,定义当前二进制 UDP 姿态包和上位机动捕链路使用的时间字段。所有多字节包字段均为小端序。

UDP 通道

通道

端口

方向

用途

数据

4211

设备到主机

二进制姿态包和文本状态

发现

4212

设备广播

VQF_DISC 设备发现信标

发现信标格式为:

VQF_DISC,node=<MAC后缀>,id=<可选设备ID>,ip=<IP>,tcp=4210,udp=4211,ver=<固件版本>

设备同时通过 mDNS 发布 _vqf-imu._tcp 服务,主机名为 vqf-imu-<node_id>.local

v1 姿态包

type = 0x01,长度必须恰好为 12 字节:

偏移

类型

字段

0

uint8

type

1

uint8

flagsbit0=RESTbit1=REST_SKIP

2

uint16

seq

4

int16[4]

Q15 四元数 w, x, y, z

Python 格式为 "<BBH4h",Q15 解码为 value / 32767.0seq 只能在同一设备内判断丢包,不能比较不同 IMU 的采样先后。

v2 姿态包

type = 0x03,长度必须恰好为 20 字节:

偏移

类型

字段

0

uint8

type

1

uint8

flags

2

uint16

seq

4

uint64

sensor_time_us

12

int16[4]

Q15 四元数 w, x, y, z

Python 格式为 "<BBHQ4h"

sensor_time_us 是设备自启动后的单调微秒时钟,必须在 IMU 采样或 VQF 姿态更新处记录,不能在 UDP 发送时生成。它不是 Unix 时间;在完成跨设备时钟同步之前,不能直接比较不同设备的该字段。

当前固件发送 v2 0x03 包。上位机继续兼容旧固件的 v1 0x01 包;v1 节点不能参与共同时间轴解算。

文本状态包

type = 0x02,至少为 4 字节:

偏移

类型

字段

0

uint8

type

1

uint8

保留 flags

2

uint16

包序列号

4

UTF-8 字节

以空字符结尾的文本载荷

设备用该包发送低频消息,例如 $CAL,...$PWR,...$DIAG,...

主机时间字段

字段

含义

host_received_ns

recvfrom() 返回后立即记录的主机单调时钟。

mapped_sample_time_ns

有效时钟映射后的主机单调采样时刻;映射无效时为 null

frame_id

后续共同时间轴动捕帧的递增 ID。

frame_time_ns

后续用于解算一帧身体姿态的共同目标时刻。

solved_time_ns

后续人体解算完成的主机单调时刻。

time_sync_valid

至少 4 个有效样本且时间跨度不小于 3 秒时,节点到主机的时钟映射有效。

time_sync_error_us

有效样本线性拟合的 RMS 残差估计;未知时为 null

max_sample_age_ms

一帧共同动捕中最旧有效样本年龄;尚未计算时为 null

Unix 墙上时间只用于日志和界面显示。实时排序、在线超时和包频率统计使用主机单调时钟。

TCP 命令

主机连接 TCP 4210 后发送以换行符结尾的文本命令:

命令

用途

$CMD,CAL_START

开始磁力计标定采集

$CMD,CAL_STOP

停止标定采集

$CMD,CAL_ERASE

擦除已保存标定参数

$CMD,OFF

请求设备关机

$CMD,SET_ID,<id>

保存自定义设备 ID

$CMD,GET_ID

查询自定义设备 ID

$CMD,SYNC,<nonce>

时间同步请求,TCP 直接回复 $SYNC,<nonce>,<t1_us>,<t2_us>

TCP 时间同步

主机仅对已发送 v2 姿态包的节点执行同步。每 10 秒对每个节点连续发起 4 次:

  1. 主机发送 $CMD,SYNC,<nonce> 前记录 t0_host_ns

  2. 设备 TCP 接收任务收到完整行后立即记录 t1_device_us,回复前记录 t2_device_us

  3. 主机收到 $SYNC,<nonce>,<t1_us>,<t2_us> 后记录 t3_host_ns

  4. 主机拟合 host_monotonic_ns ~= scale * sensor_time_us + offset

RTT 大于 50 ms 或相对最佳 RTT 明显偏大的样本会被丢弃。offset 消除不同开机时刻,scale 补偿晶振误差和长期漂移。未达到有效条件时,仅显示各节点最新姿态,不将它们视为同一采样时刻。

共同动捕时间轴

人体解算不会直接拼接各节点刚收到的“最新姿态”。对于已同步的 v2 节点,主机保留短时缓存并选择:

t_target = min(latest_sample_time[node]) - safety_margin

默认 safety_margin 为 20 ms。胸部、上臂、小臂和手掌都必须在 t_target 前后拥有间隔不大于 80 ms 的两个样本,系统以最短路径 SLERP 插值得到共同帧。任一必需节点缺失、未同步、缓存不足或样本过旧时,共同帧明确无效,不能混用旧姿态和新姿态。

MocapState.frame_time_nst_targetnode_samples 保留插值前后序号、时间、插值比例和样本年龄;time_sync_error_usmax_sample_age_ms 分别给出这一帧最差同步误差和最老前向样本年龄。

OTA

OTA HTTP 服务监听 8080 端口:

请求

用途

POST /update

Content-Type: application/octet-stream 上传完整固件二进制

GET /info

查询 OTA / 设备信息

上传必须使用原始固件二进制请求体,不支持 multipart 表单上传。