一些给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/",
"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": [
{

View File

@ -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"
}
]
}
}
],
"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 <fstream>
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<std::shared_mutex> lock(m_mutex);
bool ConfigManager::load(const std::string &configFilePath)
{
std::unique_lock<std::shared_mutex> 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<std::shared_mutex> lock(m_mutex);
return save_unlocked();
}
std::string ConfigManager::getDeviceID() {
std::string ConfigManager::getDeviceID()
{
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/");
}
std::string ConfigManager::getMqttBroker() {
std::string ConfigManager::getMqttBroker()
{
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();
}
std::string ConfigManager::getDataStorageDbPath() {
std::string ConfigManager::getDataStorageDbPath()
{
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");
}
std::string ConfigManager::getDevicesConfigPath() {
return getConfigBasePath() + "devices.json";
std::string ConfigManager::getDevicesConfigPath()
{
return getConfigBasePath() + "devices.json";
}
int ConfigManager::getWebServerPort() {
int ConfigManager::getWebServerPort()
{
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});
}
std::string ConfigManager::getLogLevel() {
std::string ConfigManager::getLogLevel()
{
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");
}
std::string ConfigManager::getPiperExecutablePath() {
std::string ConfigManager::getPiperExecutablePath()
{
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");
}
// --- (getIsVideoServiceEnabled 保持不变) ---
bool ConfigManager::getIsVideoServiceEnabled() const {
std::shared_lock<std::shared_mutex> 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<std::shared_mutex> 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::VideoStreamConfig> ConfigManager::getVideoStreamConfigs() const
{
std::vector<VideoStreamConfig> configs;
std::shared_lock<std::shared_mutex> lock(m_mutex);
// --- 修改: getVideoStreamConfigs ---
std::vector<ConfigManager::VideoStreamConfig> ConfigManager::getVideoStreamConfigs() const {
std::vector<VideoStreamConfig> configs;
std::shared_lock<std::shared_mutex> 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;
}

View File

@ -6,67 +6,71 @@
#include <shared_mutex>
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <typeinfo>
#include <typeinfo>
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<typename T>
T get(const std::string& key, const T& default_value) {
template <typename T>
T get(const std::string &key, const T &default_value)
{
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);
return default_value;
}
try {
try
{
return m_config_json.at(key).get<T>();
} 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<typename T>
void set(const std::string& key, const T& value) {
template <typename T>
void set(const std::string &key, const T &value)
{
{
std::unique_lock<std::shared_mutex> 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<VideoStreamConfig> getVideoStreamConfigs() const; // (签名不变, 实现改变)
std::vector<VideoStreamConfig> 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;
};

View File

@ -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 <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <csignal>
#include <iostream>
#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;
/**
* @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<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();
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<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);
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;
}

View File

@ -5,6 +5,7 @@
#include <unistd.h>
#include <array>
#include <memory>
using json = nlohmann::json;
namespace SystemMonitor {
@ -71,27 +72,35 @@ MemoryInfo SystemMonitor::getMemoryInfo() const {
return info;
}
std::vector<ThermalInfo> SystemMonitor::getChipTemperature() const {
std::vector<ThermalInfo> temps;
std::string SystemMonitor::getChipTemperature() const {
// std::vector<ThermalInfo> temps;
const std::vector<std::pair<std::string, std::string>> 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 {

View File

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

View File

@ -1,153 +1,157 @@
// 文件名: src/web/web_server.cc
#include "web_server.h"
#include <fstream>
#include <sstream>
#include "config/config_manager.h"
#include "spdlog/spdlog.h"
#include <fstream>
#include <sstream>
WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager, LiveDataCache& liveDataCache, AlarmService& alarm_service, uint16_t port)
: crow::Crow<crow::CORSHandler>(),
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<crow::CORSHandler>();
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<crow::CORSHandler>(),
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<crow::CORSHandler>();
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<crow::CORSHandler>::stop();
if (m_thread.joinable()) {
m_thread.join();
}
crow::Crow<crow::CORSHandler>::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<double>(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<double>(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<crow::json::wvalue> 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<crow::json::wvalue> 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;
}
});
});
}

View File

@ -37,6 +37,7 @@ private:
DeviceManager& m_device_manager;
LiveDataCache& m_live_data_cache;
AlarmService& m_alarm_service;
uint16_t m_port;