diff --git a/CMakeLists.txt b/CMakeLists.txt index ab817fd..981ded2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,6 @@ project(EdgeProxy LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# --- 查找并链接 spdlog --- find_package(spdlog REQUIRED) find_package(Boost REQUIRED COMPONENTS system thread) message(STATUS "Found Boost version: ${Boost_VERSION}") @@ -14,8 +13,7 @@ find_package(SQLite3 REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(GST REQUIRED gstreamer-1.0 gstreamer-app-1.0) find_package(OpenCV REQUIRED) -# 在包含 Crow 子目录之前,设置一个 CMake 选项 -# 这会告诉 Crow 的构建脚本去使用 Boost 库中包含的 Asio + add_subdirectory(src/vendor/crow) add_library(nlohmann_json INTERFACE) @@ -26,27 +24,21 @@ target_include_directories(nlohmann_json INTERFACE add_library(edge_proxy_lib STATIC # TCP通讯层 src/network/tcp_server.cc - # --- 设备管理模块 --- src/deviceManager/device_manager.cc - # 小工具 src/utils/mqtt_topic_matcher.cpp - # --- MQTT 核心模块 --- src/mqtt/mqtt_client.cpp src/mqtt/mqtt_router.cpp src/mqtt/handler/data_handler.cpp src/mqtt/handler/command_handler.cpp - # --- 协议层 --- src/protocol/protocol.cc src/protocol/private_protocol_adapter.cc src/protocol/protocol_factory.cc - # 系统监视 src/systemMonitor/system_monitor.cc - #modbus src/modbus/modbus_rtu_client.cc src/modbus/modbus_rtu_bus_service.cc @@ -54,19 +46,17 @@ add_library(edge_proxy_lib STATIC # --- Modbus Master --- src/protocol/modbus/modbus_protocol.cc src/modbus/modbus_master_poller.cc - # 数据缓存/断点续传 src/dataCache/data_cache.cc src/dataCache/cache_uploader.cc - #web src/web/web_server.cc - #SQL src/dataStorage/data_storage.cc - #tts src/tts/piper_tts_interface.cc + #config配置 + src/config/config_manager.cc ) target_include_directories(edge_proxy_lib PUBLIC @@ -88,9 +78,9 @@ target_link_libraries(edge_proxy_lib PUBLIC Crow nlohmann_json ) -# ================================================================= -# Main Application Target (UNCOMMENTED AND ACTIVATED) -# ================================================================= +# ================================== +# Main Application Target +# ================================== add_executable(edge_proxy src/main.cpp ) @@ -115,7 +105,7 @@ target_include_directories(edge_streamer PRIVATE ${GST_INCLUDE_DIRS} ) -# =================#================================================ +# ================================================================= # 测试目标 # ================================================================= # add_executable(test diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..19bc436 --- /dev/null +++ b/config/config.json @@ -0,0 +1,14 @@ +{ + "config_base_path": "/app/config/", + "data_cache_db_path": "edge_data_cache.db", + "data_storage_db_path": "edge_proxy_data.db", + "device_id": "rk3588-proxy-001", + "log_level": "info", + "mqtt_broker": "tcp://localhost:1883", + "mqtt_client_id_prefix": "edge-proxy-", + "tcp_server_ports": [ + 12345, + 502 + ], + "web_server_port": 8080 +} \ No newline at end of file diff --git a/src/config/config_manager.cc b/src/config/config_manager.cc new file mode 100644 index 0000000..f259bea --- /dev/null +++ b/src/config/config_manager.cc @@ -0,0 +1,123 @@ +#include "config_manager.h" +#include +ConfigManager& ConfigManager::getInstance() { + static ConfigManager instance; + return instance; +} + +json ConfigManager::createDefaultConfig() { + 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"} + // **新特性**:您可以在这里添加任何新的默认值 + }; +} + +// 私有辅助函数:保存(必须由已持有 unique_lock 的函数调用) +bool ConfigManager::save_unlocked() { + std::ofstream ofs(m_configFilePath); + if (!ofs.is_open()) { + spdlog::error("Failed to open config file '{}' for writing.", m_configFilePath); + return false; + } + try { + ofs << m_config_json.dump(4); // 格式化输出 + return true; + } catch (const json::exception& e) { + spdlog::error("Failed to serialize config. Error: {}", e.what()); + return false; + } +} + +bool ConfigManager::load(const std::string& configFilePath) { + std::unique_lock lock(m_mutex); // 写操作,使用 unique_lock + m_configFilePath = configFilePath; + + std::ifstream ifs(m_configFilePath); + if (!ifs.is_open()) { + spdlog::warn("Config file '{}' not found. Creating with default values.", m_configFilePath); + m_config_json = createDefaultConfig(); + if(save_unlocked()) { + spdlog::info("Default config file created at '{}'.", m_configFilePath); + return true; + } else { + spdlog::error("Failed to create default config file."); + return false; + } + } + + try { + ifs >> m_config_json; + // **重要**:合并默认值。确保JSON文件中缺失的键被默认值补全。 + json defaults = createDefaultConfig(); + defaults.merge_patch(m_config_json); // 已加载的值会覆盖默认值 + m_config_json = defaults; // + + spdlog::info("Successfully loaded config from '{}'. Device ID: {}", m_configFilePath, m_config_json.value("device_id", "N/A")); + + // 检查是否需要重新保存( + if (save_unlocked()) { + spdlog::debug("Config file updated with new default keys if any."); + } + return true; + } catch (const json::exception& e) { + spdlog::error("Failed to parse config file '{}'. Error: {}. Using default values.", m_configFilePath, e.what()); + m_config_json = createDefaultConfig(); + return false; + } +} + +bool ConfigManager::save() { + std::unique_lock lock(m_mutex); + return save_unlocked(); +} + + + +std::string ConfigManager::getDeviceID() { + return get("device_id", "default-edge-proxy-01"); +} + +std::string ConfigManager::getConfigBasePath() { + return get("config_base_path", "/app/config/"); +} + +std::string ConfigManager::getMqttBroker() { + return get("mqtt_broker", "tcp://localhost:1883"); +} + +std::string ConfigManager::getMqttClientID() { + // 逻辑保持不变 + return get("mqtt_client_id_prefix", "edge-proxy-") + getDeviceID(); +} + +std::string ConfigManager::getDataStorageDbPath() { + return getConfigBasePath() + get("data_storage_db_path", "edge_proxy_data.db"); +} + +std::string ConfigManager::getDataCacheDbPath() { + return getConfigBasePath() + get("data_cache_db_path", "edge_data_cache.db"); +} + +std::string ConfigManager::getDevicesConfigPath() { + return getConfigBasePath() + "devices.json"; +} + +int ConfigManager::getWebServerPort() { + return get("web_server_port", 8080); +} + +std::vector ConfigManager::getTcpServerPorts() { + return get>("tcp_server_ports", {12345}); +} + +std::string ConfigManager::getLogLevel() { + return get("log_level", "debug"); +} \ No newline at end of file diff --git a/src/config/config_manager.h b/src/config/config_manager.h new file mode 100644 index 0000000..edfcfbd --- /dev/null +++ b/src/config/config_manager.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include // 包含了 spdlog +#include // 用于 typeid + +// 使用 nlohmann::json 命名空间 +using json = nlohmann::json; + +// 移除 AppConfig 结构体 + +class ConfigManager { +public: + static ConfigManager& getInstance(); + bool load(const std::string& configFilePath); + bool save(); + + /** + * @brief [核心] 通用、线程安全的 GET 模板方法 + * @tparam T 您期望的类型 (e.g., std::string, int, bool) + * @param key JSON中的键 + * @param default_value 如果键不存在或类型不匹配时返回的默认值 + * @return T 配置值 + */ + template + T get(const std::string& key, const T& default_value) { + std::shared_lock lock(m_mutex); + + if (!m_config_json.contains(key)) { + spdlog::debug("Config key '{}' not found, using default value.", key); + return default_value; + } + + try { + return m_config_json.at(key).get(); + } catch (const json::type_error& e) { + spdlog::warn("Config type mismatch for key '{}'. Expected '{}', found '{}'. Using default. Error: {}", + key, + typeid(T).name(), + m_config_json.at(key).type_name(), + e.what()); + return default_value; + } + } + + /** + * @brief [核心] 通用、线程安全的 SET 模板方法 + * @tparam T 值的类型 + * @param key 要设置的键 + * @param value 要设置的值 + */ + template + void set(const std::string& key, const T& value) { + { + std::unique_lock lock(m_mutex); + // 无论键是否存在,都会被创建或覆盖 + m_config_json[key] = value; + spdlog::info("Config updated: [{}] set.", key); + } // 锁在这里释放 + + save(); // 自动保存到文件 + + // **特殊处理**: 某些配置需要立即生效 + if (key == "log_level") { + spdlog::set_level(spdlog::level::from_str(value)); + } + } + + + std::string getDeviceID(); + std::string getConfigBasePath(); + std::string getMqttBroker(); + std::string getMqttClientID(); + std::string getDataStorageDbPath(); + std::string getDataCacheDbPath(); + std::string getDevicesConfigPath(); + int getWebServerPort(); + std::vector getTcpServerPorts(); + std::string getLogLevel(); + // ... 您可以继续为其他核心配置添加包装器 ... + +private: + ConfigManager() = default; + ~ConfigManager() = default; + ConfigManager(const ConfigManager&) = delete; + ConfigManager& operator=(const ConfigManager&) = delete; + json createDefaultConfig(); + + bool save_unlocked(); + + std::string m_configFilePath; + json m_config_json; + mutable std::shared_mutex m_mutex; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index bbf3812..ea7ec35 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ // main.cpp - #include "network/tcp_server.h" #include "mqtt/mqtt_client.h" #include "mqtt/mqtt_router.h" @@ -11,6 +10,7 @@ #include "web/web_server.h" #include "dataCache/live_data_cache.h" #include "dataStorage/data_storage.h" +#include "config/config_manager.h" #include #include @@ -45,8 +45,15 @@ void poll_system_metrics( int main(int argc, char* argv[]) { + const std::string config_path = "/app/config/config.json"; + if (!ConfigManager::getInstance().load(config_path)) { + std::cerr << "Failed to load configuration from " << config_path + << ". Running with defaults, but this may cause issues." << std::endl; + + } + try { - spdlog::set_level(spdlog::level::debug); + spdlog::set_level(spdlog::level::from_str(ConfigManager::getInstance().getLogLevel())); spdlog::info("Edge Proxy starting up..."); } catch (const spdlog::spdlog_ex& ex) { std::cerr << "Log initialization failed: " << ex.what() << std::endl; @@ -54,7 +61,7 @@ int main(int argc, char* argv[]) { } spdlog::info("Initializing Data Storage..."); - if (!DataStorage::getInstance().initialize("edge_proxy_data.db")) { + if (!DataStorage::getInstance().initialize(ConfigManager::getInstance().getDataStorageDbPath())) { spdlog::critical("Failed to initialize DataStorage. Exiting."); return 1; } @@ -62,9 +69,10 @@ int main(int argc, char* argv[]) { try { DataCache data_cache; LiveDataCache live_data_cache; - MqttClient mqtt_client("tcp://localhost:1883", "edge-proxy-main-client"); + MqttClient mqtt_client(ConfigManager::getInstance().getMqttBroker(), + ConfigManager::getInstance().getMqttClientID() + ); auto report_to_mqtt = [&](const UnifiedData& data) { - // 使用 DataStorage 单例存储处理后的 UnifiedData 对象 if (DataStorage::getInstance().storeProcessedData(data)) { spdlog::debug("Successfully stored PROCESSED data for device '{}'", data.device_id); } else { @@ -86,8 +94,8 @@ int main(int argc, char* argv[]) { }; DeviceManager device_manager(g_io_context, report_to_mqtt); - MqttRouter mqtt_router(mqtt_client, device_manager, "../config/devices.json"); - std::vector listen_ports = { 12345 }; + MqttRouter mqtt_router(mqtt_client, device_manager, ConfigManager::getInstance().getDevicesConfigPath()); + std::vector listen_ports = ConfigManager::getInstance().getTcpServerPorts(); TCPServer tcp_server(g_io_context, listen_ports, mqtt_client); SystemMonitor::SystemMonitor monitor; @@ -111,9 +119,9 @@ int main(int argc, char* argv[]) { system_monitor_timer.async_wait(std::bind(poll_system_metrics, std::ref(system_monitor_timer), std::ref(monitor), std::ref(mqtt_client))); - device_manager.load_and_start("../config/devices.json"); + device_manager.load_and_start(ConfigManager::getInstance().getDevicesConfigPath()); - WebServer web_server(monitor, device_manager, live_data_cache, 8080); + WebServer web_server(monitor, device_manager, live_data_cache, ConfigManager::getInstance().getWebServerPort()); web_server.start(); boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM);