diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 0cb6ed4..9c52da6 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,9 +3,12 @@ { "name": "Linux", "includePath": [ - "${workspaceFolder}/**" + "${workspaceFolder}/**", + "/usr/include" + ], + "defines": [ + "CROW_USE_BOOST" ], - "defines": [], "compilerPath": "/usr/bin/gcc", "cStandard": "c17", "cppStandard": "gnu++17", diff --git a/CMakeLists.txt b/CMakeLists.txt index 5381081..449e6d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,11 @@ find_package(spdlog REQUIRED) find_package(Boost REQUIRED COMPONENTS system thread) message(STATUS "Found Boost version: ${Boost_VERSION}") find_package(PahoMqttCpp REQUIRED) +find_package(SQLite3 REQUIRED) +# 在包含 Crow 子目录之前,设置一个 CMake 选项 +# 这会告诉 Crow 的构建脚本去使用 Boost 库中包含的 Asio +add_subdirectory(src/vendor/crow) add_library(nlohmann_json INTERFACE) target_include_directories(nlohmann_json INTERFACE @@ -54,6 +58,9 @@ add_library(edge_proxy_lib STATIC # 数据缓存/断点续传 src/dataCache/data_cache.cc src/dataCache/cache_uploader.cc + + #web + src/web/web_server.cc ) target_include_directories(edge_proxy_lib PUBLIC @@ -65,11 +72,14 @@ target_link_libraries(edge_proxy_lib PRIVATE Boost::system Boost::thread PahoMqttCpp::paho-mqttpp3 - sqlite3 + SQLite::SQLite3 pthread nlohmann_json ) +target_link_libraries(edge_proxy_lib PUBLIC + Crow +) # ================================================================= # Main Application Target (UNCOMMENTED AND ACTIVATED) # ================================================================= diff --git a/docker-compose.yml b/docker-compose.yml index 208d611..c251350 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: - "8888:8888" - "9999:9999" - "502:502" + - "8080:8080" command: sleep infinity mqtt-broker: diff --git a/docker/Dockerfile b/docker/Dockerfile index ead337e..36e09b2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,6 +21,11 @@ RUN apt-get update && \ git \ gdb \ vim \ + # Piper TTS 所需的系统库 + espeak-ng-data \ + libespeak1 \ + python3 \ + python3-pip \ # 开发库 libssl-dev \ libspdlog-dev \ @@ -68,8 +73,16 @@ RUN cd /tmp/build-context/external/paho.mqtt.c && \ rm -rf /tmp/build-context && \ # 在所有 apt 操作完成后,最后进行清理 rm -rf /var/lib/apt/lists/* +COPY piper_models/ /app/piper_models/ + +RUN pip install --no-cache-dir --user piper-tts +# 将 ~/.local/bin (包含 piper 二进制文件) 添加到 PATH +RUN echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bash_profile +# 现在,dev 用户可以使用 `piper` 命令了,只要 shell 环境被正确加载。 +# 我们可以将最终的 CMD 设置为加载环境然后执行一个命令,或者留空让用户交互。 # 5. 设置默认用户 # ================================= # 容器的默认用户将是 'dev' -USER dev \ No newline at end of file +USER dev + diff --git a/piper_models/zh_CN-huayan-medium.onnx b/piper_models/zh_CN-huayan-medium.onnx new file mode 100644 index 0000000..fd1b1c7 Binary files /dev/null and b/piper_models/zh_CN-huayan-medium.onnx differ diff --git a/piper_models/zh_CN-huayan-medium.onnx.json b/piper_models/zh_CN-huayan-medium.onnx.json new file mode 100644 index 0000000..f0e6e6e --- /dev/null +++ b/piper_models/zh_CN-huayan-medium.onnx.json @@ -0,0 +1,487 @@ +{ + "audio": { + "sample_rate": 22050, + "quality": "medium" + }, + "espeak": { + "voice": "cmn" + }, + "inference": { + "noise_scale": 0.667, + "length_scale": 1, + "noise_w": 0.8 + }, + "phoneme_type": "espeak", + "phoneme_map": {}, + "phoneme_id_map": { + "_": [ + 0 + ], + "^": [ + 1 + ], + "$": [ + 2 + ], + " ": [ + 3 + ], + "!": [ + 4 + ], + "'": [ + 5 + ], + "(": [ + 6 + ], + ")": [ + 7 + ], + ",": [ + 8 + ], + "-": [ + 9 + ], + ".": [ + 10 + ], + ":": [ + 11 + ], + ";": [ + 12 + ], + "?": [ + 13 + ], + "a": [ + 14 + ], + "b": [ + 15 + ], + "c": [ + 16 + ], + "d": [ + 17 + ], + "e": [ + 18 + ], + "f": [ + 19 + ], + "h": [ + 20 + ], + "i": [ + 21 + ], + "j": [ + 22 + ], + "k": [ + 23 + ], + "l": [ + 24 + ], + "m": [ + 25 + ], + "n": [ + 26 + ], + "o": [ + 27 + ], + "p": [ + 28 + ], + "q": [ + 29 + ], + "r": [ + 30 + ], + "s": [ + 31 + ], + "t": [ + 32 + ], + "u": [ + 33 + ], + "v": [ + 34 + ], + "w": [ + 35 + ], + "x": [ + 36 + ], + "y": [ + 37 + ], + "z": [ + 38 + ], + "æ": [ + 39 + ], + "ç": [ + 40 + ], + "ð": [ + 41 + ], + "ø": [ + 42 + ], + "ħ": [ + 43 + ], + "ŋ": [ + 44 + ], + "œ": [ + 45 + ], + "ǀ": [ + 46 + ], + "ǁ": [ + 47 + ], + "ǂ": [ + 48 + ], + "ǃ": [ + 49 + ], + "ɐ": [ + 50 + ], + "ɑ": [ + 51 + ], + "ɒ": [ + 52 + ], + "ɓ": [ + 53 + ], + "ɔ": [ + 54 + ], + "ɕ": [ + 55 + ], + "ɖ": [ + 56 + ], + "ɗ": [ + 57 + ], + "ɘ": [ + 58 + ], + "ə": [ + 59 + ], + "ɚ": [ + 60 + ], + "ɛ": [ + 61 + ], + "ɜ": [ + 62 + ], + "ɞ": [ + 63 + ], + "ɟ": [ + 64 + ], + "ɠ": [ + 65 + ], + "ɡ": [ + 66 + ], + "ɢ": [ + 67 + ], + "ɣ": [ + 68 + ], + "ɤ": [ + 69 + ], + "ɥ": [ + 70 + ], + "ɦ": [ + 71 + ], + "ɧ": [ + 72 + ], + "ɨ": [ + 73 + ], + "ɪ": [ + 74 + ], + "ɫ": [ + 75 + ], + "ɬ": [ + 76 + ], + "ɭ": [ + 77 + ], + "ɮ": [ + 78 + ], + "ɯ": [ + 79 + ], + "ɰ": [ + 80 + ], + "ɱ": [ + 81 + ], + "ɲ": [ + 82 + ], + "ɳ": [ + 83 + ], + "ɴ": [ + 84 + ], + "ɵ": [ + 85 + ], + "ɶ": [ + 86 + ], + "ɸ": [ + 87 + ], + "ɹ": [ + 88 + ], + "ɺ": [ + 89 + ], + "ɻ": [ + 90 + ], + "ɽ": [ + 91 + ], + "ɾ": [ + 92 + ], + "ʀ": [ + 93 + ], + "ʁ": [ + 94 + ], + "ʂ": [ + 95 + ], + "ʃ": [ + 96 + ], + "ʄ": [ + 97 + ], + "ʈ": [ + 98 + ], + "ʉ": [ + 99 + ], + "ʊ": [ + 100 + ], + "ʋ": [ + 101 + ], + "ʌ": [ + 102 + ], + "ʍ": [ + 103 + ], + "ʎ": [ + 104 + ], + "ʏ": [ + 105 + ], + "ʐ": [ + 106 + ], + "ʑ": [ + 107 + ], + "ʒ": [ + 108 + ], + "ʔ": [ + 109 + ], + "ʕ": [ + 110 + ], + "ʘ": [ + 111 + ], + "ʙ": [ + 112 + ], + "ʛ": [ + 113 + ], + "ʜ": [ + 114 + ], + "ʝ": [ + 115 + ], + "ʟ": [ + 116 + ], + "ʡ": [ + 117 + ], + "ʢ": [ + 118 + ], + "ʲ": [ + 119 + ], + "ˈ": [ + 120 + ], + "ˌ": [ + 121 + ], + "ː": [ + 122 + ], + "ˑ": [ + 123 + ], + "˞": [ + 124 + ], + "β": [ + 125 + ], + "θ": [ + 126 + ], + "χ": [ + 127 + ], + "ᵻ": [ + 128 + ], + "ⱱ": [ + 129 + ], + "0": [ + 130 + ], + "1": [ + 131 + ], + "2": [ + 132 + ], + "3": [ + 133 + ], + "4": [ + 134 + ], + "5": [ + 135 + ], + "6": [ + 136 + ], + "7": [ + 137 + ], + "8": [ + 138 + ], + "9": [ + 139 + ], + "̧": [ + 140 + ], + "̃": [ + 141 + ], + "̪": [ + 142 + ], + "̯": [ + 143 + ], + "̩": [ + 144 + ], + "ʰ": [ + 145 + ], + "ˤ": [ + 146 + ], + "ε": [ + 147 + ], + "↓": [ + 148 + ], + "#": [ + 149 + ], + "\"": [ + 150 + ], + "↑": [ + 151 + ] + }, + "num_symbols": 256, + "num_speakers": 1, + "speaker_id_map": {}, + "piper_version": "1.0.0", + "language": { + "code": "zh_CN", + "family": "zh", + "region": "CN", + "name_native": "简体中文", + "name_english": "Chinese", + "country_english": "China" + }, + "dataset": "huayan" +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 213eac3..6b84345 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "deviceManager/device_manager.h" #include "dataCache/data_cache.h" #include "dataCache/cache_uploader.h" +#include "web/web_server.h" #include #include @@ -34,7 +35,7 @@ void poll_system_metrics( SystemMonitor::SystemMonitor& monitor, MqttClient& mqtt_client ) { - // ... 此函数内部逻辑保持不变 ... + 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; @@ -63,38 +64,33 @@ int main(int argc, char* argv[]) { signal(SIGTERM, signalHandler); try { - // --- 1. 初始化核心服务 --- MqttClient mqtt_client("tcp://mqtt-broker:1883", "edge-proxy-main-client"); MqttRouter mqtt_router(mqtt_client); std::vector listen_ports = { 8888 }; TCPServer tcp_server(g_io_context, listen_ports, mqtt_client); SystemMonitor::SystemMonitor monitor; - // <<< MODIFIED: 初始化数据缓存和上传器 >>> DataCache data_cache; - if (!data_cache.open("edge_data_cache.db")) { // 数据库文件将创建在程序运行目录 + if (!data_cache.open("edge_data_cache.db")) { spdlog::critical("Failed to initialize data cache. Exiting."); return 1; } CacheUploader cache_uploader(g_io_context, mqtt_client, data_cache); - // <<< MODIFIED: 设置MQTT连接成功的回调,用于触发断点续传 >>> mqtt_client.set_connected_handler([&](const std::string& cause){ spdlog::info("MQTT client connected: {}", cause); - // 当连接成功时,启动缓存上传 cache_uploader.start_upload(); }); - // 现在再连接MQTT mqtt_client.connect(); mqtt_router.start(); - // --- 2. 启动系统状态监控定时器 --- - monitor.getCpuUtilization(); // 首次调用以初始化 + + 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))); - // --- 3. <<< MODIFIED: 创建包含缓存逻辑的统一数据上报回调函数 >>> + // --- 创建包含缓存逻辑的统一数据上报回调函数 >>> auto report_to_mqtt = [&](const UnifiedData& data) { if (mqtt_client.is_connected()) { // 网络正常,直接上报 @@ -109,19 +105,29 @@ int main(int argc, char* argv[]) { } }; - // --- 4. 实例化设备管理器并从文件加载所有设备 (使用新的回调) --- + // 实例化设备管理器并从文件加载所有设备 (使用新的回调) DeviceManager device_manager(g_io_context, report_to_mqtt); - // 默认从程序运行目录下的 "devices.json" 文件加载 device_manager.load_and_start("../config/devices.json"); - // --- 5. 启动主事件循环 (程序将阻塞在这里直到被信号中断) --- + WebServer web_server(monitor, 8080); + web_server.start(); + spdlog::info("All services are running. Press Ctrl+C to exit."); g_io_context.run(); // --- 清理工作 --- spdlog::info("Shutting down MQTT client..."); + + spdlog::info("Stopping device manager services..."); + device_manager.stop_all(); + spdlog::info("Stopping web server..."); + web_server.stop(); + spdlog::info("Disconnecting from MQTT broker..."); mqtt_client.disconnect(); + // mqtt_client.disconnect(); + // web_server.stop(); + } catch (const std::exception& e) { spdlog::critical("An unhandled exception occurred: {}", e.what()); return 1; diff --git a/src/modbus/modbus_master_poller.h b/src/modbus/modbus_master_poller.h index 8478373..c19b57f 100644 --- a/src/modbus/modbus_master_poller.h +++ b/src/modbus/modbus_master_poller.h @@ -3,7 +3,7 @@ #define MODBUS_MASTER_POLLER_H #include "protocol/iprotocol_adapter.h" // For UnifiedData and ReportDataCallback -#include "modbus_common.h" // <<< MODIFIED: 包含通用配置定义 +#include "modbus_common.h" #include #include #include diff --git a/src/systemMonitor/system_monitor.cc b/src/systemMonitor/system_monitor.cc index 4ed9789..3ba7c18 100644 --- a/src/systemMonitor/system_monitor.cc +++ b/src/systemMonitor/system_monitor.cc @@ -64,12 +64,21 @@ MemoryInfo SystemMonitor::getMemoryInfo() const { std::string meminfo_content = readFile("/proc/meminfo"); std::istringstream stream(meminfo_content); std::string line; + + // 遍历/proc/meminfo的每一行 while (std::getline(stream, line)) { + // 查找并解析总内存 if (line.rfind("MemTotal:", 0) == 0) { std::sscanf(line.c_str(), "MemTotal: %lu kB", &info.total_kb); - break; + } + // 查找并解析可用内存 (MemAvailable通常比MemFree更准确) + else if (line.rfind("MemAvailable:", 0) == 0) { + std::sscanf(line.c_str(), "MemAvailable: %lu kB", &info.available_kb); } } + + // 如果系统中没有 MemAvailable 字段 (非常老的内核), 则将 available_kb 设为0 + // 我们的 web_server 代码会处理这种情况 return info; } diff --git a/src/systemMonitor/system_monitor.h b/src/systemMonitor/system_monitor.h index 84a4275..2a8744d 100644 --- a/src/systemMonitor/system_monitor.h +++ b/src/systemMonitor/system_monitor.h @@ -27,6 +27,7 @@ struct StorageDevice { struct MemoryInfo { uint64_t total_kb = 0; + uint64_t available_kb = 0; // 未来可以添加 free, available, cached 等 }; @@ -55,7 +56,6 @@ public: std::string getKernelLogs(int last_n_lines = 20) const; private: - // --- 核心工具函数(现在作为私有成员)--- std::string readFile(const std::string& filePath) const; std::string execCommand(const char* cmd) const; diff --git a/src/web/web_server.cc b/src/web/web_server.cc new file mode 100644 index 0000000..93868bd --- /dev/null +++ b/src/web/web_server.cc @@ -0,0 +1,68 @@ +// 文件名: src/web/web_server.cc +#include "web_server.h" +#include "spdlog/spdlog.h" + +WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, uint16_t port) + : m_monitor(monitor), m_port(port) +{ + // Crow默认会输出很多调试信息,我们可以将其日志级别调高, + // 以便只显示警告和错误,让我们的终端更干净。 + m_app.loglevel(crow::LogLevel::Warning); + + // 调用函数来设置所有的API路由 + setup_routes(); +} + +WebServer::~WebServer() { + stop(); +} + +void WebServer::start() { + if (m_thread.joinable()) { + spdlog::warn("Web server is already running."); + return; + } + + // Crow的 run() 方法是一个阻塞操作,它会一直运行直到被停止。 + // 因此,我们必须在一个独立的线程中启动它,否则它会卡住我们的主程序。 + m_thread = std::thread([this]() { + spdlog::info("Starting Web server on port {}", m_port); + m_app.port(m_port).run(); + spdlog::info("Web server has stopped."); + }); +} + +void WebServer::stop() { + // a. 停止Crow应用 + m_app.stop(); + + // b. 等待线程执行完毕 + if (m_thread.joinable()) { + m_thread.join(); + } +} + +void WebServer::setup_routes() { + // ---------------------------------------------------------------- + // 定义第一个API路由: GET /api/system/status + // ---------------------------------------------------------------- + CROW_ROUTE(m_app, "/api/system/status") + ([this] { + auto cpu_util = m_monitor.getCpuUtilization(); + auto mem_info = m_monitor.getMemoryInfo(); + + crow::json::wvalue response; + response["cpu_usage_percentage"] = cpu_util.totalUsagePercentage; + response["memory_total_kb"] = mem_info.total_kb; + response["memory_free_kb"] = mem_info.available_kb; + response["memory_usage_percentage"] = (mem_info.total_kb > 0) + ? (1.0 - static_cast(mem_info.available_kb) / mem_info.total_kb) * 100.0 + : 0.0; + + + return response; + }); + + // 未来在这里添加更多的路由,例如: + // CROW_ROUTE(m_app, "/api/devices")([this]{ ... }); +} \ No newline at end of file diff --git a/src/web/web_server.h b/src/web/web_server.h new file mode 100644 index 0000000..0e02787 --- /dev/null +++ b/src/web/web_server.h @@ -0,0 +1,56 @@ +// 文件名: src/web/web_server.h +#ifndef WEB_SERVER_H +#define WEB_SERVER_H + +#include "crow.h" // 引入 Crow 库的头文件 +#include "systemMonitor/system_monitor.h" +#include + +/** + * @brief Web服务器模块 + * * 负责启动一个后台HTTP服务器,为前端提供RESTful API接口。 + * 它在自己的线程中运行,以避免阻塞主程序的io_context事件循环。 + */ +class WebServer { +public: + /** + * @brief 构造函数 + * @param monitor SystemMonitor的引用,用于获取系统状态数据 + * @param port Web服务器监听的端口号,默认为8080 + */ + WebServer(SystemMonitor::SystemMonitor& monitor, uint16_t port = 8080); + + /** + * @brief 析构函数 + * * 确保在程序退出时,Web服务线程能够被安全地停止和清理。 + */ + ~WebServer(); + + // 禁止拷贝和赋值,因为该类管理着一个线程资源 + WebServer(const WebServer&) = delete; + WebServer& operator=(const WebServer&) = delete; + + /** + * @brief 在后台线程中启动Web服务 + */ + void start(); + + /** + * @brief 停止Web服务并等待线程退出 + */ + void stop(); + +private: + /** + * @brief 设置所有的API路由(URL路径) + */ + void setup_routes(); + + crow::SimpleApp m_app; // Crow 应用实例 + SystemMonitor::SystemMonitor& m_monitor; + uint16_t m_port; + + std::thread m_thread; // 运行Web服务的后台线程 +}; + +#endif // WEB_SERVER_H \ No newline at end of file