2025-12-17 13:32:05 +08:00
|
|
|
|
#include "config_manager.h"
|
2026-01-07 10:38:17 +08:00
|
|
|
|
|
|
|
|
|
|
#include <cstdio> // for std::rename, std::remove
|
2025-12-17 13:32:05 +08:00
|
|
|
|
#include <fstream>
|
|
|
|
|
|
|
2026-01-07 10:38:17 +08:00
|
|
|
|
ConfigManager& ConfigManager::getInstance() {
|
|
|
|
|
|
static ConfigManager instance;
|
|
|
|
|
|
return instance;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 注册监听器
|
|
|
|
|
|
void ConfigManager::monitorKey(const std::string& key, KeyUpdateCallback cb) {
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(m_callbackMutex);
|
|
|
|
|
|
m_key_callbacks[key].push_back(cb);
|
|
|
|
|
|
spdlog::debug("Registered observer for config key: '{}'", key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 核心逻辑:对比新旧配置,触发回调
|
|
|
|
|
|
void ConfigManager::checkDiffAndNotify(const json& old_config, const json& new_config) {
|
|
|
|
|
|
// 复制一份监听列表,避免在遍历时加锁时间过长,或者回调中再次注册导致死锁
|
|
|
|
|
|
std::map<std::string, std::vector<KeyUpdateCallback>> callbacks_snapshot;
|
|
|
|
|
|
{
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(m_callbackMutex);
|
|
|
|
|
|
callbacks_snapshot = m_key_callbacks;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int trigger_count = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto& [key, callbacks] : callbacks_snapshot) {
|
|
|
|
|
|
if (!new_config.contains(key))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取新旧值 (如果旧配置没这个key,视为 null)
|
|
|
|
|
|
json old_val = old_config.contains(key) ? old_config[key] : json();
|
|
|
|
|
|
json new_val = new_config[key];
|
|
|
|
|
|
|
|
|
|
|
|
if (old_val != new_val) {
|
|
|
|
|
|
spdlog::info("Config Hot-Reload: Key '{}' changed. Triggering {} callbacks.", key,
|
|
|
|
|
|
callbacks.size());
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto& cb : callbacks) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
cb(new_val); // 执行回调
|
|
|
|
|
|
trigger_count++;
|
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
|
spdlog::error("Exception inside config callback for key '{}': {}", key,
|
|
|
|
|
|
e.what());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (trigger_count > 0) {
|
|
|
|
|
|
spdlog::info("Config reload finished. {} callbacks triggered.", trigger_count);
|
|
|
|
|
|
}
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
json ConfigManager::createDefaultConfig() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return json{
|
|
|
|
|
|
{"device_id", "default-edge-proxy-01"},
|
|
|
|
|
|
{"config_base_path", "/app/config/"},
|
|
|
|
|
|
{"mqtt_broker", "tcp://localhost:1883"},
|
|
|
|
|
|
{"mqtt_client_id_prefix", "edge-proxy-"},
|
|
|
|
|
|
{"data_storage_db_path", "edge_proxy_data.db"},
|
|
|
|
|
|
{"data_cache_db_path", "edge_data_cache.db"},
|
|
|
|
|
|
{"tcp_server_ports", {12345}},
|
|
|
|
|
|
{"web_server_port", 8080},
|
|
|
|
|
|
{"log_level", "debug"},
|
|
|
|
|
|
{"alarm_rules_path", "alarms.json"},
|
|
|
|
|
|
{"piper_executable_path", "/usr/bin/piper"},
|
|
|
|
|
|
{"piper_model_path", "/app/models/model.onnx"},
|
|
|
|
|
|
{"video_config_path", "/app/config/video_config.json"},
|
|
|
|
|
|
};
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-07 10:38:17 +08:00
|
|
|
|
// 原子写入实现
|
2025-12-17 13:32:05 +08:00
|
|
|
|
bool ConfigManager::save_unlocked() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
std::string tmpPath = m_configFilePath + ".tmp";
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 写入临时文件
|
|
|
|
|
|
{
|
|
|
|
|
|
std::ofstream ofs(tmpPath);
|
|
|
|
|
|
if (!ofs.is_open()) {
|
|
|
|
|
|
spdlog::error("Failed to open temp config file '{}' for writing.", tmpPath);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
ofs << m_config_json.dump(4);
|
|
|
|
|
|
ofs.flush(); // 确保缓冲区刷入内核
|
2026-01-07 13:40:52 +08:00
|
|
|
|
// 在某些关键系统上,可能还需要 fsync
|
2026-01-07 10:38:17 +08:00
|
|
|
|
} catch (const json::exception& e) {
|
|
|
|
|
|
spdlog::error("Failed to serialize config. Error: {}", e.what());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
} // ofs 关闭
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 原子重命名 (Atomic Rename)
|
|
|
|
|
|
// 如果重命名失败,原文件不会损坏
|
|
|
|
|
|
if (std::rename(tmpPath.c_str(), m_configFilePath.c_str()) != 0) {
|
|
|
|
|
|
spdlog::error("Failed to rename temp config file to '{}'. Errno: {}", m_configFilePath,
|
|
|
|
|
|
errno);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ConfigManager::load(const std::string& configFilePath) {
|
|
|
|
|
|
// 准备两个容器,用于在锁外进行对比
|
|
|
|
|
|
json old_config_copy;
|
|
|
|
|
|
json new_config_copy;
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
std::unique_lock<std::shared_mutex> lock(m_mutex); // 获取写锁
|
|
|
|
|
|
m_configFilePath = configFilePath;
|
|
|
|
|
|
|
|
|
|
|
|
// 备份当前配置(如果是第一次加载,这里是空的)
|
|
|
|
|
|
old_config_copy = m_config_json;
|
|
|
|
|
|
|
|
|
|
|
|
std::ifstream ifs(m_configFilePath);
|
|
|
|
|
|
if (!ifs.is_open()) {
|
|
|
|
|
|
spdlog::warn("Config file '{}' not found. Creating default.", m_configFilePath);
|
|
|
|
|
|
m_config_json = createDefaultConfig();
|
|
|
|
|
|
// 保存默认文件
|
|
|
|
|
|
if (!save_unlocked()) {
|
|
|
|
|
|
spdlog::error("Failed to create default config file.");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 第一次加载,不需要触发“变更”回调
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
json loaded_json;
|
|
|
|
|
|
ifs >> loaded_json;
|
|
|
|
|
|
|
|
|
|
|
|
// 合并默认值
|
|
|
|
|
|
json defaults = createDefaultConfig();
|
|
|
|
|
|
defaults.merge_patch(loaded_json);
|
|
|
|
|
|
m_config_json = defaults;
|
|
|
|
|
|
|
|
|
|
|
|
// 复制新配置用于后续对比
|
|
|
|
|
|
new_config_copy = m_config_json;
|
|
|
|
|
|
|
|
|
|
|
|
spdlog::info("Config loaded from '{}'. DeviceID: {}", m_configFilePath,
|
|
|
|
|
|
m_config_json.value("device_id", "N/A"));
|
|
|
|
|
|
|
|
|
|
|
|
// 可选:如果缺少key,回写文件
|
|
|
|
|
|
save_unlocked();
|
|
|
|
|
|
|
|
|
|
|
|
} catch (const json::exception& e) {
|
|
|
|
|
|
spdlog::error("Failed to parse config file. Error: {}. Using defaults.", e.what());
|
|
|
|
|
|
m_config_json = createDefaultConfig();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
} // !!! 写锁在这里释放 !!!
|
|
|
|
|
|
|
|
|
|
|
|
// 锁释放后,执行回调通知
|
|
|
|
|
|
// 这样做非常安全,不会因为回调里调用了 get() 而导致死锁
|
|
|
|
|
|
checkDiffAndNotify(old_config_copy, new_config_copy);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-17 13:32:05 +08:00
|
|
|
|
bool ConfigManager::save() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
std::unique_lock<std::shared_mutex> lock(m_mutex);
|
|
|
|
|
|
return save_unlocked();
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
2026-01-07 10:38:17 +08:00
|
|
|
|
|
|
|
|
|
|
std::string ConfigManager::getConfigFilePath() const {
|
|
|
|
|
|
std::shared_lock<std::shared_mutex> lock(m_mutex);
|
|
|
|
|
|
return m_configFilePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-17 13:32:05 +08:00
|
|
|
|
std::string ConfigManager::getDeviceID() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<std::string>("device_id", "default-edge-proxy-01");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getConfigBasePath() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<std::string>("config_base_path", "/app/config/");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getMqttBroker() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<std::string>("mqtt_broker", "tcp://localhost:1883");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getMqttClientID() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<std::string>("mqtt_client_id_prefix", "edge-proxy-") + getDeviceID();
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getDataStorageDbPath() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return getConfigBasePath() + get<std::string>("data_storage_db_path", "edge_proxy_data.db");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getDataCacheDbPath() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return getConfigBasePath() + get<std::string>("data_cache_db_path", "edge_data_cache.db");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getDevicesConfigPath() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return getConfigBasePath() + "devices.json";
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
int ConfigManager::getWebServerPort() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<int>("web_server_port", 8080);
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::vector<uint16_t> ConfigManager::getTcpServerPorts() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<std::vector<uint16_t>>("tcp_server_ports", {12345});
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getLogLevel() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<std::string>("log_level", "debug");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getAlarmRulesPath() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return getConfigBasePath() + get<std::string>("alarm_rules_path", "alarms.json");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getPiperExecutablePath() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<std::string>("piper_executable_path", "/usr/bin/piper");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getPiperModelPath() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return get<std::string>("piper_model_path", "/app/models/model.onnx");
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getVideoConfigPath() {
|
2026-01-07 10:38:17 +08:00
|
|
|
|
return getConfigBasePath() + get<std::string>("video_config_path", "video_config.json");
|
2026-01-07 13:40:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::string ConfigManager::getDbUser() {
|
|
|
|
|
|
return get<std::string>("db_user", "forlinx");
|
|
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getDbPwd() {
|
|
|
|
|
|
return get<std::string>("db_pwd", "forlinx");
|
|
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getDbHost() {
|
|
|
|
|
|
return get<std::string>("db_host", "127.0.0.1");
|
|
|
|
|
|
}
|
|
|
|
|
|
std::string ConfigManager::getDbName() {
|
|
|
|
|
|
return get<std::string>("db_database", "smart-car-dev");
|
2026-01-07 16:50:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TripwireConfig ConfigManager::getTripwireConfig() {
|
|
|
|
|
|
TripwireConfig config;
|
|
|
|
|
|
|
|
|
|
|
|
std::shared_lock<std::shared_mutex> lock(m_mutex);
|
|
|
|
|
|
|
|
|
|
|
|
if (!m_config_json.contains("tripwire") || !m_config_json["tripwire"].is_object()) {
|
|
|
|
|
|
spdlog::warn("Config key 'tripwire' not found or not an object. Using defaults.");
|
|
|
|
|
|
return config;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const auto& j = m_config_json.at("tripwire");
|
|
|
|
|
|
|
|
|
|
|
|
// 注意:JSON 中是 "enable",结构体中是 "enabled"
|
|
|
|
|
|
config.enabled = j.value("enable", false);
|
|
|
|
|
|
config.name = j.value("name", "Unnamed_Line");
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 解析嵌套的 line 坐标
|
|
|
|
|
|
if (j.contains("line") && j["line"].is_object()) {
|
|
|
|
|
|
const auto& line = j.at("line");
|
|
|
|
|
|
|
|
|
|
|
|
// 解析 p1 (起点)
|
|
|
|
|
|
if (line.contains("p1")) {
|
|
|
|
|
|
config.p1_norm.x = line["p1"].value("x", 0.0f);
|
|
|
|
|
|
config.p1_norm.y = line["p1"].value("y", 0.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析 p2 (终点)
|
|
|
|
|
|
if (line.contains("p2")) {
|
|
|
|
|
|
config.p2_norm.x = line["p2"].value("x", 1.0f);
|
|
|
|
|
|
config.p2_norm.y = line["p2"].value("y", 1.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (const json::exception& e) {
|
|
|
|
|
|
spdlog::error("Failed to parse TripwireConfig: {}", e.what());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return config;
|
2025-12-17 13:32:05 +08:00
|
|
|
|
}
|