历史快照
本专题页写于包拆分与 FAST-LIVO2 / CustomMsg / 图像输入落地之前,反映当时的单包结构与
五前端状态。已知过时点:目录布局(apps/launch 现属 asuka_ros 包)、共享的
config_sensor.json(外参已改为各前端 odometry 配置的 sensor 块)、ROS 层 acc_scale
等描述、前端数量(现六种,新增 fastlivo)、CustomMsg 与图像输入链路。2026-09 下旬以来:lightning / superodom 前端与 optimization_miao 后端已移除
(现五前端:fastlio / batchlio / superlio / fastlivo / srlivo;smallpointlio 已于 2026-10-23 移除,odometry/10-11 为其历史快照);
freedom 扩展已整体迁出 asuka(移入 nagisa 包);KeyFrame::stamp 统一为扫描
起始时刻;执行器新增连续失败熔断与 wait_for() 排空检测、扩展与执行器有
ok() 健康查询。
现行事实以主文档 (index / architecture / parameters / odometry 等)与源码为准。
title: "ROS 节点入口 asuka_rosnode" description: >- main 函数完成 ros::init 后构造 AsukaROS,从 config_ros 读取 imu_topic、lidar_topic 与 map_saving_path,分别订阅 sensor_msgs::Imu 与 PointCloud2 并转发到 insert_imu/insert_frame;ros::spin 结束后 wait(true) 等待队列清空再 save 地图,是理解进程生命周期的最小入口。
ROS 节点入口 asuka_rosnode
模块职责
apps/asuka_rosnode.cpp 是整个 asuka 包最小、最直接的进程入口(仅 33 行)。它自身不做任何算法与数据处理,只承担四件事:
- 初始化 ROS:
ros::init(argc, argv, "asuka_rosnode")并持有ros::NodeHandle; - 装配核心对象:构造
AsukaROS asuka_ros(nh),由后者完成配置定位、里程计插件装载、异步执行器与扩展模块的创建; - 接线上游话题:读取
config_ros中的imu_topic、lidar_topic与map_saving_path,订阅sensor_msgs::Imu与sensor_msgs::PointCloud2,把消息原样转发给insert_imu/insert_frame; - 定义进程生命周期:
ros::spin()阻塞 →wait(true)排空队列 →save(map_saving_path)落盘地图 → 返回并触发析构。
换言之,main 是进程生命周期的"骨架",而所有实质性装配与数据路径都封装在 AsukaROS 中。
main 函数逐段解读
装配阶段(L8–L17)
AsukaROS 构造函数(asuka_ros.cpp L19–L43)在 main 看不见的地方完成了真正的装配:定位 asuka 包的 config 目录初始化 GlobalConfig,按 config_odometry 的 so_name 通过 OdometryEstimation::load_module 动态装载算法插件并取出其共享的 cloud_preprocess,创建 AsyncOdometryEstimation 异步执行器,再读取 lidar_type、acc_scale、imu_time_offset、time_offset_lidar_to_imu、map_saving_path 等 ROS 边界参数,最后 load_extensions() 装载扩展模块。注意:AsukaROS 构造失败会直接抛 std::runtime_error(L28–L30),main 未捕获,即插件装载失败等价于进程启动失败。
随后 main 自己再次打开 config_ros:
asuka::Config config(asuka::GlobalConfig::get_config_path("config_ros"));
const std::string imu_topic = config.param<std::string>("ros", "imu_topic", "/livox/imu");
const std::string lidar_topic = config.param<std::string>("ros", "lidar_topic", "/livox/lidar");
const std::string map_saving_path = config.param<std::string>("ros", "map_saving_path", "");
三个参数都带兜底默认值:IMU 话题 /livox/imu、点云话题 /livox/lidar、保存路径为空串。main 读取的 map_saving_path 用于传给 save(),与构造函数中读入成员的同名配置同源,二者天然一致。
订阅阶段(L19–L26)
ros::Subscriber imu_sub = nh.subscribe<sensor_msgs::Imu>(imu_topic, 1000, [&](const sensor_msgs::Imu::ConstPtr& msg) {
asuka_ros.insert_imu(msg);
});
ros::Subscriber points_sub =
nh.subscribe<sensor_msgs::PointCloud2>(lidar_topic, 50, [&](const sensor_msgs::PointCloud2::ConstPtr& msg) {
asuka_ros.insert_frame(msg);
});
两个回调都是纯转发:lambda 以 [&] 捕获栈上的 asuka_ros(其生命周期覆盖 spin/wait/save 全程,安全)。值得注意的是队列长度不对称:IMU 1000、点云 50,反映 IMU 频率远高于雷达帧率;这两个数值硬编码在入口中,不在配置里。真正的转换、回环检查、标定补偿都发生在 AsukaROS::insert_imu/insert_frame 内部,入口层完全不感知。
生命周期阶段(L28–L31)
这四行是理解进程生命周期的关键序列:
ros::spin():主线程阻塞,ROS 回调队列在此被分发。进程常态停留在这一行(流式在线运行);wait(true)(asuka_ros.cppL110–L116):以ros::WallRate(100)即 100Hz 轮询async->workload(),直到输入队列与算法内部负载清零才返回,保证退出前"消化完"积压数据;save(map_saving_path)(L118–L155):先async->stop()强制停止并 join worker 线程,再调用odometry->save_map()。代码注释明确说明原因:workload()只反映输入缓冲,不先停 worker,它可能仍在process_scan中写map_cloud,与保存读取构成数据竞争/堆破坏;return 0:随后按声明逆序析构局部对象(订阅器 →config→asuka_ros),~AsukaROS(L45–L57)依次async->stop()(幂等)→ 各扩展模块stop()→at_exit(map_saving_path)。
进程生命周期图
flowchart TD
A["main()<br>ros::init('asuka_rosnode')"] --> B["构造 AsukaROS<br>GlobalConfig → 装载 odometry 插件<br>创建 AsyncOdometryEstimation → load_extensions"]
B --> C["读取 config_ros<br>imu_topic / lidar_topic / map_saving_path"]
C --> D["订阅 sensor_msgs::Imu<br>队列 1000 → insert_imu"]
C --> E["订阅 sensor_msgs::PointCloud2<br>队列 50 → insert_frame"]
D --> F["ros::spin()<br>主线程阻塞,常驻阶段"]
E --> F
F --> G["wait(true)<br>100Hz 轮询 async->workload()==0"]
G --> H["save(map_saving_path)<br>async->stop() 先行 → save_map → PCD 落盘"]
H --> I["return 0<br>~AsukaROS:async->stop → ext->stop → at_exit"]
关键节点解释:
- B → C:配置被读取两次(构造函数读 ROS 参数集、
main读话题与保存路径),同源同键,不会失配; - F:spin 期间回调在 ROS spinner 线程执行,算法计算在
AsyncOdometryEstimation的 worker 线程执行,二者经互斥锁保护的环形缓冲队列解耦; - G → H 的顺序不可颠倒:
wait只保证"无积压",save内部的stop()才保证"worker 已 join",两道闸门共同消除保存时的数据竞争; - I:析构链是
save之后的第一道也是最后一道防线,stop()的幂等性(running.exchange(false))使重复停止安全。
关键状态
AsukaROS 在入口装配后持有的核心状态(asuka_ros.hpp L44–L60):
| 状态 | 来源 | 作用 |
|---|---|---|
odometry / cloud_preprocess |
config_odometry 的 so_name |
算法插件与其私有预处理 |
async |
构造时创建 | 环形缓冲异步执行器,持有 worker 线程 |
lidar_type / acc_scale / 两个时间偏移 |
config_ros |
点云分发与 IMU 标定补偿 |
map_saving_path |
config_ros |
save() 与扩展模块 at_exit 共用 |
last_timestamp_lidar/imu |
运行时更新 | 时间回环检测基准(初值 -1.0) |
进程级状态迁移:spin 中(在线流式)→ 排空中(drain)→ 保存中(stop + 落盘)→ 析构(worker → 扩展模块 → at_exit)。状态推进完全由 main 的四行顺序驱动,无额外状态机。
边界条件
- spin 的返回条件:
ros::spin()只在节点被关闭(SIGINT /ros::shutdown)时返回;正常使用中save每进程仅执行一次,位于退出路径上。 wait(true)依赖ros::ok():循环条件是while (ros::ok())。若 spin 因 Ctrl+C 返回,ros::ok()通常已为 false,循环体不会执行,"排空等待"退化为立即返回;此时仍留在缓冲队列中未消费的数据不会被处理,save内的stop()会 join worker 并保存已积分的部分。map_saving_path为空(默认值)时,save()的条件cloud && !cloud->empty() && !path.empty()不满足,直接跳过落盘,进程仍正常退出。- 路径语义二分:
save中若路径带扩展名则视为完整文件名直接使用;否则视为目录,create_directories后按时间戳写mapping_%Y%m%d_%H%M%S.pcd(PCL 二进制格式,点类型压平为PointXYZI)。 - 落盘失败不致命:
pcl::IOException被 catch 后仅logger->warn,退出流程继续。 - IMU/点云队列溢出:入口的 1000/50 为 ROS 订阅队列,处理过慢时旧消息会被丢弃,这属于 ROS 层行为,
AsukaROS不感知。
扩展点
- 话题重接线:
imu_topic/lidar_topic全部由config_ros.json控制,换话题不需要重新编译; - 保存目标定制:通过给
map_saving_path一个带扩展名的路径可固定输出文件名,否则走时间戳目录约定; - 退出前插入自定义逻辑:
save(...)与return 0之间是天然挂载点;扩展模块侧则统一通过at_exit(map_saving_path)收尾; - 非 spin 驱动的入口:
main暴露的insert_imu / insert_frame / workload / wait / save正是AsukaROS的完整对外接口面,其他运行形态(nodelet、rosbag 离线回放)复用同一接口汇聚到同一数据链路,仅替换外层驱动方式。
主要文件
| 文件 | 角色 |
|---|---|
src/asuka/apps/asuka_rosnode.cpp |
入口 main,定义进程生命周期(33 行) |
src/asuka/src/asuka/asuka_ros.cpp |
AsukaROS 实现:装配、回调转发、等待与保存 |
src/asuka/include/asuka/asuka_ros.hpp |
对外接口与成员状态声明 |
src/asuka/config/config_ros.json |
话题名、雷达类型、标定参数与保存路径的默认配置 |
Sources: src/asuka/apps/asuka_rosnode.cpp#L1-L33 | src/asuka/src/asuka/asuka_ros.cpp#L1-L186 | src/asuka/include/asuka/asuka_ros.hpp#L1-L64 | src/asuka/config/config_ros.json#L1-L12 | src/asuka/src/asuka/core/async_odometry_estimation.cpp#L1-L86
关联源码
src/asuka/apps/asuka_rosnode.cpp