first commit

This commit is contained in:
2026-09-02 15:13:13 +08:00
commit cb00322a50
107 changed files with 10918 additions and 0 deletions
@@ -0,0 +1,225 @@
// =============================================================================
// vtc_config.cpp —— 配置加载器实现(启动期一次性解析)
//
// 对应规格:docs/物理追踪思路.md §8(阈值集中配置化,NFR-007)。
//
// 实现要点:
// - 仅在启动期调用一次:缺字段 / 类型错误 / 非法取值抛
// std::runtime_error,异常消息携带完整字段名(如 "heading.gain");
// - 热路径绝不触碰 JSON:解析结果拷贝进 TrackingConfig 后即释放;
// - 本文件是全库唯一包含 nlohmann/json 的 TU。
// =============================================================================
#include "visual_track_control/vtc_config.h"
#include <fstream>
#include <stdexcept>
#include <string>
#include <nlohmann/json.hpp>
namespace drone::vtc {
namespace {
using Json = nlohmann::json;
/// 读取并解析 JSON 文件;失败抛异常并携带路径
Json ParseConfigFile(const std::string& path) {
std::ifstream ifs(path);
if (!ifs) {
throw std::runtime_error("配置文件打开失败: " + path);
}
try {
return Json::parse(ifs);
} catch (const std::exception& e) {
throw std::runtime_error("配置文件解析失败: " + path + " (" + e.what() + ")");
}
}
/// 取必需的对象分组;缺失或类型错抛异常
const Json& RequireSection(const Json& root, const char* section) {
const auto it = root.find(section);
if (it == root.end() || !it->is_object()) {
throw std::runtime_error(std::string("配置缺失分组或类型错误: ") + section);
}
return *it;
}
/// 取必需的数值字段;缺失/类型错抛异常并携带字段名
double RequireNumber(const Json& section, const char* section_name, const char* key) {
const auto it = section.find(key);
if (it == section.end() || !it->is_number()) {
throw std::runtime_error(std::string("配置缺失字段或类型错误: ") +
section_name + "." + key);
}
return it->get<double>();
}
/// 取必需的整数字段
std::int64_t RequireInteger(const Json& section, const char* section_name,
const char* key) {
const auto it = section.find(key);
if (it == section.end() || !it->is_number_integer()) {
throw std::runtime_error(std::string("配置缺失字段或类型错误(需整数): ") +
section_name + "." + key);
}
return it->get<std::int64_t>();
}
/// 取必需的字符串字段
std::string RequireString(const Json& section, const char* section_name,
const char* key) {
const auto it = section.find(key);
if (it == section.end() || !it->is_string()) {
throw std::runtime_error(std::string("配置缺失字段或类型错误(需字符串): ") +
section_name + "." + key);
}
return it->get<std::string>();
}
/// 校验字段为正数;非法抛异常并携带字段名
double RequirePositive(double value, const char* field_name) {
if (!(value > 0.0)) {
throw std::runtime_error(std::string("配置非法(必须为正): ") + field_name);
}
return value;
}
/// 校验字段为非负数
double RequireNonNegative(double value, const char* field_name) {
if (value < 0.0) {
throw std::runtime_error(std::string("配置非法(不得为负): ") + field_name);
}
return value;
}
/// 校验字段在开区间 (lo, hi)
double RequireRange(double value, double lo, double hi, const char* field_name) {
if (!(value > lo && value < hi)) {
throw std::runtime_error(std::string("配置非法(超出范围): ") + field_name);
}
return value;
}
ControlGroupConfig LoadControl(const Json& root) {
const Json& s = RequireSection(root, "control");
ControlGroupConfig c;
c.frequency_hz = RequirePositive(RequireNumber(s, "control", "frequency_hz"),
"control.frequency_hz");
c.visual_stale_ms = RequireInteger(s, "control", "visual_stale_ms");
c.radar_stale_ms = RequireInteger(s, "control", "radar_stale_ms");
c.attitude_stale_ms = RequireInteger(s, "control", "attitude_stale_ms");
if (c.visual_stale_ms <= 0 || c.radar_stale_ms <= 0 || c.attitude_stale_ms <= 0) {
throw std::runtime_error("配置非法: control.*_stale_ms 必须为正");
}
return c;
}
CameraGroupConfig LoadCamera(const Json& root) {
const Json& s = RequireSection(root, "camera");
CameraGroupConfig c;
c.image_width = static_cast<int>(
RequirePositive(RequireInteger(s, "camera", "image_width"), "camera.image_width"));
c.image_height = static_cast<int>(
RequirePositive(RequireInteger(s, "camera", "image_height"), "camera.image_height"));
c.fov_h_deg = RequireRange(RequireNumber(s, "camera", "fov_h_deg"), 0.0, 180.0,
"camera.fov_h_deg");
c.fov_v_deg = RequireRange(RequireNumber(s, "camera", "fov_v_deg"), 0.0, 180.0,
"camera.fov_v_deg");
return c;
}
HeadingGroupConfig LoadHeading(const Json& root) {
const Json& s = RequireSection(root, "heading");
HeadingGroupConfig h;
const std::string mode = RequireString(s, "heading", "mode");
if (mode == "position") {
h.mode = HeadingMode::kPosition;
} else if (mode == "rate") {
h.mode = HeadingMode::kRate;
} else {
throw std::runtime_error("配置非法: heading.mode 仅支持 position / rate");
}
h.gain = RequireNonNegative(RequireNumber(s, "heading", "gain"), "heading.gain");
h.yaw_rate_limit_dps = RequirePositive(
RequireNumber(s, "heading", "yaw_rate_limit_dps"), "heading.yaw_rate_limit_dps");
h.yaw_slew_limit_dps2 = RequirePositive(
RequireNumber(s, "heading", "yaw_slew_limit_dps2"), "heading.yaw_slew_limit_dps2");
return h;
}
VerticalGroupConfig LoadVertical(const Json& root) {
const Json& s = RequireSection(root, "vertical");
VerticalGroupConfig v;
v.vz_gain_mps_per_deg = RequireNonNegative(
RequireNumber(s, "vertical", "vz_gain_mps_per_deg"), "vertical.vz_gain_mps_per_deg");
v.vz_limit_mps = RequirePositive(RequireNumber(s, "vertical", "vz_limit_mps"),
"vertical.vz_limit_mps");
return v;
}
DistanceGroupConfig LoadDistance(const Json& root) {
const Json& s = RequireSection(root, "distance");
DistanceGroupConfig d;
d.d_exp_m = RequirePositive(RequireNumber(s, "distance", "d_exp_m"), "distance.d_exp_m");
d.kp = RequireNonNegative(RequireNumber(s, "distance", "kp"), "distance.kp");
d.ki = RequireNonNegative(RequireNumber(s, "distance", "ki"), "distance.ki");
d.kd = RequireNonNegative(RequireNumber(s, "distance", "kd"), "distance.kd");
d.integral_limit = RequireNonNegative(RequireNumber(s, "distance", "integral_limit"),
"distance.integral_limit");
d.derivative_filter_coef = RequireRange(
RequireNumber(s, "distance", "derivative_filter_coef"), -0.000001, 1.0,
"distance.derivative_filter_coef");
if (d.derivative_filter_coef < 0.0) {
d.derivative_filter_coef = 0.0; // 容许 -0.0
}
d.approach_velocity_limit_mps = RequirePositive(
RequireNumber(s, "distance", "approach_velocity_limit_mps"),
"distance.approach_velocity_limit_mps");
d.retreat_velocity_limit_mps = RequirePositive(
RequireNumber(s, "distance", "retreat_velocity_limit_mps"),
"distance.retreat_velocity_limit_mps");
const std::string action = RequireString(s, "distance", "no_distance_action");
if (action == "hold") {
d.no_distance_action = NoDistanceAction::kHold;
} else if (action == "slow_approach") {
d.no_distance_action = NoDistanceAction::kSlowApproach;
} else if (action == "exit") {
d.no_distance_action = NoDistanceAction::kExit;
} else {
throw std::runtime_error(
"配置非法: distance.no_distance_action 仅支持 hold / slow_approach / exit");
}
d.no_distance_approach_limit_mps = RequireNonNegative(
RequireNumber(s, "distance", "no_distance_approach_limit_mps"),
"distance.no_distance_approach_limit_mps");
return d;
}
AccelLimitConfig LoadAccelLimit(const Json& root) {
const Json& s = RequireSection(root, "accel_limit");
AccelLimitConfig a;
a.ax_mps2 = RequirePositive(RequireNumber(s, "accel_limit", "ax_mps2"),
"accel_limit.ax_mps2");
a.ay_mps2 = RequirePositive(RequireNumber(s, "accel_limit", "ay_mps2"),
"accel_limit.ay_mps2");
a.az_mps2 = RequirePositive(RequireNumber(s, "accel_limit", "az_mps2"),
"accel_limit.az_mps2");
return a;
}
} // namespace
TrackingConfig TrackingConfig::LoadFromJson(const std::string& path) {
const Json root = ParseConfigFile(path);
TrackingConfig cfg;
cfg.control = LoadControl(root);
cfg.camera = LoadCamera(root);
cfg.heading = LoadHeading(root);
cfg.vertical = LoadVertical(root);
cfg.distance = LoadDistance(root);
cfg.accel_limit = LoadAccelLimit(root);
return cfg;
}
} // namespace drone::vtc