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