一些给APP的展示修改

This commit is contained in:
GuanYuankai 2025-11-03 01:37:54 +00:00
parent 975a28dfe5
commit 4e8cb2e4a0
10 changed files with 571 additions and 493 deletions

View File

@ -3,7 +3,7 @@
"config_base_path": "/app/config/", "config_base_path": "/app/config/",
"data_cache_db_path": "edge_data_cache.db", "data_cache_db_path": "edge_data_cache.db",
"data_storage_db_path": "edge_proxy_data.db", "data_storage_db_path": "edge_proxy_data.db",
"device_id": "rk3588-proxy-001", "device_id": "rk3588-proxy-002",
"log_level": "info", "log_level": "info",
"mqtt_broker": "tcp://localhost:1883", "mqtt_broker": "tcp://localhost:1883",
"mqtt_client_id_prefix": "edge-proxy-", "mqtt_client_id_prefix": "edge-proxy-",
@ -13,7 +13,7 @@
12345 12345
], ],
"video_service": { "video_service": {
"enabled": true "enabled": false
}, },
"video_streams": [ "video_streams": [
{ {

View File

@ -1,59 +1,98 @@
{ {
"modbus_rtu_devices": [ "modbus_rtu_devices": [
{ {
"enabled": false, "enabled": true,
"device_id": "rtu_temp_sensor_lab", "device_id": "rtu_temp_sensor_lab",
"port_path": "/dev/ttyS7", "port_path": "/dev/ttyS7",
"baud_rate": 9600, "baud_rate": 9600,
"slave_id": 1, "slave_id": 1,
"poll_interval_ms": 5000, "poll_interval_ms": 5000,
"data_points": [ "data_points": [
{"name": "temperature", "address": 0, "type": "INT16", "scale": 0.1}, {
{"name": "humidity", "address": 1, "type": "UINT16", "scale": 0.1} "name": "temperature",
] "address": 0,
}, "type": "INT16",
{ "scale": 0.1
"enabled": false, },
"device_id": "rotary encoder", {
"port_path": "/dev/ttyS7", "name": "humidity",
"baud_rate": 9600, "address": 1,
"slave_id": 111, "type": "UINT16",
"poll_interval_ms": 5000, "scale": 0.1
"data_points": [ }
{"name": "count", "address": 1, "type": "INT16", "scale": 1.0}, ]
{"name": "total_count", "address": 2, "type": "INT16", "scale": 1.0} },
] {
}, "enabled": true,
{ "device_id": "rotary encoder",
"enabled": false, "port_path": "/dev/ttyS7",
"device_id": "backup_counter", "baud_rate": 9600,
"port_path": "/dev/ttyS7", "slave_id": 111,
"baud_rate": 9600, "poll_interval_ms": 5000,
"slave_id": 10, "data_points": [
"poll_interval_ms": 1000, {
"data_points": [ "name": "count",
{"name": "count", "address": 32, "type": "UINT32"} "address": 1,
] "type": "INT16",
} "scale": 1.0
], },
"modbus_tcp_devices": [ {
{ "name": "total_count",
"enabled": false, "address": 2,
"device_id": "plc_workshop1", "type": "INT16",
"ip_address": "192.168.1.120", "scale": 1.0
"port": 502, }
"slave_id": 1, ]
"poll_interval_ms": 1000, },
"data_points": [ {
{"name": "motor_speed", "address": 100, "type": "UINT16", "scale": 1.0}, "enabled": false,
{"name": "pressure", "address": 102, "type": "FLOAT32", "scale": 0.01}, "device_id": "backup_counter",
{"name": "valve_status","address": 104, "type": "UINT16", "scale": 1.0} "port_path": "/dev/ttyS7",
] "baud_rate": 9600,
} "slave_id": 10,
], "poll_interval_ms": 1000,
"modbus_rtu_bus_configs": { "data_points": [
"/dev/ttyS7": { {
"inter_device_delay_ms": 150 "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
} }
} }
}

Binary file not shown.

View File

@ -2,14 +2,16 @@
#include "config_manager.h" #include "config_manager.h"
#include <fstream> #include <fstream>
ConfigManager& ConfigManager::getInstance() { ConfigManager &ConfigManager::getInstance()
{
static ConfigManager instance; static ConfigManager instance;
return instance; return instance;
} }
json ConfigManager::createDefaultConfig() { json ConfigManager::createDefaultConfig()
{
// --- 修改: 添加新的 video_service 和 video_streams 默认值 --- // --- 修改: 添加新的 video_service 和 video_streams 默认值 ---
return json { return json{
{"device_id", "default-edge-proxy-01"}, {"device_id", "default-edge-proxy-01"},
{"config_base_path", "/app/config/"}, {"config_base_path", "/app/config/"},
{"mqtt_broker", "tcp://localhost:1883"}, {"mqtt_broker", "tcp://localhost:1883"},
@ -25,76 +27,70 @@ json ConfigManager::createDefaultConfig() {
// --- 新增: 视频服务配置 --- // --- 新增: 视频服务配置 ---
{ {
"video_service", { "video_service", {{"enabled", false}}},
{"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_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); std::ofstream ofs(m_configFilePath);
if (!ofs.is_open()) { if (!ofs.is_open())
{
spdlog::error("Failed to open config file '{}' for writing.", m_configFilePath); spdlog::error("Failed to open config file '{}' for writing.", m_configFilePath);
return false; return false;
} }
try { try
{
ofs << m_config_json.dump(4); ofs << m_config_json.dump(4);
return true; return true;
} catch (const json::exception& e) { }
catch (const json::exception &e)
{
spdlog::error("Failed to serialize config. Error: {}", e.what()); spdlog::error("Failed to serialize config. Error: {}", e.what());
return false; return false;
} }
} }
bool ConfigManager::load(const std::string& configFilePath) { bool ConfigManager::load(const std::string &configFilePath)
{
std::unique_lock<std::shared_mutex> lock(m_mutex); std::unique_lock<std::shared_mutex> lock(m_mutex);
m_configFilePath = configFilePath; m_configFilePath = configFilePath;
std::ifstream ifs(m_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); spdlog::warn("Config file '{}' not found. Creating with default values.", m_configFilePath);
m_config_json = createDefaultConfig(); m_config_json = createDefaultConfig();
if(save_unlocked()) { if (save_unlocked())
{
spdlog::info("Default config file created at '{}'.", m_configFilePath); spdlog::info("Default config file created at '{}'.", m_configFilePath);
return true; return true;
} else { }
else
{
spdlog::error("Failed to create default config file."); spdlog::error("Failed to create default config file.");
return false; return false;
} }
} }
try { try
{
ifs >> m_config_json; ifs >> m_config_json;
// **重要**:合并默认值。 // **重要**:合并默认值。
json defaults = createDefaultConfig(); json defaults = createDefaultConfig();
defaults.merge_patch(m_config_json); // <-- 关键: m_config_json 会覆盖 defaults 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")); spdlog::info("Successfully loaded config from '{}'. Device ID: {}", m_configFilePath, m_config_json.value("device_id", "N/A"));
if (save_unlocked()) { if (save_unlocked())
spdlog::debug("Config file updated with new default keys if any."); {
spdlog::debug("Config file updated with new default keys if any.");
} }
return true; 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()); spdlog::error("Failed to parse config file '{}'. Error: {}. Using default values.", m_configFilePath, e.what());
m_config_json = createDefaultConfig(); m_config_json = createDefaultConfig();
return false; return false;
@ -102,103 +98,118 @@ bool ConfigManager::load(const std::string& configFilePath) {
} }
// ... (save, getDeviceID, ... getPiperModelPath 保持不变) ... // ... (save, getDeviceID, ... getPiperModelPath 保持不变) ...
bool ConfigManager::save() { bool ConfigManager::save()
{
std::unique_lock<std::shared_mutex> lock(m_mutex); std::unique_lock<std::shared_mutex> lock(m_mutex);
return save_unlocked(); return save_unlocked();
} }
std::string ConfigManager::getDeviceID() { std::string ConfigManager::getDeviceID()
{
return get<std::string>("device_id", "default-edge-proxy-01"); return get<std::string>("device_id", "default-edge-proxy-01");
} }
std::string ConfigManager::getConfigBasePath() { std::string ConfigManager::getConfigBasePath()
{
return get<std::string>("config_base_path", "/app/config/"); return get<std::string>("config_base_path", "/app/config/");
} }
std::string ConfigManager::getMqttBroker() { std::string ConfigManager::getMqttBroker()
{
return get<std::string>("mqtt_broker", "tcp://localhost:1883"); return get<std::string>("mqtt_broker", "tcp://localhost:1883");
} }
std::string ConfigManager::getMqttClientID() { std::string ConfigManager::getMqttClientID()
{
return get<std::string>("mqtt_client_id_prefix", "edge-proxy-") + getDeviceID(); return get<std::string>("mqtt_client_id_prefix", "edge-proxy-") + getDeviceID();
} }
std::string ConfigManager::getDataStorageDbPath() { std::string ConfigManager::getDataStorageDbPath()
{
return getConfigBasePath() + get<std::string>("data_storage_db_path", "edge_proxy_data.db"); return getConfigBasePath() + get<std::string>("data_storage_db_path", "edge_proxy_data.db");
} }
std::string ConfigManager::getDataCacheDbPath() { std::string ConfigManager::getDataCacheDbPath()
{
return getConfigBasePath() + get<std::string>("data_cache_db_path", "edge_data_cache.db"); return getConfigBasePath() + get<std::string>("data_cache_db_path", "edge_data_cache.db");
} }
std::string ConfigManager::getDevicesConfigPath() { std::string ConfigManager::getDevicesConfigPath()
{
return getConfigBasePath() + "devices.json"; return getConfigBasePath() + "devices.json";
} }
int ConfigManager::getWebServerPort() { int ConfigManager::getWebServerPort()
{
return get<int>("web_server_port", 8080); return get<int>("web_server_port", 8080);
} }
std::vector<uint16_t> ConfigManager::getTcpServerPorts() { std::vector<uint16_t> ConfigManager::getTcpServerPorts()
{
return get<std::vector<uint16_t>>("tcp_server_ports", {12345}); return get<std::vector<uint16_t>>("tcp_server_ports", {12345});
} }
std::string ConfigManager::getLogLevel() { std::string ConfigManager::getLogLevel()
{
return get<std::string>("log_level", "debug"); return get<std::string>("log_level", "debug");
} }
std::string ConfigManager::getAlarmRulesPath() { std::string ConfigManager::getAlarmRulesPath()
{
return getConfigBasePath() + get<std::string>("alarm_rules_path", "alarms.json"); return getConfigBasePath() + get<std::string>("alarm_rules_path", "alarms.json");
} }
std::string ConfigManager::getPiperExecutablePath() { std::string ConfigManager::getPiperExecutablePath()
{
return get<std::string>("piper_executable_path", "/usr/bin/piper"); return get<std::string>("piper_executable_path", "/usr/bin/piper");
} }
std::string ConfigManager::getPiperModelPath() { std::string ConfigManager::getPiperModelPath()
{
return get<std::string>("piper_model_path", "/app/models/model.onnx"); return get<std::string>("piper_model_path", "/app/models/model.onnx");
} }
// --- (getIsVideoServiceEnabled 保持不变) --- bool ConfigManager::getIsVideoServiceEnabled() const
bool ConfigManager::getIsVideoServiceEnabled() const { {
std::shared_lock<std::shared_mutex> lock(m_mutex); std::shared_lock<std::shared_mutex> lock(m_mutex);
try { try
if (m_config_json.contains("video_service")) { {
if (m_config_json.contains("video_service"))
{
return m_config_json["video_service"].value("enabled", false); 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()); spdlog::warn("Config type mismatch for key 'video_service.enabled'. Error: {}", e.what());
} }
return false; return false;
} }
// --- 移除: getVideoModelPath --- std::vector<ConfigManager::VideoStreamConfig> ConfigManager::getVideoStreamConfigs() const
// std::string ConfigManager::getVideoModelPath() const { ... } {
// --- 修改: getVideoStreamConfigs ---
std::vector<ConfigManager::VideoStreamConfig> ConfigManager::getVideoStreamConfigs() const {
std::vector<VideoStreamConfig> configs; std::vector<VideoStreamConfig> configs;
std::shared_lock<std::shared_mutex> lock(m_mutex); std::shared_lock<std::shared_mutex> lock(m_mutex);
try { try
// --- 修改: 路径变为顶层的 "video_streams" --- {
if (m_config_json.contains("video_streams") && 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"]) { for (const auto &stream_json : m_config_json["video_streams"])
{
VideoStreamConfig cfg; VideoStreamConfig cfg;
cfg.id = stream_json.value("id", ""); cfg.id = stream_json.value("id", "");
cfg.enabled = stream_json.value("enabled", false); cfg.enabled = stream_json.value("enabled", false);
cfg.input_url = stream_json.value("input_url", ""); cfg.input_url = stream_json.value("input_url", "");
cfg.output_rtsp = stream_json.value("output_rtsp", ""); 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_type = stream_json.value("module_type", "");
cfg.module_config = stream_json.value("module_config", json::object()); // 传递整个json对象 cfg.module_config = stream_json.value("module_config", json::object());
if (cfg.module_type.empty()) { if (cfg.module_type.empty())
{
spdlog::warn("Video stream '{}' has no 'module_type' defined. It may fail to start.", cfg.id); spdlog::warn("Video stream '{}' has no 'module_type' defined. It may fail to start.", cfg.id);
} }
configs.push_back(cfg); configs.push_back(cfg);
} }
} else { }
else
{
spdlog::warn("Config key 'video_streams' not found or is not an array."); 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()); spdlog::error("Error parsing 'video_streams': {}", e.what());
} }

View File

@ -10,38 +10,41 @@
using json = nlohmann::json; using json = nlohmann::json;
class ConfigManager { class ConfigManager
{
public: public:
// struct VideoStreamConfig
// --- 核心修改 --- {
//
struct VideoStreamConfig {
std::string id; std::string id;
bool enabled; bool enabled;
std::string input_url; std::string input_url;
std::string output_rtsp; std::string output_rtsp;
// int rknn_thread_num; // <-- 移除 (已移动到 module_config)
std::string module_type; // <-- 新增: "intrusion_detection" 或 "face_recognition" std::string module_type;
json module_config; // <-- 新增: 传递模块的特定配置 (最灵活的方式) json module_config;
}; };
static ConfigManager& getInstance(); static ConfigManager &getInstance();
bool load(const std::string& configFilePath); bool load(const std::string &configFilePath);
bool save(); bool save();
template<typename T> template <typename T>
T get(const std::string& key, const T& default_value) { T get(const std::string &key, const T &default_value)
{
std::shared_lock<std::shared_mutex> lock(m_mutex); std::shared_lock<std::shared_mutex> 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); spdlog::debug("Config key '{}' not found, using default value.", key);
return default_value; return default_value;
} }
try { try
{
return m_config_json.at(key).get<T>(); return m_config_json.at(key).get<T>();
} catch (const json::type_error& e) { }
catch (const json::type_error &e)
{
spdlog::warn("Config type mismatch for key '{}'. Expected '{}', found '{}'. Using default. Error: {}", spdlog::warn("Config type mismatch for key '{}'. Expected '{}', found '{}'. Using default. Error: {}",
key, key,
typeid(T).name(), typeid(T).name(),
@ -51,8 +54,9 @@ public:
} }
} }
template<typename T> template <typename T>
void set(const std::string& key, const T& value) { void set(const std::string &key, const T &value)
{
{ {
std::unique_lock<std::shared_mutex> lock(m_mutex); std::unique_lock<std::shared_mutex> lock(m_mutex);
m_config_json[key] = value; m_config_json[key] = value;
@ -61,12 +65,12 @@ public:
save(); save();
if (key == "log_level") { if (key == "log_level")
{
spdlog::set_level(spdlog::level::from_str(value)); spdlog::set_level(spdlog::level::from_str(value));
} }
} }
std::string getDeviceID(); std::string getDeviceID();
std::string getConfigBasePath(); std::string getConfigBasePath();
std::string getMqttBroker(); std::string getMqttBroker();
@ -82,13 +86,13 @@ public:
std::string getPiperModelPath(); std::string getPiperModelPath();
bool getIsVideoServiceEnabled() const; bool getIsVideoServiceEnabled() const;
std::vector<VideoStreamConfig> getVideoStreamConfigs() const; // (签名不变, 实现改变) std::vector<VideoStreamConfig> getVideoStreamConfigs() const;
private: private:
ConfigManager() = default; ConfigManager() = default;
~ConfigManager() = default; ~ConfigManager() = default;
ConfigManager(const ConfigManager&) = delete; ConfigManager(const ConfigManager &) = delete;
ConfigManager& operator=(const ConfigManager&) = delete; ConfigManager &operator=(const ConfigManager &) = delete;
json createDefaultConfig(); json createDefaultConfig();
bool save_unlocked(); bool save_unlocked();

View File

@ -1,224 +1,233 @@
// main.cpp // 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 <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
#include <csignal> #include <csignal>
#include <iostream>
#include <functional> #include <functional>
#include <iostream>
#include <string>
#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; boost::asio::io_context g_io_context;
/** /**
* @brief MQTT * @brief MQTT
*/ */
void poll_system_metrics( void poll_system_metrics(boost::asio::steady_timer& timer,
boost::asio::steady_timer& timer, SystemMonitor::SystemMonitor& monitor,
SystemMonitor::SystemMonitor& monitor, MqttClient& mqtt_client, AlarmService& alarm_service) {
MqttClient& mqtt_client, if (g_io_context.stopped()) return;
AlarmService& alarm_service auto cpu_util = monitor.getCpuUtilization();
) { auto mem_info = monitor.getMemoryInfo();
if (g_io_context.stopped()) return; double mem_total_gb = mem_info.total_kb / 1024.0 / 1024.0;
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) double mem_usage_percentage =
? (100.0 * (mem_info.total_kb - mem_info.available_kb) / mem_info.total_kb) (mem_info.total_kb > 0)
: 0.0; ? (100.0 * (mem_info.total_kb - mem_info.available_kb) /
mem_info.total_kb)
: 0.0;
std::string topic = "proxy/system_status"; auto thermalInfoString = monitor.getChipTemperature();
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); std::string topic = "proxy/system_status";
std::string payload;
mqtt_client.publish(topic, payload); try {
spdlog::debug("System metrics published."); 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);
timer.expires_at(timer.expiry() + std::chrono::seconds(15)); payload = payload_json.dump();
timer.async_wait(std::bind(poll_system_metrics,
std::ref(timer), } catch (const nlohmann::json::parse_error& e) {
std::ref(monitor), spdlog::error("Failed to parse thermalInfo JSON: {}. Sending partial data.",
std::ref(mqtt_client), e.what());
std::ref(alarm_service) 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[]) { 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"; auto& config = ConfigManager::getInstance();
if (!ConfigManager::getInstance().load(config_path)) {
std::cerr << "Failed to load configuration from " << config_path try {
<< ". Running with defaults, but this may cause issues." << std::endl; 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 { live_data_cache.update_data(data.device_id, data.data_json);
spdlog::set_level(spdlog::level::from_str(config.getLogLevel()));
spdlog::info("Edge Proxy starting up..."); alarm_service.process_device_data(data.device_id, data.data_json);
} catch (const spdlog::spdlog_ex& ex) {
std::cerr << "Log initialization failed: " << ex.what() << std::endl; if (mqtt_client.is_connected()) {
return 1; 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<uint16_t> 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();
});
mqtt_client.connect();
mqtt_router.start();
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)));
device_manager.load_and_start(config.getDevicesConfigPath());
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.");
} }
spdlog::info("Initializing Data Storage..."); boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM);
auto& data_storage = DataStorage::getInstance(); signals.async_wait(
if (!data_storage.initialize(config.getDataStorageDbPath())) { [&](const boost::system::error_code& error, int signal_number) {
spdlog::critical("Failed to initialize DataStorage. Exiting."); spdlog::warn("Interrupt signal ({}) received. Shutting down.",
return 1; signal_number);
}
try { // a. 停止所有数据采集
spdlog::info("Initializing Video Service..."); spdlog::info("[Shutdown] A. Stopping device manager services...");
VideoServiceManager video_manager; device_manager.stop_all();
DataCache data_cache; // b. 停止Web服务器
LiveDataCache live_data_cache; spdlog::info("[Shutdown] B. Stopping web server...");
MqttClient mqtt_client(config.getMqttBroker(), web_server.stop();
config.getMqttClientID()
);
PiperTTSInterface tts_service( // c. [新] 停止告警服务 (释放TTS线程)
config.getPiperExecutablePath(), spdlog::info("[Shutdown] C. Stopping alarm service...");
config.getPiperModelPath() alarm_service.stop();
);
AlarmService alarm_service(g_io_context, // d. 断开MQTT
tts_service, spdlog::info("[Shutdown] D. Disconnecting from MQTT broker...");
mqtt_client, mqtt_client.disconnect();
data_storage);
if (!alarm_service.load_rules(config.getAlarmRulesPath())) { spdlog::info("[Shutdown] E. Stopping video Service loop...");
spdlog::error("Failed to load alarm rules. Alarms may be disabled."); video_manager.stop_all();
} // e. 最后安全地停止io_context
spdlog::info("[Shutdown] F. Stopping main event loop...");
auto report_to_mqtt = [&](const UnifiedData& data) { g_io_context.stop();
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<uint16_t> 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();
}); });
mqtt_client.connect(); spdlog::info("All services are running. Press Ctrl+C to exit.");
mqtt_router.start(); g_io_context.run();
} catch (const std::exception& e) {
spdlog::critical("An unhandled exception occurred: {}", e.what());
return 1;
}
monitor.getCpuUtilization(); spdlog::info("Server has been shut down gracefully. Exiting.");
boost::asio::steady_timer system_monitor_timer(g_io_context, std::chrono::seconds(15)); return 0;
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)
));
device_manager.load_and_start(config.getDevicesConfigPath());
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.");
}
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();
} 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;
} }

View File

@ -5,6 +5,7 @@
#include <unistd.h> #include <unistd.h>
#include <array> #include <array>
#include <memory> #include <memory>
using json = nlohmann::json;
namespace SystemMonitor { namespace SystemMonitor {
@ -71,27 +72,35 @@ MemoryInfo SystemMonitor::getMemoryInfo() const {
return info; return info;
} }
std::vector<ThermalInfo> SystemMonitor::getChipTemperature() const { std::string SystemMonitor::getChipTemperature() const {
std::vector<ThermalInfo> temps; // std::vector<ThermalInfo> temps;
const std::vector<std::pair<std::string, std::string>> zones = { const std::vector<std::pair<std::string, std::string>> zones = {
{"CPU Core", "/sys/class/thermal/thermal_zone0/temp"}, {"CPU Core", "/sys/class/thermal/thermal_zone0/temp"},
{"GPU Core", "/sys/class/thermal/thermal_zone1/temp"}, {"GPU Core", "/sys/class/thermal/thermal_zone1/temp"},
{"NPU Core", "/sys/class/thermal/thermal_zone2/temp"}, {"NPU Core", "/sys/class/thermal/thermal_zone2/temp"},
{"Other", "/sys/class/thermal/thermal_zone3/temp"} {"Other", "/sys/class/thermal/thermal_zone3/temp"}
}; };
json jsonArray = json::array();
for (const auto& zone : zones) { for (const auto& zone : zones) {
std::string temp_str = readFile(zone.second); std::string temp_str = readFile(zone.second);
if (!temp_str.empty()) { if (!temp_str.empty()) {
try { try {
ThermalInfo info;
info.zoneName = zone.first; json tempObject;
info.temperatureCelsius = std::stod(temp_str) / 1000.0; tempObject["zoneName"] = zone.first;
temps.push_back(info); 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 (...) { /* 忽略转换失败 */ } } catch (...) { /* 忽略转换失败 */ }
} }
} }
return temps; return jsonArray.dump(2);
} }
CpuUtilization SystemMonitor::getCpuUtilization(int interval_ms) const { CpuUtilization SystemMonitor::getCpuUtilization(int interval_ms) const {

View File

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
#include <nlohmann/json.hpp> // 包含 nlohmann/json 库
namespace SystemMonitor { namespace SystemMonitor {
struct SystemInfo { struct SystemInfo {
@ -47,7 +48,7 @@ public:
SystemInfo getSystemInfo() const; SystemInfo getSystemInfo() const;
std::vector<StorageDevice> getStorageInfo() const; std::vector<StorageDevice> getStorageInfo() const;
MemoryInfo getMemoryInfo() const; MemoryInfo getMemoryInfo() const;
std::vector<ThermalInfo> getChipTemperature() const; std::string getChipTemperature() const;
CpuUtilization getCpuUtilization(int interval_ms = 1000) const; CpuUtilization getCpuUtilization(int interval_ms = 1000) const;
std::string getKernelLogs(int last_n_lines = 20) const; std::string getKernelLogs(int last_n_lines = 20) const;

View File

@ -1,153 +1,157 @@
// 文件名: src/web/web_server.cc // 文件名: src/web/web_server.cc
#include "web_server.h" #include "web_server.h"
#include "spdlog/spdlog.h"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager, LiveDataCache& liveDataCache, AlarmService& alarm_service, uint16_t port) #include "config/config_manager.h"
#include "spdlog/spdlog.h"
WebServer::WebServer(SystemMonitor::SystemMonitor& monitor,
DeviceManager& deviceManager, LiveDataCache& liveDataCache,
AlarmService& alarm_service, uint16_t port)
: crow::Crow<crow::CORSHandler>(), : crow::Crow<crow::CORSHandler>(),
m_monitor(monitor), m_monitor(monitor),
m_device_manager(deviceManager), m_device_manager(deviceManager),
m_live_data_cache(liveDataCache), m_live_data_cache(liveDataCache),
m_alarm_service(alarm_service), m_alarm_service(alarm_service),
m_port(port) m_port(port) {
{ auto& cors = this->get_middleware<crow::CORSHandler>();
auto& cors = this->get_middleware<crow::CORSHandler>(); cors.global()
cors .origin("*")
.global() .headers("Content-Type", "Authorization")
.origin("*") .methods("GET"_method, "POST"_method, "OPTIONS"_method);
.headers("Content-Type", "Authorization")
.methods("GET"_method, "POST"_method, "OPTIONS"_method);
this->loglevel(crow::LogLevel::Warning); this->loglevel(crow::LogLevel::Warning);
setup_routes(); setup_routes();
} }
WebServer::~WebServer() { WebServer::~WebServer() { stop(); }
stop();
}
void WebServer::start() { void WebServer::start() {
if (m_thread.joinable()) { if (m_thread.joinable()) {
spdlog::warn("Web server is already running."); spdlog::warn("Web server is already running.");
return; return;
} }
m_thread = std::thread([this]() { m_thread = std::thread([this]() {
spdlog::info("Starting Web server on port {}", m_port); spdlog::info("Starting Web server on port {}", m_port);
this->bindaddr("0.0.0.0").port(m_port).run(); this->bindaddr("0.0.0.0").port(m_port).run();
spdlog::info("Web server has stopped."); spdlog::info("Web server has stopped.");
}); });
} }
void WebServer::stop() { void WebServer::stop() {
crow::Crow<crow::CORSHandler>::stop(); crow::Crow<crow::CORSHandler>::stop();
if (m_thread.joinable()) { if (m_thread.joinable()) {
m_thread.join(); m_thread.join();
} }
} }
void WebServer::setup_routes() { void WebServer::setup_routes() {
CROW_ROUTE((*this), "/api/system/status").methods("GET"_method) CROW_ROUTE((*this), "/api/system/id").methods("GET"_method)([this] {
([this] { auto deviceID = ConfigManager::getInstance().getDeviceID();
auto cpu_util = m_monitor.getCpuUtilization(); crow::json::wvalue response;
auto mem_info = m_monitor.getMemoryInfo(); response["deviceID"] = deviceID;
return response;
});
crow::json::wvalue response; CROW_ROUTE((*this), "/api/system/status").methods("GET"_method)([this] {
response["cpu_usage_percentage"] = cpu_util.totalUsagePercentage; auto cpu_util = m_monitor.getCpuUtilization();
response["memory_total_kb"] = mem_info.total_kb; auto mem_info = m_monitor.getMemoryInfo();
response["memory_free_kb"] = mem_info.available_kb;
response["memory_usage_percentage"] = (mem_info.total_kb > 0) crow::json::wvalue response;
? (1.0 - static_cast<double>(mem_info.available_kb) / mem_info.total_kb) * 100.0 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<double>(mem_info.available_kb) / mem_info.total_kb) *
100.0
: 0.0; : 0.0;
return response; return response;
}); });
CROW_ROUTE((*this), "/api/devices").methods("GET"_method) CROW_ROUTE((*this), "/api/devices").methods("GET"_method)([this] {
([this] { auto devices_info = m_device_manager.get_all_device_info();
auto devices_info = m_device_manager.get_all_device_info();
std::vector<crow::json::wvalue> devices_json; std::vector<crow::json::wvalue> devices_json;
for (const auto& info : devices_info) { for (const auto& info : devices_info) {
crow::json::wvalue device_obj; crow::json::wvalue device_obj;
device_obj["id"] = info.id; device_obj["id"] = info.id;
device_obj["type"] = info.type; device_obj["type"] = info.type;
device_obj["is_running"] = info.is_running; device_obj["is_running"] = info.is_running;
crow::json::wvalue details_obj; crow::json::wvalue details_obj;
for (const auto& pair : info.connection_details) { for (const auto& pair : info.connection_details) {
details_obj[pair.first] = pair.second; details_obj[pair.first] = pair.second;
} }
device_obj["connection_details"] = std::move(details_obj); device_obj["connection_details"] = std::move(details_obj);
devices_json.push_back(std::move(device_obj)); devices_json.push_back(std::move(device_obj));
} }
return crow::json::wvalue(devices_json); return crow::json::wvalue(devices_json);
}); });
CROW_ROUTE((*this), "/api/data/latest").methods("GET"_method)([this] {
auto latest_data_map = m_live_data_cache.get_all_data();
CROW_ROUTE((*this), "/api/data/latest").methods("GET"_method) crow::json::wvalue response;
([this] { for (const auto& pair : latest_data_map) {
auto latest_data_map = m_live_data_cache.get_all_data(); response[pair.first] = crow::json::load(pair.second);
}
crow::json::wvalue response; return response;
for (const auto& pair : latest_data_map) { });
response[pair.first] = crow::json::load(pair.second);
}
return response; CROW_ROUTE((*this), "/api/alarms/active").methods("GET"_method)([this] {
}); try {
auto json_string = m_alarm_service.getActiveAlarmsJson().dump();
CROW_ROUTE((*this), "/api/alarms/active").methods("GET"_method) auto res = crow::response(200, json_string);
([this] { res.set_header("Content-Type", "application/json");
try { return res;
auto json_string = m_alarm_service.getActiveAlarmsJson().dump();
auto res = crow::response(200, json_string); } catch (const std::exception& e) {
res.set_header("Content-Type", "application/json"); spdlog::error("Error processing /api/alarms/active: {}", e.what());
return res; crow::json::wvalue error_resp;
error_resp["error"] = "Failed to retrieve active alarms.";
} catch (const std::exception& e) { auto res = crow::response(500, error_resp.dump());
spdlog::error("Error processing /api/alarms/active: {}", e.what()); res.set_header("Content-Type", "application/json");
crow::json::wvalue error_resp; return res;
error_resp["error"] = "Failed to retrieve active alarms."; }
});
auto res = crow::response(500, error_resp.dump()); CROW_ROUTE((*this), "/api/alarms/history")
res.set_header("Content-Type", "application/json"); .methods("GET"_method)([this](const crow::request& req) {
return res;
}
});
CROW_ROUTE((*this), "/api/alarms/history").methods("GET"_method)
([this](const crow::request& req) {
int limit = 100; int limit = 100;
if (req.url_params.get("limit")) { if (req.url_params.get("limit")) {
try { try {
limit = std::stoi(req.url_params.get("limit")); limit = std::stoi(req.url_params.get("limit"));
} catch (const std::exception&) { /* ignore invalid */ } } catch (const std::exception&) { /* ignore invalid */
}
} }
if (limit <= 0) limit = 100; if (limit <= 0) limit = 100;
try { 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); auto res = crow::response(200, json_string);
res.set_header("Content-Type", "application/json"); res.set_header("Content-Type", "application/json");
return res; return res;
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Error processing /api/alarms/history: {}", e.what()); spdlog::error("Error processing /api/alarms/history: {}", e.what());
crow::json::wvalue error_resp; crow::json::wvalue error_resp;
error_resp["error"] = "Failed to retrieve alarm history."; error_resp["error"] = "Failed to retrieve alarm history.";
auto res = crow::response(500, error_resp.dump()); auto res = crow::response(500, error_resp.dump());
res.set_header("Content-Type", "application/json"); res.set_header("Content-Type", "application/json");
return res; return res;
} }
}); });
} }

View File

@ -38,6 +38,7 @@ private:
LiveDataCache& m_live_data_cache; LiveDataCache& m_live_data_cache;
AlarmService& m_alarm_service; AlarmService& m_alarm_service;
uint16_t m_port; uint16_t m_port;
std::thread m_thread; std::thread m_thread;