一些给APP的展示修改
This commit is contained in:
parent
975a28dfe5
commit
4e8cb2e4a0
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
379
src/main.cpp
379
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 <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;
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ private:
|
|||
DeviceManager& m_device_manager;
|
||||
LiveDataCache& m_live_data_cache;
|
||||
AlarmService& m_alarm_service;
|
||||
|
||||
|
||||
uint16_t m_port;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue