diff --git a/config/config.json b/config/config.json index 9624a85..d894e25 100644 --- a/config/config.json +++ b/config/config.json @@ -3,7 +3,7 @@ "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", + "device_id": "rk3588-proxy-002", "log_level": "info", "mqtt_broker": "tcp://localhost:1883", "mqtt_client_id_prefix": "edge-proxy-", @@ -13,7 +13,7 @@ 12345 ], "video_service": { - "enabled": true + "enabled": false }, "video_streams": [ { diff --git a/config/devices.json b/config/devices.json index ebeb624..99da3d9 100644 --- a/config/devices.json +++ b/config/devices.json @@ -1,59 +1,98 @@ - { - "modbus_rtu_devices": [ - { - "enabled": false, - "device_id": "rtu_temp_sensor_lab", - "port_path": "/dev/ttyS7", - "baud_rate": 9600, - "slave_id": 1, - "poll_interval_ms": 5000, - "data_points": [ - {"name": "temperature", "address": 0, "type": "INT16", "scale": 0.1}, - {"name": "humidity", "address": 1, "type": "UINT16", "scale": 0.1} - ] - }, - { - "enabled": false, - "device_id": "rotary encoder", - "port_path": "/dev/ttyS7", - "baud_rate": 9600, - "slave_id": 111, - "poll_interval_ms": 5000, - "data_points": [ - {"name": "count", "address": 1, "type": "INT16", "scale": 1.0}, - {"name": "total_count", "address": 2, "type": "INT16", "scale": 1.0} - ] - }, - { - "enabled": false, - "device_id": "backup_counter", - "port_path": "/dev/ttyS7", - "baud_rate": 9600, - "slave_id": 10, - "poll_interval_ms": 1000, - "data_points": [ - {"name": "count", "address": 32, "type": "UINT32"} - ] - } - ], - "modbus_tcp_devices": [ - { - "enabled": false, - "device_id": "plc_workshop1", - "ip_address": "192.168.1.120", - "port": 502, - "slave_id": 1, - "poll_interval_ms": 1000, - "data_points": [ - {"name": "motor_speed", "address": 100, "type": "UINT16", "scale": 1.0}, - {"name": "pressure", "address": 102, "type": "FLOAT32", "scale": 0.01}, - {"name": "valve_status","address": 104, "type": "UINT16", "scale": 1.0} - ] - } - ], - "modbus_rtu_bus_configs": { - "/dev/ttyS7": { - "inter_device_delay_ms": 150 - } +{ + "modbus_rtu_devices": [ + { + "enabled": true, + "device_id": "rtu_temp_sensor_lab", + "port_path": "/dev/ttyS7", + "baud_rate": 9600, + "slave_id": 1, + "poll_interval_ms": 5000, + "data_points": [ + { + "name": "temperature", + "address": 0, + "type": "INT16", + "scale": 0.1 + }, + { + "name": "humidity", + "address": 1, + "type": "UINT16", + "scale": 0.1 + } + ] + }, + { + "enabled": true, + "device_id": "rotary encoder", + "port_path": "/dev/ttyS7", + "baud_rate": 9600, + "slave_id": 111, + "poll_interval_ms": 5000, + "data_points": [ + { + "name": "count", + "address": 1, + "type": "INT16", + "scale": 1.0 + }, + { + "name": "total_count", + "address": 2, + "type": "INT16", + "scale": 1.0 + } + ] + }, + { + "enabled": false, + "device_id": "backup_counter", + "port_path": "/dev/ttyS7", + "baud_rate": 9600, + "slave_id": 10, + "poll_interval_ms": 1000, + "data_points": [ + { + "name": "count", + "address": 32, + "type": "UINT32" + } + ] } - } \ No newline at end of file + ], + "modbus_tcp_devices": [ + { + "enabled": false, + "device_id": "plc_workshop1", + "ip_address": "192.168.1.120", + "port": 502, + "slave_id": 1, + "poll_interval_ms": 1000, + "data_points": [ + { + "name": "motor_speed", + "address": 100, + "type": "UINT16", + "scale": 1.0 + }, + { + "name": "pressure", + "address": 102, + "type": "FLOAT32", + "scale": 0.01 + }, + { + "name": "valve_status", + "address": 104, + "type": "UINT16", + "scale": 1.0 + } + ] + } + ], + "modbus_rtu_bus_configs": { + "/dev/ttyS7": { + "inter_device_delay_ms": 150 + } + } +} \ No newline at end of file diff --git a/config/edge_proxy_data.db b/config/edge_proxy_data.db index 371ac64..95299f6 100644 Binary files a/config/edge_proxy_data.db and b/config/edge_proxy_data.db differ diff --git a/src/config/config_manager.cc b/src/config/config_manager.cc index 36e9372..1fa2c62 100644 --- a/src/config/config_manager.cc +++ b/src/config/config_manager.cc @@ -2,14 +2,16 @@ #include "config_manager.h" #include -ConfigManager& ConfigManager::getInstance() { +ConfigManager &ConfigManager::getInstance() +{ static ConfigManager instance; return instance; } -json ConfigManager::createDefaultConfig() { +json ConfigManager::createDefaultConfig() +{ // --- 修改: 添加新的 video_service 和 video_streams 默认值 --- - return json { + return json{ {"device_id", "default-edge-proxy-01"}, {"config_base_path", "/app/config/"}, {"mqtt_broker", "tcp://localhost:1883"}, @@ -19,82 +21,76 @@ json ConfigManager::createDefaultConfig() { {"tcp_server_ports", {12345}}, {"web_server_port", 8080}, {"log_level", "debug"}, - {"alarm_rules_path", "alarms.json"}, - {"piper_executable_path", "/usr/bin/piper"}, + {"alarm_rules_path", "alarms.json"}, + {"piper_executable_path", "/usr/bin/piper"}, {"piper_model_path", "/app/models/model.onnx"}, // --- 新增: 视频服务配置 --- { - "video_service", { - {"enabled", false} - } - }, - { - "video_streams", { - { - {"id", "cam_01_example"}, - {"enabled", false}, - {"input_url", "rtsp://your_camera_stream"}, - {"output_rtsp", "rtsp://localhost:8554/cam_01_out"}, - {"module_type", "intrusion_detection"}, - {"module_config", { - {"model_path", "/app/models/yolov5s.rknn"}, - {"rknn_thread_num", 3}, - {"intrusion_zone", {0, 0, 1920, 1080}}, - {"time_threshold_sec", 5.0} - }} - } - } - } - }; + "video_service", {{"enabled", false}}}, + {"video_streams", {{{"id", "cam_01_example"}, {"enabled", false}, {"input_url", "rtsp://your_camera_stream"}, {"output_rtsp", "rtsp://localhost:8554/cam_01_out"}, {"module_type", "intrusion_detection"}, {"module_config", {{"model_path", "/app/models/yolov5s.rknn"}, {"rknn_thread_num", 3}, {"intrusion_zone", {0, 0, 1920, 1080}}, {"time_threshold_sec", 5.0}}}}}}}; } -bool ConfigManager::save_unlocked() { +bool ConfigManager::save_unlocked() +{ std::ofstream ofs(m_configFilePath); - if (!ofs.is_open()) { + if (!ofs.is_open()) + { spdlog::error("Failed to open config file '{}' for writing.", m_configFilePath); return false; } - try { + try + { ofs << m_config_json.dump(4); return true; - } catch (const json::exception& e) { + } + 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); +bool ConfigManager::load(const std::string &configFilePath) +{ + std::unique_lock lock(m_mutex); m_configFilePath = configFilePath; std::ifstream ifs(m_configFilePath); - if (!ifs.is_open()) { + if (!ifs.is_open()) + { spdlog::warn("Config file '{}' not found. Creating with default values.", m_configFilePath); m_config_json = createDefaultConfig(); - if(save_unlocked()) { + if (save_unlocked()) + { spdlog::info("Default config file created at '{}'.", m_configFilePath); return true; - } else { + } + else + { spdlog::error("Failed to create default config file."); return false; } } - try { + try + { ifs >> m_config_json; // **重要**:合并默认值。 json defaults = createDefaultConfig(); defaults.merge_patch(m_config_json); // <-- 关键: m_config_json 会覆盖 defaults - m_config_json = defaults; // 将合并后的结果存回 - + 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."); + + if (save_unlocked()) + { + spdlog::debug("Config file updated with new default keys if any."); } return true; - } catch (const json::exception& e) { + } + 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; @@ -102,105 +98,120 @@ bool ConfigManager::load(const std::string& configFilePath) { } // ... (save, getDeviceID, ... getPiperModelPath 保持不变) ... -bool ConfigManager::save() { +bool ConfigManager::save() +{ std::unique_lock lock(m_mutex); return save_unlocked(); } -std::string ConfigManager::getDeviceID() { +std::string ConfigManager::getDeviceID() +{ return get("device_id", "default-edge-proxy-01"); } -std::string ConfigManager::getConfigBasePath() { +std::string ConfigManager::getConfigBasePath() +{ return get("config_base_path", "/app/config/"); } -std::string ConfigManager::getMqttBroker() { +std::string ConfigManager::getMqttBroker() +{ return get("mqtt_broker", "tcp://localhost:1883"); } -std::string ConfigManager::getMqttClientID() { +std::string ConfigManager::getMqttClientID() +{ return get("mqtt_client_id_prefix", "edge-proxy-") + getDeviceID(); } -std::string ConfigManager::getDataStorageDbPath() { +std::string ConfigManager::getDataStorageDbPath() +{ return getConfigBasePath() + get("data_storage_db_path", "edge_proxy_data.db"); } -std::string ConfigManager::getDataCacheDbPath() { +std::string ConfigManager::getDataCacheDbPath() +{ return getConfigBasePath() + get("data_cache_db_path", "edge_data_cache.db"); } -std::string ConfigManager::getDevicesConfigPath() { - return getConfigBasePath() + "devices.json"; +std::string ConfigManager::getDevicesConfigPath() +{ + return getConfigBasePath() + "devices.json"; } -int ConfigManager::getWebServerPort() { +int ConfigManager::getWebServerPort() +{ return get("web_server_port", 8080); } -std::vector ConfigManager::getTcpServerPorts() { +std::vector ConfigManager::getTcpServerPorts() +{ return get>("tcp_server_ports", {12345}); } -std::string ConfigManager::getLogLevel() { +std::string ConfigManager::getLogLevel() +{ return get("log_level", "debug"); } -std::string ConfigManager::getAlarmRulesPath() { +std::string ConfigManager::getAlarmRulesPath() +{ return getConfigBasePath() + get("alarm_rules_path", "alarms.json"); } -std::string ConfigManager::getPiperExecutablePath() { +std::string ConfigManager::getPiperExecutablePath() +{ return get("piper_executable_path", "/usr/bin/piper"); } -std::string ConfigManager::getPiperModelPath() { +std::string ConfigManager::getPiperModelPath() +{ return get("piper_model_path", "/app/models/model.onnx"); } -// --- (getIsVideoServiceEnabled 保持不变) --- -bool ConfigManager::getIsVideoServiceEnabled() const { - std::shared_lock lock(m_mutex); - try { - if (m_config_json.contains("video_service")) { - return m_config_json["video_service"].value("enabled", false); +bool ConfigManager::getIsVideoServiceEnabled() const +{ + std::shared_lock lock(m_mutex); + try + { + if (m_config_json.contains("video_service")) + { + return m_config_json["video_service"].value("enabled", false); } - } catch (const json::type_error& e) { + } + catch (const json::type_error &e) + { spdlog::warn("Config type mismatch for key 'video_service.enabled'. Error: {}", e.what()); } - return false; + return false; } -// --- 移除: getVideoModelPath --- -// std::string ConfigManager::getVideoModelPath() const { ... } +std::vector ConfigManager::getVideoStreamConfigs() const +{ + std::vector configs; + std::shared_lock lock(m_mutex); -// --- 修改: getVideoStreamConfigs --- -std::vector ConfigManager::getVideoStreamConfigs() const { - std::vector configs; - - std::shared_lock lock(m_mutex); - - try { - // --- 修改: 路径变为顶层的 "video_streams" --- + try + { if (m_config_json.contains("video_streams") && - m_config_json["video_streams"].is_array()) + m_config_json["video_streams"].is_array()) { - for (const auto& stream_json : m_config_json["video_streams"]) { - VideoStreamConfig cfg; - cfg.id = stream_json.value("id", ""); + for (const auto &stream_json : m_config_json["video_streams"]) + { + VideoStreamConfig cfg; + cfg.id = stream_json.value("id", ""); cfg.enabled = stream_json.value("enabled", false); cfg.input_url = stream_json.value("input_url", ""); cfg.output_rtsp = stream_json.value("output_rtsp", ""); - - // --- 移除 --- - // cfg.rknn_thread_num = stream_json.value("rknn_thread_num", 1); - - // --- 新增 --- - cfg.module_type = stream_json.value("module_type", ""); - cfg.module_config = stream_json.value("module_config", json::object()); // 传递整个json对象 - if (cfg.module_type.empty()) { + cfg.module_type = stream_json.value("module_type", ""); + cfg.module_config = stream_json.value("module_config", json::object()); + + if (cfg.module_type.empty()) + { spdlog::warn("Video stream '{}' has no 'module_type' defined. It may fail to start.", cfg.id); } - + configs.push_back(cfg); } - } else { + } + else + { spdlog::warn("Config key 'video_streams' not found or is not an array."); } - } catch (const json::exception& e) { - // 捕获所有可能的 JSON 解析异常 + } + catch (const json::exception &e) + { spdlog::error("Error parsing 'video_streams': {}", e.what()); } - + return configs; } \ No newline at end of file diff --git a/src/config/config_manager.h b/src/config/config_manager.h index a2107a3..83fc875 100644 --- a/src/config/config_manager.h +++ b/src/config/config_manager.h @@ -6,67 +6,71 @@ #include #include #include -#include +#include using json = nlohmann::json; -class ConfigManager { +class ConfigManager +{ public: - // - // --- 核心修改 --- - // - struct VideoStreamConfig { + struct VideoStreamConfig + { std::string id; bool enabled; std::string input_url; std::string output_rtsp; - // int rknn_thread_num; // <-- 移除 (已移动到 module_config) - - std::string module_type; // <-- 新增: "intrusion_detection" 或 "face_recognition" - json module_config; // <-- 新增: 传递模块的特定配置 (最灵活的方式) + + std::string module_type; + json module_config; }; - static ConfigManager& getInstance(); - bool load(const std::string& configFilePath); + static ConfigManager &getInstance(); + bool load(const std::string &configFilePath); bool save(); - template - T get(const std::string& key, const T& default_value) { + template + T get(const std::string &key, const T &default_value) + { std::shared_lock lock(m_mutex); - - if (!m_config_json.contains(key)) { + + if (!m_config_json.contains(key)) + { spdlog::debug("Config key '{}' not found, using default value.", key); return default_value; } - try { + 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(), + } + 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; } } - template - void set(const std::string& key, const T& 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") { + + save(); + + if (key == "log_level") + { spdlog::set_level(spdlog::level::from_str(value)); } } - std::string getDeviceID(); std::string getConfigBasePath(); std::string getMqttBroker(); @@ -80,20 +84,20 @@ public: std::string getAlarmRulesPath(); std::string getPiperExecutablePath(); std::string getPiperModelPath(); - + bool getIsVideoServiceEnabled() const; - std::vector getVideoStreamConfigs() const; // (签名不变, 实现改变) + std::vector getVideoStreamConfigs() const; private: ConfigManager() = default; ~ConfigManager() = default; - ConfigManager(const ConfigManager&) = delete; - ConfigManager& operator=(const ConfigManager&) = delete; + 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; + mutable std::shared_mutex m_mutex; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index aa8a862..0ebb105 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,224 +1,233 @@ // main.cpp -#include "network/tcp_server.h" -#include "mqtt/mqtt_client.h" -#include "mqtt/mqtt_router.h" -#include "systemMonitor/system_monitor.h" -#include "spdlog/spdlog.h" -#include "deviceManager/device_manager.h" -#include "dataCache/data_cache.h" -#include "dataCache/cache_uploader.h" -#include "web/web_server.h" -#include "dataCache/live_data_cache.h" -#include "dataStorage/data_storage.h" -#include "config/config_manager.h" -#include "videoServiceManager/video_service_manager.h" -#include "alarm/alarm_service.h" -#include "tts/piper_tts_interface.h" - #include #include #include -#include #include +#include +#include + +#include "alarm/alarm_service.h" +#include "config/config_manager.h" +#include "dataCache/cache_uploader.h" +#include "dataCache/data_cache.h" +#include "dataCache/live_data_cache.h" +#include "dataStorage/data_storage.h" +#include "deviceManager/device_manager.h" +#include "mqtt/mqtt_client.h" +#include "mqtt/mqtt_router.h" +#include "network/tcp_server.h" +#include "nlohmann/json.hpp" +#include "spdlog/spdlog.h" +#include "systemMonitor/system_monitor.h" +#include "tts/piper_tts_interface.h" +#include "videoServiceManager/video_service_manager.h" +#include "web/web_server.h" boost::asio::io_context g_io_context; /** * @brief 周期性轮询系统状态并发布到 MQTT */ -void poll_system_metrics( - boost::asio::steady_timer& timer, - SystemMonitor::SystemMonitor& monitor, - MqttClient& mqtt_client, - AlarmService& alarm_service -) { - if (g_io_context.stopped()) return; - auto cpu_util = monitor.getCpuUtilization(); - auto mem_info = monitor.getMemoryInfo(); - double mem_total_gb = mem_info.total_kb / 1024.0 / 1024.0; - - double mem_usage_percentage = (mem_info.total_kb > 0) - ? (100.0 * (mem_info.total_kb - mem_info.available_kb) / mem_info.total_kb) - : 0.0; +void poll_system_metrics(boost::asio::steady_timer& timer, + SystemMonitor::SystemMonitor& monitor, + MqttClient& mqtt_client, AlarmService& alarm_service) { + if (g_io_context.stopped()) return; + auto cpu_util = monitor.getCpuUtilization(); + auto mem_info = monitor.getMemoryInfo(); + double mem_total_gb = mem_info.total_kb / 1024.0 / 1024.0; - std::string topic = "proxy/system_status"; - std::string payload = "{\"cpu_usage\":" + std::to_string(cpu_util.totalUsagePercentage) + - ",\"mem_total_gb\":" + std::to_string(mem_total_gb) + - ",\"mem_usage_percentage\":" + std::to_string(mem_usage_percentage) + "}"; - - alarm_service.process_system_data(payload); + double mem_usage_percentage = + (mem_info.total_kb > 0) + ? (100.0 * (mem_info.total_kb - mem_info.available_kb) / + mem_info.total_kb) + : 0.0; - mqtt_client.publish(topic, payload); - spdlog::debug("System metrics published."); + auto thermalInfoString = monitor.getChipTemperature(); - timer.expires_at(timer.expiry() + std::chrono::seconds(15)); - timer.async_wait(std::bind(poll_system_metrics, - std::ref(timer), - std::ref(monitor), - std::ref(mqtt_client), - std::ref(alarm_service) - )); + std::string topic = "proxy/system_status"; + std::string payload; + + try { + nlohmann::json payload_json; + payload_json["cpu_usage"] = cpu_util.totalUsagePercentage; + payload_json["mem_total_gb"] = mem_total_gb; + payload_json["mem_usage_percentage"] = mem_usage_percentage; + payload_json["thermal_info"] = nlohmann::json::parse(thermalInfoString); + + payload = payload_json.dump(); + + } catch (const nlohmann::json::parse_error& e) { + spdlog::error("Failed to parse thermalInfo JSON: {}. Sending partial data.", + e.what()); + nlohmann::json fallback_json; + fallback_json["cpu_usage"] = cpu_util.totalUsagePercentage; + fallback_json["mem_total_gb"] = mem_total_gb; + fallback_json["mem_usage_percentage"] = mem_usage_percentage; + fallback_json["thermal_info_error"] = "parsing_failed"; + fallback_json["raw_thermal_info"] = thermalInfoString; + + payload = fallback_json.dump(); + } + + alarm_service.process_system_data(payload); + + mqtt_client.publish(topic, payload); + spdlog::debug("System metrics published."); + + timer.expires_at(timer.expiry() + std::chrono::seconds(15)); + timer.async_wait(std::bind(poll_system_metrics, std::ref(timer), + std::ref(monitor), std::ref(mqtt_client), + std::ref(alarm_service))); } 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; + } - 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; + auto& config = ConfigManager::getInstance(); + + try { + spdlog::set_level(spdlog::level::from_str(config.getLogLevel())); + spdlog::info("Edge Proxy starting up..."); + } catch (const spdlog::spdlog_ex& ex) { + std::cerr << "Log initialization failed: " << ex.what() << std::endl; + return 1; + } + + spdlog::info("Initializing Data Storage..."); + auto& data_storage = DataStorage::getInstance(); + if (!data_storage.initialize(config.getDataStorageDbPath())) { + spdlog::critical("Failed to initialize DataStorage. Exiting."); + return 1; + } + + try { + spdlog::info("Initializing Video Service..."); + VideoServiceManager video_manager; + + DataCache data_cache; + LiveDataCache live_data_cache; + MqttClient mqtt_client(config.getMqttBroker(), config.getMqttClientID()); + + PiperTTSInterface tts_service(config.getPiperExecutablePath(), + config.getPiperModelPath()); + + AlarmService alarm_service(g_io_context, tts_service, mqtt_client, + data_storage); + + if (!alarm_service.load_rules(config.getAlarmRulesPath())) { + spdlog::error("Failed to load alarm rules. Alarms may be disabled."); } - auto& config = ConfigManager::getInstance(); + auto report_to_mqtt = [&](const UnifiedData& data) { + if (data_storage.storeProcessedData(data)) { + spdlog::debug("Successfully stored PROCESSED data for device '{}'", + data.device_id); + } else { + spdlog::error("Failed to store PROCESSED data for device '{}'", + data.device_id); + } - try { - spdlog::set_level(spdlog::level::from_str(config.getLogLevel())); - spdlog::info("Edge Proxy starting up..."); - } catch (const spdlog::spdlog_ex& ex) { - std::cerr << "Log initialization failed: " << ex.what() << std::endl; - return 1; - } + live_data_cache.update_data(data.device_id, data.data_json); - spdlog::info("Initializing Data Storage..."); - auto& data_storage = DataStorage::getInstance(); - if (!data_storage.initialize(config.getDataStorageDbPath())) { - spdlog::critical("Failed to initialize DataStorage. Exiting."); - return 1; - } - - try { - spdlog::info("Initializing Video Service..."); - VideoServiceManager video_manager; + alarm_service.process_device_data(data.device_id, data.data_json); - DataCache data_cache; - LiveDataCache live_data_cache; - MqttClient mqtt_client(config.getMqttBroker(), - config.getMqttClientID() - ); - - PiperTTSInterface tts_service( - config.getPiperExecutablePath(), - config.getPiperModelPath() - ); - - AlarmService alarm_service(g_io_context, - tts_service, - mqtt_client, - data_storage); - - if (!alarm_service.load_rules(config.getAlarmRulesPath())) { - spdlog::error("Failed to load alarm rules. Alarms may be disabled."); - - } - - auto report_to_mqtt = [&](const UnifiedData& data) { - if (data_storage.storeProcessedData(data)) { - spdlog::debug("Successfully stored PROCESSED data for device '{}'", data.device_id); - } else { - spdlog::error("Failed to store PROCESSED data for device '{}'", data.device_id); - } - - live_data_cache.update_data(data.device_id, data.data_json); - - // [新] 1. 在发布前处理告警 - alarm_service.process_device_data(data.device_id, data.data_json); - - // 2. 正常上报或缓存 - if (mqtt_client.is_connected()) { - std::string topic = "devices/" + data.device_id + "/data"; - g_io_context.post([&, topic, payload = data.data_json]() { - mqtt_client.publish(topic, payload, 1, false); - }); - } else { - spdlog::warn("MQTT disconnected. Caching data for device '{}'.", data.device_id); - data_cache.add(data); - } - }; - - DeviceManager device_manager(g_io_context, report_to_mqtt); - MqttRouter mqtt_router(mqtt_client, device_manager); - - std::vector listen_ports = config.getTcpServerPorts(); - TCPServer tcp_server(g_io_context, listen_ports, mqtt_client); - SystemMonitor::SystemMonitor monitor; - - if (!data_cache.open(config.getDataCacheDbPath())) { - spdlog::critical("Failed to initialize data cache at '{}'. Exiting.", config.getDataCacheDbPath()); - return 1; - } - CacheUploader cache_uploader(g_io_context, mqtt_client, data_cache); - - mqtt_client.set_connected_handler([&](const std::string& cause){ - spdlog::info("MQTT client connected: {}", cause); - cache_uploader.start_upload(); + if (mqtt_client.is_connected()) { + std::string topic = "devices/" + data.device_id + "/data"; + g_io_context.post([&, topic, payload = data.data_json]() { + mqtt_client.publish(topic, payload, 1, false); }); - - mqtt_client.connect(); - mqtt_router.start(); + } else { + spdlog::warn("MQTT disconnected. Caching data for device '{}'.", + data.device_id); + data_cache.add(data); + } + }; + DeviceManager device_manager(g_io_context, report_to_mqtt); + MqttRouter mqtt_router(mqtt_client, device_manager); - monitor.getCpuUtilization(); - boost::asio::steady_timer system_monitor_timer(g_io_context, std::chrono::seconds(15)); - - system_monitor_timer.async_wait(std::bind(poll_system_metrics, - std::ref(system_monitor_timer), - std::ref(monitor), - std::ref(mqtt_client), - std::ref(alarm_service) - )); + std::vector listen_ports = config.getTcpServerPorts(); + TCPServer tcp_server(g_io_context, listen_ports, mqtt_client); + SystemMonitor::SystemMonitor monitor; + if (!data_cache.open(config.getDataCacheDbPath())) { + spdlog::critical("Failed to initialize data cache at '{}'. Exiting.", + config.getDataCacheDbPath()); + return 1; + } + CacheUploader cache_uploader(g_io_context, mqtt_client, data_cache); - device_manager.load_and_start(config.getDevicesConfigPath()); + mqtt_client.set_connected_handler([&](const std::string& cause) { + spdlog::info("MQTT client connected: {}", cause); + cache_uploader.start_upload(); + }); - WebServer web_server(monitor, - device_manager, - live_data_cache, - alarm_service, - config.getWebServerPort()); - web_server.start(); - if (config.getIsVideoServiceEnabled()) { - video_manager.load_and_start(config); - } else { - spdlog::warn("VideoService is disabled in configuration."); - } + mqtt_client.connect(); + mqtt_router.start(); - boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM); - signals.async_wait([&](const boost::system::error_code& error, int signal_number) { - spdlog::warn("Interrupt signal ({}) received. Shutting down.", signal_number); - - // a. 停止所有数据采集 - spdlog::info("[Shutdown] A. Stopping device manager services..."); - device_manager.stop_all(); + monitor.getCpuUtilization(); + boost::asio::steady_timer system_monitor_timer(g_io_context, + std::chrono::seconds(15)); - // b. 停止Web服务器 - spdlog::info("[Shutdown] B. Stopping web server..."); - web_server.stop(); - - // c. [新] 停止告警服务 (释放TTS线程) - spdlog::info("[Shutdown] C. Stopping alarm service..."); - alarm_service.stop(); + system_monitor_timer.async_wait(std::bind( + poll_system_metrics, std::ref(system_monitor_timer), std::ref(monitor), + std::ref(mqtt_client), std::ref(alarm_service))); - // d. 断开MQTT - spdlog::info("[Shutdown] D. Disconnecting from MQTT broker..."); - mqtt_client.disconnect(); + device_manager.load_and_start(config.getDevicesConfigPath()); - spdlog::info("[Shutdown] E. Stopping video Service loop..."); - video_manager.stop_all(); - - // e. 最后,安全地停止io_context - spdlog::info("[Shutdown] F. Stopping main event loop..."); + WebServer web_server(monitor, device_manager, live_data_cache, + alarm_service, config.getWebServerPort()); + web_server.start(); + if (config.getIsVideoServiceEnabled()) { + video_manager.load_and_start(config); + } else { + spdlog::warn("VideoService is disabled in configuration."); + } - g_io_context.stop(); + boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM); + signals.async_wait( + [&](const boost::system::error_code& error, int signal_number) { + spdlog::warn("Interrupt signal ({}) received. Shutting down.", + signal_number); + + // a. 停止所有数据采集 + spdlog::info("[Shutdown] A. Stopping device manager services..."); + device_manager.stop_all(); + + // b. 停止Web服务器 + spdlog::info("[Shutdown] B. Stopping web server..."); + web_server.stop(); + + // c. [新] 停止告警服务 (释放TTS线程) + spdlog::info("[Shutdown] C. Stopping alarm service..."); + alarm_service.stop(); + + // d. 断开MQTT + spdlog::info("[Shutdown] D. Disconnecting from MQTT broker..."); + mqtt_client.disconnect(); + + spdlog::info("[Shutdown] E. Stopping video Service loop..."); + video_manager.stop_all(); + + // e. 最后,安全地停止io_context + spdlog::info("[Shutdown] F. Stopping main event loop..."); + + g_io_context.stop(); }); - spdlog::info("All services are running. Press Ctrl+C to exit."); - g_io_context.run(); + spdlog::info("All services are running. Press Ctrl+C to exit."); + g_io_context.run(); + } catch (const std::exception& e) { + spdlog::critical("An unhandled exception occurred: {}", e.what()); + return 1; + } - } catch (const std::exception& e) { - spdlog::critical("An unhandled exception occurred: {}", e.what()); - return 1; - } - - spdlog::info("Server has been shut down gracefully. Exiting."); - return 0; + spdlog::info("Server has been shut down gracefully. Exiting."); + return 0; } \ No newline at end of file diff --git a/src/systemMonitor/system_monitor.cc b/src/systemMonitor/system_monitor.cc index 34986d7..bc2d81e 100644 --- a/src/systemMonitor/system_monitor.cc +++ b/src/systemMonitor/system_monitor.cc @@ -5,6 +5,7 @@ #include #include #include +using json = nlohmann::json; namespace SystemMonitor { @@ -71,27 +72,35 @@ MemoryInfo SystemMonitor::getMemoryInfo() const { return info; } -std::vector SystemMonitor::getChipTemperature() const { - std::vector temps; +std::string SystemMonitor::getChipTemperature() const { + // std::vector temps; const std::vector> zones = { {"CPU Core", "/sys/class/thermal/thermal_zone0/temp"}, {"GPU Core", "/sys/class/thermal/thermal_zone1/temp"}, {"NPU Core", "/sys/class/thermal/thermal_zone2/temp"}, {"Other", "/sys/class/thermal/thermal_zone3/temp"} }; + json jsonArray = json::array(); for (const auto& zone : zones) { std::string temp_str = readFile(zone.second); if (!temp_str.empty()) { try { - ThermalInfo info; - info.zoneName = zone.first; - info.temperatureCelsius = std::stod(temp_str) / 1000.0; - temps.push_back(info); + + json tempObject; + tempObject["zoneName"] = zone.first; + tempObject["temperatureCelsius"] = std::stod(temp_str) / 1000.0; + + jsonArray.push_back(tempObject); + + // ThermalInfo info; + // info.zoneName = zone.first; + // info.temperatureCelsius = std::stod(temp_str) / 1000.0; + // temps.push_back(info); } catch (...) { /* 忽略转换失败 */ } } } - return temps; + return jsonArray.dump(2); } CpuUtilization SystemMonitor::getCpuUtilization(int interval_ms) const { diff --git a/src/systemMonitor/system_monitor.h b/src/systemMonitor/system_monitor.h index f6e6363..b771d32 100644 --- a/src/systemMonitor/system_monitor.h +++ b/src/systemMonitor/system_monitor.h @@ -4,6 +4,7 @@ #include #include #include +#include // 包含 nlohmann/json 库 namespace SystemMonitor { struct SystemInfo { @@ -47,7 +48,7 @@ public: SystemInfo getSystemInfo() const; std::vector getStorageInfo() const; MemoryInfo getMemoryInfo() const; - std::vector getChipTemperature() const; + std::string getChipTemperature() const; CpuUtilization getCpuUtilization(int interval_ms = 1000) const; std::string getKernelLogs(int last_n_lines = 20) const; diff --git a/src/web/web_server.cc b/src/web/web_server.cc index fbc1894..8f5bdf7 100644 --- a/src/web/web_server.cc +++ b/src/web/web_server.cc @@ -1,153 +1,157 @@ // 文件名: src/web/web_server.cc #include "web_server.h" + +#include +#include + +#include "config/config_manager.h" #include "spdlog/spdlog.h" -#include -#include -WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager, LiveDataCache& liveDataCache, AlarmService& alarm_service, uint16_t port) - : crow::Crow(), - m_monitor(monitor), - m_device_manager(deviceManager), - m_live_data_cache(liveDataCache), - m_alarm_service(alarm_service), - m_port(port) -{ - auto& cors = this->get_middleware(); - cors - .global() - .origin("*") - .headers("Content-Type", "Authorization") - .methods("GET"_method, "POST"_method, "OPTIONS"_method); +WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, + DeviceManager& deviceManager, LiveDataCache& liveDataCache, + AlarmService& alarm_service, uint16_t port) + : crow::Crow(), + m_monitor(monitor), + m_device_manager(deviceManager), + m_live_data_cache(liveDataCache), + m_alarm_service(alarm_service), + m_port(port) { + auto& cors = this->get_middleware(); + cors.global() + .origin("*") + .headers("Content-Type", "Authorization") + .methods("GET"_method, "POST"_method, "OPTIONS"_method); - this->loglevel(crow::LogLevel::Warning); - setup_routes(); + this->loglevel(crow::LogLevel::Warning); + setup_routes(); } -WebServer::~WebServer() { - stop(); -} +WebServer::~WebServer() { stop(); } void WebServer::start() { - if (m_thread.joinable()) { - spdlog::warn("Web server is already running."); - return; - } - m_thread = std::thread([this]() { - spdlog::info("Starting Web server on port {}", m_port); - this->bindaddr("0.0.0.0").port(m_port).run(); - spdlog::info("Web server has stopped."); - }); + if (m_thread.joinable()) { + spdlog::warn("Web server is already running."); + return; + } + m_thread = std::thread([this]() { + spdlog::info("Starting Web server on port {}", m_port); + this->bindaddr("0.0.0.0").port(m_port).run(); + spdlog::info("Web server has stopped."); + }); } void WebServer::stop() { - crow::Crow::stop(); - if (m_thread.joinable()) { - m_thread.join(); - } + crow::Crow::stop(); + if (m_thread.joinable()) { + m_thread.join(); + } } void WebServer::setup_routes() { - CROW_ROUTE((*this), "/api/system/status").methods("GET"_method) - ([this] { - auto cpu_util = m_monitor.getCpuUtilization(); - auto mem_info = m_monitor.getMemoryInfo(); + CROW_ROUTE((*this), "/api/system/id").methods("GET"_method)([this] { + auto deviceID = ConfigManager::getInstance().getDeviceID(); + crow::json::wvalue response; + response["deviceID"] = deviceID; + return response; + }); - crow::json::wvalue response; - response["cpu_usage_percentage"] = cpu_util.totalUsagePercentage; - response["memory_total_kb"] = mem_info.total_kb; - response["memory_free_kb"] = mem_info.available_kb; - response["memory_usage_percentage"] = (mem_info.total_kb > 0) - ? (1.0 - static_cast(mem_info.available_kb) / mem_info.total_kb) * 100.0 + CROW_ROUTE((*this), "/api/system/status").methods("GET"_method)([this] { + auto cpu_util = m_monitor.getCpuUtilization(); + auto mem_info = m_monitor.getMemoryInfo(); + + crow::json::wvalue response; + response["cpu_usage_percentage"] = cpu_util.totalUsagePercentage; + response["memory_total_kb"] = mem_info.total_kb; + response["memory_free_kb"] = mem_info.available_kb; + response["memory_usage_percentage"] = + (mem_info.total_kb > 0) + ? (1.0 - + static_cast(mem_info.available_kb) / mem_info.total_kb) * + 100.0 : 0.0; - - return response; - }); - CROW_ROUTE((*this), "/api/devices").methods("GET"_method) - ([this] { - auto devices_info = m_device_manager.get_all_device_info(); + return response; + }); - std::vector devices_json; - for (const auto& info : devices_info) { - crow::json::wvalue device_obj; + CROW_ROUTE((*this), "/api/devices").methods("GET"_method)([this] { + auto devices_info = m_device_manager.get_all_device_info(); - device_obj["id"] = info.id; - device_obj["type"] = info.type; - device_obj["is_running"] = info.is_running; + std::vector devices_json; + for (const auto& info : devices_info) { + crow::json::wvalue device_obj; - crow::json::wvalue details_obj; - for (const auto& pair : info.connection_details) { - details_obj[pair.first] = pair.second; - } - device_obj["connection_details"] = std::move(details_obj); - - devices_json.push_back(std::move(device_obj)); - } - - return crow::json::wvalue(devices_json); - }); + device_obj["id"] = info.id; + device_obj["type"] = info.type; + device_obj["is_running"] = info.is_running; + crow::json::wvalue details_obj; + for (const auto& pair : info.connection_details) { + details_obj[pair.first] = pair.second; + } + device_obj["connection_details"] = std::move(details_obj); - CROW_ROUTE((*this), "/api/data/latest").methods("GET"_method) - ([this] { - auto latest_data_map = m_live_data_cache.get_all_data(); + devices_json.push_back(std::move(device_obj)); + } - crow::json::wvalue response; - for (const auto& pair : latest_data_map) { - response[pair.first] = crow::json::load(pair.second); - } - - return response; - }); + return crow::json::wvalue(devices_json); + }); - CROW_ROUTE((*this), "/api/alarms/active").methods("GET"_method) - ([this] { - try { - auto json_string = m_alarm_service.getActiveAlarmsJson().dump(); + CROW_ROUTE((*this), "/api/data/latest").methods("GET"_method)([this] { + auto latest_data_map = m_live_data_cache.get_all_data(); - auto res = crow::response(200, json_string); - res.set_header("Content-Type", "application/json"); - return res; + crow::json::wvalue response; + for (const auto& pair : latest_data_map) { + response[pair.first] = crow::json::load(pair.second); + } - } catch (const std::exception& e) { - spdlog::error("Error processing /api/alarms/active: {}", e.what()); - crow::json::wvalue error_resp; - error_resp["error"] = "Failed to retrieve active alarms."; - - auto res = crow::response(500, error_resp.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - }); + return response; + }); - CROW_ROUTE((*this), "/api/alarms/history").methods("GET"_method) - ([this](const crow::request& req) { - int limit = 100; + CROW_ROUTE((*this), "/api/alarms/active").methods("GET"_method)([this] { + try { + auto json_string = m_alarm_service.getActiveAlarmsJson().dump(); + + auto res = crow::response(200, json_string); + res.set_header("Content-Type", "application/json"); + return res; + + } catch (const std::exception& e) { + spdlog::error("Error processing /api/alarms/active: {}", e.what()); + crow::json::wvalue error_resp; + error_resp["error"] = "Failed to retrieve active alarms."; + + auto res = crow::response(500, error_resp.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + CROW_ROUTE((*this), "/api/alarms/history") + .methods("GET"_method)([this](const crow::request& req) { + int limit = 100; if (req.url_params.get("limit")) { - try { - limit = std::stoi(req.url_params.get("limit")); - } catch (const std::exception&) { /* ignore invalid */ } + try { + limit = std::stoi(req.url_params.get("limit")); + } catch (const std::exception&) { /* ignore invalid */ + } } if (limit <= 0) limit = 100; try { - auto json_string = m_alarm_service.getAlarmHistoryJson(limit).dump(); + auto json_string = m_alarm_service.getAlarmHistoryJson(limit).dump(); - auto res = crow::response(200, json_string); - res.set_header("Content-Type", "application/json"); - return res; + auto res = crow::response(200, json_string); + res.set_header("Content-Type", "application/json"); + return res; } catch (const std::exception& e) { - spdlog::error("Error processing /api/alarms/history: {}", e.what()); - crow::json::wvalue error_resp; - error_resp["error"] = "Failed to retrieve alarm history."; + spdlog::error("Error processing /api/alarms/history: {}", e.what()); + crow::json::wvalue error_resp; + error_resp["error"] = "Failed to retrieve alarm history."; - auto res = crow::response(500, error_resp.dump()); - res.set_header("Content-Type", "application/json"); - return res; + auto res = crow::response(500, error_resp.dump()); + res.set_header("Content-Type", "application/json"); + return res; } - }); - - - + }); } \ No newline at end of file diff --git a/src/web/web_server.h b/src/web/web_server.h index e12b55e..6eedbb4 100644 --- a/src/web/web_server.h +++ b/src/web/web_server.h @@ -37,6 +37,7 @@ private: DeviceManager& m_device_manager; LiveDataCache& m_live_data_cache; AlarmService& m_alarm_service; + uint16_t m_port;