You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
15 KiB
C++

2 months ago
#include "config.h"
#include "json_utils.h"
#include <algorithm>
#include <cstdio>
2 months ago
#include <iostream>
#include <fstream>
#include <sstream>
// ==================== 全局配置实例 ====================
AppConfig cfg;
// ==================== 线程级雷达配置绑定 ====================
// 每个雷达一份参数;业务代码用 radar_cfg() 取"当前线程"该用哪一份。
// 采集线程/请求线程/异步保存线程在入口用 RadarScope 绑定。
namespace
{
thread_local std::shared_ptr<const RadarConfig> tls_rc; // 当前线程绑定的雷达配置
const RadarConfig g_empty_rc; // 无任何雷达时的空配置(全默认)
}
const RadarConfig& radar_cfg()
{
if (tls_rc) return *tls_rc;
std::shared_ptr<const RadarConfig> f = cfg.first();
return f ? *f : g_empty_rc;
}
RadarScope::RadarScope(const std::string& radar_id)
: prev_(tls_rc)
{
std::shared_ptr<const RadarConfig> r = cfg.find(radar_id);
if (r) tls_rc = r; // 未找到时保持原绑定,不使用默认雷达兜底
}
RadarScope::~RadarScope() { tls_rc = prev_; }
std::shared_ptr<const RadarConfig> AppConfig::find(const std::string& id) const
{
if (id.empty()) return first();
auto it = radars.find(id);
return (it == radars.end()) ? std::shared_ptr<const RadarConfig>() : it->second;
}
std::shared_ptr<const RadarConfig> AppConfig::first() const
{
if (ids.empty()) return std::shared_ptr<const RadarConfig>();
auto it = radars.find(ids.front());
return (it == radars.end()) ? std::shared_ptr<const RadarConfig>() : it->second;
}
// 解析 "HH:MM[:SS]" -> 时/分/秒;非法返回 false
static bool parse_clean_time(const std::string& s, int& h, int& m, int& sec)
{
int a = -1, b = -1, c = -1;
int n = std::sscanf(s.c_str(), "%d:%d:%d", &a, &b, &c);
if (n == 2) { h = a; m = b; sec = 0; }
else if (n == 3) { h = a; m = b; sec = c; }
else return false;
return h >= 0 && h <= 23 && m >= 0 && m <= 59 && sec >= 0 && sec <= 59;
}
// 把一份"单雷达参数对象"解析进 rc(缺项保持默认值)。
// obj 形如 {"radar":{..},"scan":{..},"detect":{..},"count":{..},"filter":{..}}
static void parse_radar_sections(const std::string& obj, RadarConfig& rc)
2 months ago
{
std::string s; double d; bool bv;
std::vector<std::pair<double, double>> poly;
// ---------- radar: 雷达连接与雷达内置滤波 ----------
if (json_get_string(obj, "radar", "ip", s)) rc.radar.ip = s;
if (json_get_number(obj, "radar", "port", d)) rc.radar.port = (uint16_t)d;
if (json_get_number(obj, "radar", "hz", d)) rc.radar.hz = d;
if (json_get_number(obj, "radar", "tail_filter", d)) rc.radar.tail_filter = (int)d;
if (json_get_number(obj, "radar", "front_filter", d)) rc.radar.front_filter = (int)d;
if (json_get_number(obj, "radar", "far_hr_filter", d)) rc.radar.far_hr_filter = (int)d;
if (json_get_bool(obj, "radar", "near_hr_filter", bv)) rc.radar.near_hr_filter = bv;
// ---------- scan: 扫描采集与走行 ----------
if (json_get_number(obj, "scan", "collect_duration_s", d)) rc.scan.collect_duration_s = (int)d;
if (json_get_number(obj, "scan", "z_speed", d)) rc.scan.z_speed = d;
if (json_get_string(obj, "scan", "z_speed_type", s))
{
if (s == "configuration" || s == "uniformSpeed") rc.scan.z_speed_type = s;
else rc.scan.z_speed_type = "uniformSpeed";
}
// ---------- detect: 截面分析 ----------
if (json_get_number(obj, "detect", "z_range_mm", d)) rc.detect.z_range_mm = d;
if (json_get_number(obj, "detect", "y_range_mm", d)) rc.detect.y_range_mm = d;
if (json_get_number(obj, "detect", "rect_width_px", d)) rc.detect.rect_width_px = (int)d;
if (json_get_number(obj, "detect", "rect_length_ratio", d)) rc.detect.rect_length_ratio = d;
// ---------- count: 分层计数 ----------
if (json_get_number(obj, "count", "floor_height", d)) rc.count.floor_height = d;
if (json_get_number(obj, "count", "base_tolerance", d))
{
rc.count.base_tolerance = d;
if (rc.count.base_tolerance < 0) rc.count.base_tolerance = 0;
}
if (json_get_number(obj, "count", "per_level_tolerance", d))
{
rc.count.per_level_tolerance = d;
if (rc.count.per_level_tolerance < 0) rc.count.per_level_tolerance = 0;
}
if (json_get_number(obj, "count", "point_dilate", d))
{
rc.count.point_dilate = (int)d;
if (rc.count.point_dilate < 1) rc.count.point_dilate = 1;
if (rc.count.point_dilate > 64) rc.count.point_dilate = 64;
}
if (json_get_number(obj, "count", "interp_bucket_deg", d))
{
// 角度插值桶宽(度)。应等于雷达实际角度分辨率; 越界时回退 0.08
rc.count.interp_bucket_deg = d;
if (rc.count.interp_bucket_deg < 0.005) rc.count.interp_bucket_deg = 0.08;
if (rc.count.interp_bucket_deg > 5.0) rc.count.interp_bucket_deg = 0.08;
}
if (json_get_string(obj, "count", "z_interpolate_mode", s))
{
if (s == "CopyTranslation" || s == "AngleBucketInterpolation"
|| s == "AngleBucketInterpolationByDilate") rc.count.z_interpolate_mode = s;
else rc.count.z_interpolate_mode = "none"; // 未知拼写一律按不插值处理
}
if (json_get_string(obj, "count", "layer_algorithm", s))
{
if (s == "HalfMax" || s == "KdePeak") rc.count.layer_algorithm = s;
else rc.count.layer_algorithm = "LevelTolerance"; // 非法值回退默认算法
}
if (json_get_number(obj, "count", "kde_bandwidth", d))
{
rc.count.kde_bandwidth = d;
if (rc.count.kde_bandwidth < 0) rc.count.kde_bandwidth = 0;
}
if (json_get_number(obj, "count", "kde_step", d))
{
rc.count.kde_step = d;
if (rc.count.kde_step < 0.1) rc.count.kde_step = 0.1; // 步长过小无意义且极慢
if (rc.count.kde_step > 100.0) rc.count.kde_step = 100.0;
}
// ---------- filter: 多边形过滤 ----------
poly.clear();
if (json_get_point_array(obj, "filter", "polygon", poly) >= 3)
{
rc.filter.polygon = poly;
rc.filter.poly_n = (int)poly.size();
}
// ---------- filter.tail_cut ----------
{
std::string o = json_get_raw(obj, "filter", "tail_cut");
if (!o.empty())
{
if (json_get_bool(o, "enable", bv)) rc.filter.tail_cut.enable = bv;
if (json_get_number(o, "max_tail_length", d)) { rc.filter.tail_cut.max_tail_length = (int)d; if (rc.filter.tail_cut.max_tail_length < 0) rc.filter.tail_cut.max_tail_length = 0; }
if (json_get_number(o, "y_delta", d)) { rc.filter.tail_cut.y_delta = d; if (rc.filter.tail_cut.y_delta < 0) rc.filter.tail_cut.y_delta = 0; }
if (json_get_number(o, "min_run_length", d)) { rc.filter.tail_cut.min_run_length = (int)d; if (rc.filter.tail_cut.min_run_length < 1) rc.filter.tail_cut.min_run_length = 1; }
if (json_get_number(o, "core_min_len", d)) { rc.filter.tail_cut.core_min_len = (int)d; if (rc.filter.tail_cut.core_min_len < 0) rc.filter.tail_cut.core_min_len = 0; }
if (json_get_number(o, "support_rows", d)) { rc.filter.tail_cut.support_rows = (int)d; if (rc.filter.tail_cut.support_rows < 0) rc.filter.tail_cut.support_rows = 0; if (rc.filter.tail_cut.support_rows > 4) rc.filter.tail_cut.support_rows = 4; }
}
}
// ---------- filter.sparse_cut ----------
{
std::string o = json_get_raw(obj, "filter", "sparse_cut");
if (!o.empty())
{
if (json_get_bool(o, "enable", bv)) rc.filter.sparse_cut.enable = bv;
if (json_get_number(o, "x_radius", d)) { rc.filter.sparse_cut.x_radius = d; if (rc.filter.sparse_cut.x_radius < 0) rc.filter.sparse_cut.x_radius = 0; }
if (json_get_number(o, "circle_min", d)) { rc.filter.sparse_cut.circle_min = (int)d; if (rc.filter.sparse_cut.circle_min < 1) rc.filter.sparse_cut.circle_min = 1; }
if (json_get_number(o, "run_min", d)) { rc.filter.sparse_cut.run_min = (int)d; if (rc.filter.sparse_cut.run_min < 1) rc.filter.sparse_cut.run_min = 1; }
}
}
}
// ==================== 配置加载(根级 map 型 JSON + // 注释行) ====================
// 结构: {"server":{...},"log":{...},"<雷达标识>":{radar/scan/detect/count/filter}, ...}
// 兼容: 旧格式(根下直接 radar/scan/... 段)时整体视为一台雷达,标识 "default"。
bool load_config(const std::string& path)
{
std::ifstream fin(path);
if (!fin.is_open())
{
// 绝不 exit:运行期 config 命令会调用本函数,exit 会让整个服务进程瞬间消失
std::cerr << "[错误] 未找到配置文件: " << path << " (保留现有配置不变)" << std::endl;
return false;
}
// 1. 剔除行首(忽略空白)以 // 开头的注释行,其余拼接为 JSON 文本
std::ostringstream json_oss;
std::string line;
while (std::getline(fin, line))
{
size_t b = line.find_first_not_of(" \t\r\n");
if (b != std::string::npos && line.compare(b, 2, "//") == 0)
continue; // 整行注释
json_oss << line << "\n";
}
fin.close();
const std::string text = json_oss.str();
std::string s; double d; bool bv;
// 全程解析到临时对象:解析失败/无雷达时不改动正在使用的 cfg,
// 避免运行期 config 重载把全局配置清成空导致其它线程访问空 map。
AppConfig new_cfg = cfg;
// 2. 全局段: server / log
if (json_get_string(text, "server", "mode", s)) new_cfg.server.mode = s;
if (json_get_number(text, "server", "port", d)) new_cfg.server.port = (uint16_t)d;
if (json_get_bool(text, "log", "enable", bv)) new_cfg.log.enable = bv;
if (json_get_number(text, "log", "retain_days", d))
2 months ago
{
new_cfg.log.retain_days = (int)d;
if (new_cfg.log.retain_days < 1) new_cfg.log.retain_days = 1;
2 months ago
}
if (json_get_string(text, "log", "clean_time", s))
2 months ago
{
int h = -1, m = -1, sec = -1;
if (parse_clean_time(s, h, m, sec))
{
new_cfg.log.clean_hour = h;
new_cfg.log.clean_minute = m;
new_cfg.log.clean_sec = sec;
}
else
{
std::cout << "[WARN] log.clean_time 格式应为 \"HH:MM:SS\",已忽略: " << s
<< " (仍用 " << new_cfg.log.clean_hour << ":"
<< (new_cfg.log.clean_minute < 10 ? "0" : "") << new_cfg.log.clean_minute << ")"
<< std::endl;
}
2 months ago
}
// 3. 雷达段: 除 server/log 外的一级键都是雷达标识
std::map<std::string, std::shared_ptr<RadarConfig>> new_radars;
std::vector<std::string> new_ids;
std::vector<std::string> keys = json_object_keys(text);
// 旧格式判定:根下(一级键)直接有 "radar" 段且带 ip。
// 注意不能直接用 json_get_raw(text,"radar") 判定——它是扁平查找,会命中雷达条目内的 "radar" 子对象。
bool has_top_radar = std::find(keys.begin(), keys.end(), "radar") != keys.end();
std::string radar_raw = has_top_radar ? json_get_raw(text, "radar") : std::string();
bool old_format = !radar_raw.empty() && radar_raw[0] == '{' &&
json_get_string(radar_raw, "ip", s);
if (old_format)
{
std::cout << "[WARN] 检测到旧格式配置(根级 radar/scan/... 段),已按单雷达 \"default\" 载入;"
<< "建议改为根级 map: {\"<雷达标识>\":{radar,scan,detect,count,filter}}" << std::endl;
std::shared_ptr<RadarConfig> rc(new RadarConfig());
rc->id = "default";
parse_radar_sections(text, *rc);
new_radars[rc->id] = rc;
new_ids.push_back(rc->id);
}
else
{
for (const auto& k : keys)
{
if (k == "server" || k == "log") continue;
std::string obj = json_get_raw(text, k);
if (obj.empty() || obj[0] != '{') continue; // 非对象键忽略
std::shared_ptr<RadarConfig> rc(new RadarConfig());
rc->id = k;
parse_radar_sections(obj, *rc);
new_radars[k] = rc;
new_ids.push_back(k);
}
}
if (new_ids.empty())
{
std::cerr << "[错误] 配置中未找到任何雷达(除 server/log 外需至少一个雷达标识对象)!"
<< " 保留现有配置不变。" << std::endl;
return false;
}
// 解析成功:整体提交(旧配置由仍在使用的线程通过 shared_ptr 继续持有,不会悬空)
new_cfg.radars.swap(new_radars);
new_cfg.ids.swap(new_ids);
cfg = std::move(new_cfg);
// ==================== 打印配置摘要 ====================
std::cout << "========== 配置已加载 (雷达" << cfg.ids.size() << "台) ==========" << std::endl;
for (const auto& id : cfg.ids)
{
const RadarConfig& rc = *cfg.radars[id];
std::cout << "[" << id << "] 雷达: " << rc.radar.ip << ":" << rc.radar.port
<< " 帧率: " << rc.radar.hz << "Hz"
<< " 拖尾滤波: " << rc.radar.tail_filter
<< " 前冲滤波: " << rc.radar.front_filter << std::endl;
std::cout << "[" << id << "] 扫描: " << rc.scan.collect_duration_s << "s"
<< " Z速度: " << rc.scan.z_speed_type << " " << rc.scan.z_speed << "cm/s"
<< " 地板高: " << rc.count.floor_height << "mm"
<< " 分层: 基准" << rc.count.base_tolerance
<< "+每层" << rc.count.per_level_tolerance
<< " 层数算法: " << rc.count.layer_algorithm
<< " 插值: " << rc.count.z_interpolate_mode
<< " 插值桶宽: " << rc.count.interp_bucket_deg << "°" << std::endl;
std::cout << "[" << id << "] 多边形(" << rc.filter.poly_n << "点):";
for (const auto& p : rc.filter.polygon)
std::cout << " (" << p.first << "," << p.second << ")";
std::cout << " SparseCut=" << (rc.filter.sparse_cut.enable ? "开" : "关")
<< "(X±" << rc.filter.sparse_cut.x_radius << "mm,同圈>=" << rc.filter.sparse_cut.circle_min
<< ",跨圈>=" << rc.filter.sparse_cut.run_min << ")"
<< " TailCut=" << (rc.filter.tail_cut.enable ? "开" : "关") << std::endl;
}
std::cout << "运行模式: " << cfg.server.mode
<< (cfg.server.mode == "tcp" || cfg.server.mode == "http" ? " (端口 " + std::to_string(cfg.server.port) + ")" : "") << std::endl;
std::cout << "日志保留: " << cfg.log.retain_days << "天"
<< " 每日清理: " << (cfg.log.clean_hour < 10 ? "0" : "") << cfg.log.clean_hour << ":"
<< (cfg.log.clean_minute < 10 ? "0" : "") << cfg.log.clean_minute << ":"
<< (cfg.log.clean_sec < 10 ? "0" : "") << cfg.log.clean_sec << std::endl;
2 months ago
std::cout << "================================" << std::endl;
return true;
2 months ago
}