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++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "config.h"
#include "json_utils.h"
#include <algorithm>
#include <cstdio>
#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)
{
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))
{
new_cfg.log.retain_days = (int)d;
if (new_cfg.log.retain_days < 1) new_cfg.log.retain_days = 1;
}
if (json_get_string(text, "log", "clean_time", s))
{
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;
}
}
// 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;
std::cout << "================================" << std::endl;
return true;
}