核心概念
读数据前请务必理解本篇内容,否则很容易把坐标系、点云类型或录制语义理解错。
坐标系
一句话结论
SDK 输出的位姿与点云,坐标原点在设备的 IMU 点(设备基准),朝向遵循右手系(x 前 / y 左 / z 上)。 设备不知道也不关心它被装在什么载体上——"设备 → 载体(狗心/车体中心)"的安装变换交给集成方自己做。这与行业主流 SLAM 设备的做法一致。

X-Brain 设备(IMU)坐标系:右手系,x 前 / y 左 / z 上
涉及的坐标系
| 坐标系 | 简称 | 定义 | 特征 |
|---|---|---|---|
| 世界坐标系 | W 系 / map | 设备首次建图时的起始位置为原点 | 全局固定,重定位/回环时可能跳变,长期无漂移 |
| 设备(IMU)坐标系 | I 系 / base_link | 原点在 IMU 芯片安装点,为整机基准 | 随设备运动 |
| 载体坐标系 | 你的机器人中心 | 由集成方按安装外参定义 | SDK/设备不提供 |
变换关系:
世界系 (map)
│ R_W_I, t_W_I(SDK 输出的位姿)
└── 设备(IMU)系 (base_link)
│ 由集成方按安装外参挂接(SDK 不提供)
└── 载体系 (your_robot_center)
方向与单位约定(遵循 ROS REP-103)
- 所有坐标系为右手系。
- 机体坐标轴:x 向前、y 向左、z 向上。
- 姿态旋转:绕 x = roll、绕 y = pitch、绕 z = yaw。
- 单位统一用 SI:长度 m、角度 rad、时间 s、速度 m/s、角速度 rad/s。
- 姿态一律用单位四元数
(w, x, y, z),避免欧拉角万向锁。(1, 0, 0, 0)表示无旋转。
注:方向记号约定用
T_A_B表示"把 B 系下的点变换到 A 系"(从右往左读)。例如R_W_I= IMU 系相对世界系的旋转(世界←IMU)。
如何接到你的载体(关键)
设备只输出设备(IMU)系。要把它用在你的机器狗/车/无人机上,你需要自己发布一层"设备 → 载体中心"的静态变换:
map ──► base_link(设备IMU) ──► your_robot_center(狗心/车体中心)
└ 你自己标定并发布这层静态 TF
ROS 侧用静态 TF 广播即可:
# 参数:x y z qx qy qz qw parent child
ros2 run tf2_ros static_transform_publisher \
<x> <y> <z> <qx> <qy> <qz> <qw> base_link your_robot_center
其中 <x y z ...> 是"设备 IMU 点 → 你的载体中心"的安装外参,由你在自己的载体上标定。
注:为什么这样设计——设备是通用模组,可能装在任意载体上。把安装外参写进设备/SDK 会绑死某一种载体(换平台就错)。交给集成方,才能通用且无歧义。
位姿数据
subscribePose() 回调输出的位姿(~100Hz)字段语义(坐标系均为设备/IMU 系):
| 字段 | 坐标系 | 单位 | 说明 |
|---|---|---|---|
| posX/Y/Z | W 系 | m | 设备(IMU)点在世界系下的位置 |
| quatW/X/Y/Z | W←I | — | 设备(IMU)相对世界系的姿态四元数(R_W_I) |
| velX/Y/Z | I 系 | m/s | 设备(IMU)系下的线速度 |
| gyroX/Y/Z | I 系 | rad/s | 设备(IMU)系下的角速度 |
| errorCode | — | — | 定位故障码,0 = 正常 |
| isLocal | — | — | 保留字段,恒为 0,勿使用 |
两种点云
| 接口 | 频率 | 坐标系 | 含义 |
|---|---|---|---|
subscribeCloud | ~10 Hz | 设备(IMU)系 | 重定位配准点云(NDT 产物),不是每帧雷达扫描 |
subscribeRawCloud | ~10 Hz | 雷达系 | 去畸变的单帧雷达扫描,未经 SLAM/建图配准 |
两者共用 CloudFrame 结构体,字段相同但语义不同。要每帧扫描、做自有建图或点云处理,用 subscribeRawCloud;要与地图配准后的稀疏结果,用 subscribeCloud。
IMU 数据
subscribeImu() 回调输出原始 IMU 数据(~200Hz),为设备端未经 SLAM 处理的原始输出:
| 字段 | 单位 | 说明 |
|---|---|---|
| accX/Y/Z | m/s² | 三轴加速度 |
| gyrX/Y/Z | rad/s | 三轴角速度 |
注:IMU 回调频率很高(~200Hz),实现务必尽快返回,不可做耗时操作。
相机图像
subscribeImage() 按相机 ID 订阅 JPEG 图像:
| CameraId | 相机 | Zenoh key |
|---|---|---|
| Left (0) | 左相机 | camera/left |
| Center (1) | 中相机 | camera/center |
| Right (2) | 右相机 | camera/right |
图像发布的帧率取决于设备端 JPEG 编码配置(默认约 2fps)。回调交付的是纯 JPEG 码流(SDK 已解析掉线上头部)。
注:
subscribeImage(CameraId)的相机编号(0/1/2)与getCameraCalib(0..3)的标定索引不是同一套映射,不要混用。
时间戳时基
- 位姿 / 点云 / IMU / 图像回调结构体中的
timestampSec单位为秒(由设备侧微秒时间戳 / 1e6 得来)。 - 错误码条目的
timestamp单位为微秒。 - 所有时间戳来源于设备端,用于同一设备内多数据流的对齐;跨设备/跨主机时钟同步不在本 SDK 职责内。
录制语义
启停只下发、不等待
startRecord() / stopRecord() 只把命令发给设备,不等待状态机到达目标。返回 Ok 只表示设备已接受命令。必须轮询 queryRecordStatus() 确认结果。
为什么不代为等待
设备从收到命令到真正开始录制,需要初始化定位与建图,耗时与地图规模、存储状态等因素相关,实测从数秒到数十秒不等,没有对所有场景都成立的上限。如果 SDK 内部按固定超时等待,超时值定短了就会在设备正常启动的过程中返回失败——制造出一个并不存在的故障。所以等多久、如何反馈进度,交给调用方按业务判断。
录制状态机
enum class RecordState {
Idle, // 待机,可以开始录制
Starting, // 启动中,尚未开始出数据
Recording, // 录制中
Stopping, // 停止中
Error, // 故障,见 message
};
轮询要点:
- 单次查询失败不代表操作失败(可能只是通信抖动),应重试。
- 状态为
Error时立即退出,设备已明确失败。 Idle和Starting都要继续等,命令刚下发时状态可能还是Idle。- 等待上限到了也不代表失败,命令已下发,此时应继续观察或提示操作者,不要重复下发
startRecord()。
Recording 不等于定位就绪
进入 Recording 后,定位算法还要完成重定位才输出可用的定位数据。若业务依赖定位(如导航),需另行确认定位数据已到达——本版 SDK 不提供该判据(位姿开始输出且合理即为经验判据)。
warning 与 Error 的区别
warning 不阻塞操作,Error 阻塞操作。 这是判断该继续还是该中止的依据。
| 场景 | state | 含义 |
|---|---|---|
| 建图过程异常 | Recording + warning | 录制仍在继续,已退化为仅录包模式,可正常停止 |
| 上次工程后处理异常 | Idle + warning | 设备本身可用,不影响本次作业 |
| 存储/相机/雷达/内存故障 | Error | 需先处理故障才能录制,见 message |
rawCode 是设备内部原始状态码,仅用于排障和日志,不保证跨版本稳定,请勿作为业务判断依据。
错误模型
所有接口以返回值报错,不抛异常:
| 返回码 | 典型原因 | 建议动作 |
|---|---|---|
Ok | 成功(启停时仅表示命令已接受) | 启停后轮询确认 |
InvalidArg | 空指针、IP 空/超长、参数越界 | 修正参数 |
NotConnected | 未连接 | 先 open |
Timeout | 未在超时内收到应答 | 启停勿直接重发,先 queryRecordStatus |
Rejected | 设备拒绝,或订阅槽已占 | 查状态,或先 unsubscribe |
DeviceError | 设备故障态 | 见 RecordStatus.message |
Protocol | 应答无法解析 | 核对固件/SDK 版本 |
Internal | SDK 内部错误 | 检查连接与日志 |
xg::toString(err) 返回可读字符串。完整错误模型见 API 参考。
线程安全
同一个 Device 实例的方法不可并发调用,多线程使用时请由调用方加锁。不同实例之间互不影响。订阅回调在 SDK 内部线程触发,不在调用 subscribe* 的线程。