快速开始
本篇带你从零跑通 XGRIDS 设备的数据订阅与录制控制,提供两条路径:
- 路径 A — C/C++:直接链接
liblixel_sdk.so调 API。 - 路径 B — ROS2:用自带的桥接节点,把设备数据转成标准 ROS2 Topic。
环境与前提
| 项 | 要求 |
|---|---|
| 操作系统 | Linux x86_64 / aarch64 |
| glibc | x86_64 ≥ 2.34;aarch64 ≥ 2.30(ldd --version 查看) |
| 网络 | 主机与设备同一交换机直连,设备端口(默认 7448)可达 |
| 交付物 | include/(头文件)、lib/<arch>/liblixel_sdk.so、bin/<arch>/(预编译示例)、examples/、ros2/ |
| ROS2(仅路径 B) | Humble(Ubuntu 22.04)或 Foxy(Ubuntu 20.04),需 Eigen3 |
先做连通性自检(open() 超时最常见的原因就是这一步没过):
ping <设备IP> # 网络与网段
nc -z <设备IP> 7448 && echo OK # 设备端口可达
注:SDK 只连一个端口(默认 7448)。数据面(位姿/点云/IMU/图像/错误码)与命令面(录制启停、状态查询)都在同一会话。若设备端改过端口,把
Options.port/device_port直接填其实际端口即可。
读数据前必须知道的三件事
- 坐标系 = 设备(IMU)系:SDK 输出的位姿/点云,原点在设备 IMU 点,遵循右手系(x 前 / y 左 / z 上)。载体(机器狗/车/无人机)的安装变换由你自己补一层 TF。
- 两种点云不是一回事:
subscribeCloud是重定位配准点云(~10Hz,设备系,NDT 产物);subscribeRawCloud是去畸变的单帧雷达扫描(~10Hz)。要每帧扫描用后者。 - 录制启停只下发、不等待:
startRecord()返回成功仅表示命令被接受;进入Recording需轮询确认;而Recording也 不等于 定位就绪。
路径 A — C/C++
编译
# <arch> 取 x86_64 或 aarch64,与目标机架构一致
g++ -std=c++17 my_app.cpp \
-I<sdk>/include \
-L<sdk>/lib/<arch> -llixel_sdk \
-Wl,-rpath,'$ORIGIN/lib/<arch>' \
-o my_app
-rpath 已内嵌库路径;或运行前 export LD_LIBRARY_PATH=<sdk>/lib/<arch>。
最小示例:连接 → 订阅位姿 → 断开
#include "xg/robot_sdk.h"
#include <cstdio>
#include <thread>
#include <chrono>
int main() {
xg::Options opts;
opts.deviceIp = "192.168.123.103"; // 改成你的设备 IP
// opts.port = 0; // 0 = 默认 7448
// opts.connectTimeoutMs = 0; // 0 = 默认 5000ms
xg::Error err = xg::Error::Ok;
auto dev = xg::Device::open(opts, &err);
if (!dev) {
std::printf("连接失败: %s\n", xg::toString(err));
return 1;
}
// 回调在 SDK 内部线程触发,勿阻塞
dev->subscribePose([](const xg::Pose& p) {
std::printf("t=%.3f pos=(%.3f, %.3f, %.3f)\n",
p.timestampSec, p.posX, p.posY, p.posZ);
});
std::this_thread::sleep_for(std::chrono::seconds(10));
return 0; // dev 析构自动取消订阅并断开,无需手动 close
}
Device由unique_ptr持有,析构自动收尾;禁止拷贝/移动,同一实例不可重入(多线程自行加锁)。
录制启停(只下发 + 轮询确认)
if (dev->startRecord() != xg::Error::Ok) {
// Timeout 时命令可能已送达,禁止直接重发,先 queryRecordStatus 再决定
}
xg::RecordStatus st;
while (true) {
std::this_thread::sleep_for(
std::chrono::milliseconds(XG_RECOMMENDED_POLL_INTERVAL_MS)); // 500ms
if (dev->queryRecordStatus(&st) != xg::Error::Ok) continue;
if (st.state == xg::RecordState::Recording) break;
if (st.state == xg::RecordState::Error) break; // 处理故障
}
完整状态机见示例 record_demo.cpp(示例程序)。
可订阅的数据
所有订阅同构:subscribeXxx(回调) / unsubscribeXxx(),每类占一个订阅槽,重复订阅返回 Rejected。
dev->subscribeCloud([](const xg::CloudFrame& f) {
// f.points 指向 SDK 内部缓冲,仅本次回调有效!留用须拷贝:
// std::vector<xg_point_t> saved(f.points, f.points + f.count);
std::printf("cloud: %zu points\n", f.count);
});
dev->subscribeImu([](const xg::Device::Imu& imu) { // 纯标量,可按值保存
std::printf("acc=(%.2f,%.2f,%.2f)\n", imu.accX, imu.accY, imu.accZ);
});
dev->subscribeImage(xg::Device::CameraId::Left,
[](const xg::Device::ImageFrame& f) { // jpegData 仅回调内有效
std::printf("jpeg: %zu bytes\n", f.jpegSize);
});
回调内存三条铁律
回调在 SDK 内部线程触发:
- 不要阻塞(会拖住整个会话线程,影响所有订阅);重活拷贝后转交自己的线程。
- 变长指针只在回调期内有效(
CloudFrame::points、ImageFrame::jpegData指向复用缓冲,下一帧覆盖);留用必须回调内深拷贝。 - 不要
free/delete回调里的任何指针(不是你分配的)。
按值类型(Pose/Imu)可整体保存。详见集成指南。
路径 B — ROS2
用自带桥接节点把数据转成标准 ROS2 Topic,适合已有 ROS2 工作流的用户。
编译
mkdir -p ~/ros2_ws/src
ln -sf <sdk>/ros2 ~/ros2_ws/src/lixel_sdk_ros2
cd ~/ros2_ws
source /opt/ros/humble/setup.bash
colcon build --packages-select lixel_sdk_ros2
运行
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
# <arch> 取 x86_64 或 aarch64,与目标机架构一致
export LD_LIBRARY_PATH=<sdk>/lib/<arch>:$LD_LIBRARY_PATH
ros2 launch lixel_sdk_ros2 sdk_bridge.launch.py device_ip:=192.168.123.103
节点启动后自动连接设备并启动录制,进入录制状态后开始发布数据;Ctrl+C 退出时自动停录并断开。
查看数据
ros2 topic list # 查看话题
ros2 topic hz /lixel/odom # 确认位姿在发
ros2 topic echo /lixel/odom # 查看位姿内容
完整 Topic 列表与 TF 结构见 ROS2 桥接。