历史快照
本专题页写于包拆分与 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: "配置系统与 JSON 读取"
description: >-
JSON 配置读取与类型化 param
配置系统与 JSON 读取
模块职责与文件分层
配置系统由三个文件协作完成,分层非常清晰:
| 文件 | 职责 |
|---|---|
utility/config.hpp |
对外接口。声明 Config 基类与 GlobalConfig 单例;所有模板访问接口只有声明。用 std::any config 擦除 JSON 类型,使头文件不暴露 nlohmann::json |
utility/config_impl.hpp |
模板实现载体。包含 ConfigTraits 类型转换特质与 param<T> 系列模板的定义,并用 ASUKA_DEFINE_CONFIG_IO 宏对常用类型显式实例化 |
utility/config.cpp |
非模板部分:JSON 文件解析、has/has_param/save、14 个类型的显式实例化声明,以及单例与配置路径解析逻辑 |
由于模板定义在 config_impl.hpp 而非 config.hpp,使用方若要调用 param<T> 必须显式包含实现头——asuka_ros.cpp 就同时 include 了两份。
配置的形状与加载
JSON 采用两层结构:顶层按模块名分组,每个模块下是键值对,即 {module: {param: value}}。config.json 作为顶层入口,其 global 段登记各子配置文件的文件名(config_ros、config_sensor、config_odometry、config_extensions),logging 段则由 configure_logging() 消费。
Config 构造函数(config.cpp L17-L35)的加载行为:
- 文件名为空:直接持有空 JSON(纯内存配置);
- 打开失败:
spdlog::error后继续,得到空 JSON——所有后续param都会走默认值分支,进程不崩溃; - 解析异常:捕获
nlohmann::json::exception并记录错误。parse(ifs, nullptr, true, true)的两个true表示允许抛异常、忽略注释,因此配置文件中可以写注释。
类型化访问接口与三级失败策略
param<T> 的实现路径是:any_cast 取回 JSON → 逐层 find(module_name)、find(param_name) → 用 ConfigTraits<T>::convert(parameter->get<InType>()) 完成类型转换。同一读取能力提供三种失败语义:
| 接口 | 参数缺失时 | 日志 |
|---|---|---|
param<T>(module, key) 返回 optional<T> |
nullopt |
无 |
param<T>(module, key, default) |
返回默认值 | warn |
param_cast<T>(module, key) |
std::abort() 终止进程 |
critical |
param_nested 系列支持深层嵌套:按 nested_module_names 顺序逐层下钻,任一层缺失即返回 nullopt(空列表同样返回 nullopt)。override_param 则反向写入:json[module][param] = ConfigTraits<T>::invert(value),恒返回 true,用于运行时改写内存中的配置。save 以 setw(2) 缩进落盘;GlobalConfig::dump 先 create_directories 再保存 config.json。
ConfigTraits:JSON 数组到 Eigen 的桥
匿名命名空间中的特质模板定义了"JSON 中怎么存、C++ 里怎么取"的约定:
- 主模板:
InType = OutType = T,直通转换; Eigen::Matrix<T,N,M>:JSON 侧是vector<double>,长度必须等于N*M否则nullopt;列向量(M==1)直接Map,一般矩阵按 RowMajor 读入与写出;Quaternion<T>:4 个 double,读入后自动normalized(),序列化顺序为x,y,z,w;Isometry3d:7 个 double,格式[tx,ty,tz,qx,qy,qz,qw],四元数归一化后转旋转矩阵;invert反向分解为平移 + 四元数。
GlobalConfig 单例与路径解析链
GlobalConfig 继承 Config,构造函数私有,通过静态 inst 指针维护单例(config.cpp L72-L81)。instance(config_path, override_path) 在 inst == nullptr || override_path 时执行 delete inst; new GlobalConfig(config_path + "/config.json"),随后把目录注入 JSON(override_param("global", "config_path", config_path))并调用 configure_logging()。
get_config_path(config_name) 是两段式解析:目录取自 global/config_path(缺省 "."),文件名取自 global/<config_name>(缺省 config_name + ".json"),二者用 / 拼接。由于只是字符串拼接,文件名可以携带子目录——config.json 中 config_odometry 指向 odometry/config_fastlio.json 就是靠这一点生效。
调用链示例
AsukaROS 构造函数(asuka_ros.cpp L19-L43)是标准用法样本:先定位包内 config 目录并引导单例,再为每个子系统构造独立的局部 Config 实例读取参数:
flowchart TD
A["AsukaROS 构造函数"] --> B["ros::package::getPath('asuka') + '/config'"]
B --> C["GlobalConfig::instance(config_dir, override_path=true)"]
C --> D["delete 旧单例 / new GlobalConfig"]
D --> E["解析 config_dir/config.json"]
E --> F["override_param 注入 global/config_path"]
F --> G["configure_logging 消费 logging 段"]
G --> H["get_config_path(name)"]
H --> I["目录 = param('global','config_path')"]
H --> J["文件名 = param('global', name)<br/>缺省 name + '.json'"]
I --> K["dir + '/' + filename"]
J --> K
K --> L["局部 Config 实例<br/>config_odometry / config_ros / config_extensions"]
L --> M["param<T>(module, key, default)<br/>so_name / lidar_type / acc_scale ..."]
关键节点说明:引导(A→E)只用一次 ros::package::getPath 定位包内目录;注入(F)把目录写进单例内存,之后所有 get_config_path 无需再传路径;两段拼接(I/J→K)让"换哪个文件"成为纯数据改动——切换里程计实现只需修改 global.config_odometry 的值;局部 Config(L)各自持有独立 JSON,与单例解耦。
边界条件
- 空文件名 / 打不开 / 解析失败均降级为空 JSON,不会让构造函数抛异常,但所有参数回落默认值;
- 三种失败策略严格区分:缺失是常态(warn + 默认),
param_cast缺失视为致命(abort);但类型不匹配(如数字位置放了字符串)会由get<InType>()抛 nlohmann 异常,接口层未捕获; - Eigen 转换有尺寸校验(N*M / 4 / 7),不符返回
nullopt走缺失路径; instance(..., override_path=true)会销毁并重建单例,此前持有的GlobalConfig*全部失效(裸new/delete);- 四元数读取自动归一化,写入时按
x,y,z,w平铺。
扩展点
- 新参数类型:为
ConfigTraits<T>添加InType/OutType/convert/invert特化,并在 config.cpp 追加一行ASUKA_DEFINE_CONFIG_IO(新类型)完成预实例化(当前覆盖 bool/int/size_t/float/double/string、三种 vector、Vector3d/Vector4d/Matrix3d/Quaterniond/Isometry3d); - 派生配置类:
Config提供虚析构与protected std::any config,子类可复用整套模板接口并定制存储; - 运行时改写:
override_param+save/dump可把程序内计算的值固化回磁盘; - 与上游消费方的衔接:
get_config_path的四个名字正好对应config.json的global段,ROS 层、扩展模块加载与各算法子配置都经由本页接口取参。
Sources: src/asuka/src/asuka/utility/config.cpp#L1 src/asuka/include/asuka/utility/config.hpp#L1 src/asuka/include/asuka/utility/config_impl.hpp#L1
关联源码
src/asuka/src/asuka/utility/config.cppsrc/asuka/include/asuka/utility/config.hppsrc/asuka/include/asuka/utility/config_impl.hpp