From 3f4f662951da6f40cae7ac2d3aba40941f2ad5f3 Mon Sep 17 00:00:00 2001 From: GuanYuankai Date: Thu, 16 Oct 2025 03:07:24 +0000 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84webserver=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E4=BB=A5=E5=8F=8Amodbus=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0modbus=E8=AE=BE=E5=A4=87=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/deviceManager/device_manager.cc | 45 ++++++++++++++++ src/deviceManager/device_manager.h | 24 +++++++++ src/modbus/modbus_master_poller.cc | 37 ++++++++++++++ src/modbus/modbus_master_poller.h | 5 ++ src/modbus/modbus_rtu_poller_service.cc | 7 +++ src/modbus/modbus_rtu_poller_service.h | 11 ++++ src/web/web_server.cc | 68 ++++++++++++++++++------- src/web/web_server.h | 44 +++++----------- 8 files changed, 191 insertions(+), 50 deletions(-) diff --git a/src/deviceManager/device_manager.cc b/src/deviceManager/device_manager.cc index 8532eed..49a0a32 100644 --- a/src/deviceManager/device_manager.cc +++ b/src/deviceManager/device_manager.cc @@ -33,6 +33,7 @@ DeviceManager::~DeviceManager() { } void DeviceManager::load_and_start(const std::string& config_path) { + std::lock_guard lock(m_mutex); spdlog::info("Loading device configuration from '{}'...", config_path); std::ifstream config_file(config_path); if (!config_file.is_open()) { @@ -107,6 +108,8 @@ void DeviceManager::load_and_start(const std::string& config_path) { } void DeviceManager::stop_all() { + // --- <<< 添加锁 >>> --- + std::lock_guard lock(m_mutex); spdlog::info("Stopping all device services..."); for (auto& service : m_rtu_services) { service->stop(); @@ -115,4 +118,46 @@ void DeviceManager::stop_all() { // If they have explicit stop logic, call it here. m_tcp_pollers.clear(); spdlog::info("All device services stopped."); +} + +std::vector DeviceManager::get_all_device_info() const { + // 使用 lock_guard 确保在函数返回前互斥锁能被自动释放 + std::lock_guard lock(m_mutex); + + std::vector all_devices; + all_devices.reserve(m_rtu_services.size() + m_tcp_pollers.size()); + + // 遍历 RTU 设备 + for (const auto& service : m_rtu_services) { + // !! 前提: ModbusRtuPollerService 必须有 get_config() 方法 + const auto& config = service->get_config(); + + DeviceInfo info; + info.id = config.device_id; + info.type = "ModbusRTU"; + info.is_running = service->is_running(); // 假设 service 有 is_running() 方法 + info.connection_details["Port Path"] = config.port_path; + info.connection_details["Baud Rate"] = std::to_string(config.baud_rate); + info.connection_details["Slave ID"] = std::to_string(config.slave_id); + + all_devices.push_back(info); + } + + // 遍历 TCP 设备 + for (const auto& poller : m_tcp_pollers) { + // !! 前提: ModbusMasterPoller 必须有 get_config() 方法 + const auto& config = poller->get_config(); + + DeviceInfo info; + info.id = config.device_id; + info.type = "ModbusTCP"; + info.is_running = poller->is_running(); // 假设 poller 有 is_running() 方法 + info.connection_details["IP Address"] = config.ip_address; + info.connection_details["Port"] = std::to_string(config.port); + info.connection_details["Slave ID"] = std::to_string(config.slave_id); + + all_devices.push_back(info); + } + + return all_devices; } \ No newline at end of file diff --git a/src/deviceManager/device_manager.h b/src/deviceManager/device_manager.h index e1effb3..b678617 100644 --- a/src/deviceManager/device_manager.h +++ b/src/deviceManager/device_manager.h @@ -9,6 +9,21 @@ #include #include #include +#include + +/** + * @brief 用于向API层传递设备信息的统一结构体 + */ +struct DeviceInfo { + std::string id; + std::string type; // 例如 "ModbusRTU", "ModbusTCP" + bool is_running; + + // 使用 map 存储连接相关的详细信息,增加灵活性 + // 例如: {"IP Address": "192.168.1.10", "Port": "502"} + // 或 {"Port Path": "/dev/ttyS0", "Baud Rate": "9600"} + std::map connection_details; +}; /** * @brief 设备管理模块 @@ -29,6 +44,11 @@ public: * @param config_path JSON配置文件的路径 */ void load_and_start(const std::string& config_path); + /** + * @brief 获取所有当前管理的设备的信息 + * @return 包含所有设备信息的vector + */ + std::vector get_all_device_info() const; /** * @brief 安全地停止所有正在运行的设备服务 @@ -42,6 +62,10 @@ private: // 用于存储正在运行的服务实例,以管理其生命周期 std::vector> m_rtu_services; std::vector> m_tcp_pollers; + + // --- <<< 新增成员 >>> --- + // mutable 关键字允许在 const 成员函数中修改它 (例如在 get_all_device_info 中加锁) + mutable std::mutex m_mutex; }; #endif // DEVICE_MANAGER_H \ No newline at end of file diff --git a/src/modbus/modbus_master_poller.cc b/src/modbus/modbus_master_poller.cc index 292b834..cb21f41 100644 --- a/src/modbus/modbus_master_poller.cc +++ b/src/modbus/modbus_master_poller.cc @@ -17,6 +17,12 @@ ModbusMasterPoller::ModbusMasterPoller(boost::asio::io_context& io_context, m_config(std::move(config)), m_report_callback(std::move(report_cb)) {} +ModbusMasterPoller::~ModbusMasterPoller() { + if (is_running()) { + stop(); + } +} + void ModbusMasterPoller::start() { if (m_config.data_points.empty()) { spdlog::warn("[Modbus TCP] Device '{}' has no data points configured. Poller will not start.", m_config.device_id); @@ -25,6 +31,37 @@ void ModbusMasterPoller::start() { do_connect(); } +// --- <<< 新增 stop 方法实现 >>> --- +void ModbusMasterPoller::stop() { + if (!m_is_running.exchange(false)) { + // 如果之前已经是 false,说明已经停止了,直接返回 + return; + } + + // 使用 post 确保取消操作在 io_context 的事件循环中执行,保证线程安全 + boost::asio::post(m_io_context, [this, self = shared_from_this()](){ + spdlog::info("[Modbus TCP] Stopping poller for device '{}'...", m_config.device_id); + + // 取消所有挂起的异步操作 + m_timer.cancel(); + + // 安全地关闭 socket + if (m_socket.is_open()) { + boost::system::error_code ec; + m_socket.shutdown(tcp::socket::shutdown_both, ec); + m_socket.close(ec); + } + }); +} + +// --- <<< 新增 get_config 和 is_running 实现 >>> --- +const ModbusTcpDeviceConfig& ModbusMasterPoller::get_config() const { + return m_config; +} + +bool ModbusMasterPoller::is_running() const { + return m_is_running; +} void ModbusMasterPoller::do_connect() { auto self = shared_from_this(); diff --git a/src/modbus/modbus_master_poller.h b/src/modbus/modbus_master_poller.h index c19b57f..f0a89b4 100644 --- a/src/modbus/modbus_master_poller.h +++ b/src/modbus/modbus_master_poller.h @@ -17,6 +17,9 @@ public: ReportDataCallback report_cb); void start(); + void stop(); + const ModbusTcpDeviceConfig& get_config() const; + bool is_running() const; private: void do_connect(); @@ -37,6 +40,8 @@ private: std::vector m_write_buffer; std::array m_read_buffer; uint16_t m_transaction_id = 0; + + std::atomic m_is_running{false}; }; #endif // MODBUS_MASTER_POLLER_H \ No newline at end of file diff --git a/src/modbus/modbus_rtu_poller_service.cc b/src/modbus/modbus_rtu_poller_service.cc index abc5398..0f673bc 100644 --- a/src/modbus/modbus_rtu_poller_service.cc +++ b/src/modbus/modbus_rtu_poller_service.cc @@ -33,6 +33,13 @@ void ModbusRtuPollerService::stop() { } } +const ModbusRtuDeviceConfig& ModbusRtuPollerService::get_config() const { + return m_config; +} +bool ModbusRtuPollerService::is_running() const { + return !m_stop_flag && m_thread.joinable(); +} + void ModbusRtuPollerService::run() { // 检查是否有数据点被配置 if (m_config.data_points.empty()) { diff --git a/src/modbus/modbus_rtu_poller_service.h b/src/modbus/modbus_rtu_poller_service.h index 5b83613..2eca36f 100644 --- a/src/modbus/modbus_rtu_poller_service.h +++ b/src/modbus/modbus_rtu_poller_service.h @@ -46,6 +46,17 @@ public: */ void stop(); + // --- <<< 新增方法 >>> --- + /** + * @brief 获取设备的配置信息 (const-ref to avoid copy) + */ + const ModbusRtuDeviceConfig& get_config() const; + + /** + * @brief 检查轮询服务是否正在运行 + */ + bool is_running() const; + // --- <<< END >>> --- private: /** * @brief 线程的主循环函数 diff --git a/src/web/web_server.cc b/src/web/web_server.cc index 2748ee8..a1851db 100644 --- a/src/web/web_server.cc +++ b/src/web/web_server.cc @@ -2,14 +2,24 @@ #include "web_server.h" #include "spdlog/spdlog.h" -WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, uint16_t port) - : m_monitor(monitor), m_port(port) +// 构造函数现在需要调用基类的构造函数 +WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager, uint16_t port) + // 注意基类已经改为 crow::Crow() + : crow::Crow(), m_monitor(monitor),m_device_manager(deviceManager), m_port(port) { - // Crow默认会输出很多调试信息,我们可以将其日志级别调高, - // 以便只显示警告和错误,让我们的终端更干净。 - m_app.loglevel(crow::LogLevel::Warning); + // ================= [ 新增 CORS 配置 ] ================= + // 获取 CORS 中间件的引用 + auto& cors = this->get_middleware(); - // 调用函数来设置所有的API路由 + cors + .global() + .origin("*") + .headers("Content-Type", "Authorization") + .methods("GET"_method, "POST"_method, "OPTIONS"_method); + // ========================================================== + + // 设置日志级别 + this->loglevel(crow::LogLevel::Warning); setup_routes(); } @@ -22,29 +32,24 @@ void WebServer::start() { 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(); + this->port(m_port).run(); spdlog::info("Web server has stopped."); }); } void WebServer::stop() { - m_app.stop(); - + // 您的 Bug 修正非常正确,这里保持不变 + crow::Crow::stop(); if (m_thread.joinable()) { m_thread.join(); } } void WebServer::setup_routes() { - // ---------------------------------------------------------------- - // 定义第一个API路由: GET /api/system/status - // ---------------------------------------------------------------- - CROW_ROUTE(m_app, "/api/system/status") + // ================= [ 系统状态获取 API ] ================= + CROW_ROUTE((*this), "/api/system/status").methods("GET"_method) ([this] { auto cpu_util = m_monitor.getCpuUtilization(); auto mem_info = m_monitor.getMemoryInfo(); @@ -57,10 +62,35 @@ void WebServer::setup_routes() { ? (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]{ ... }); + // ================= [ 新增设备列表 API ] ================= + CROW_ROUTE((*this), "/api/devices").methods("GET"_method) + ([this] { + // 这一行不变,从 DeviceManager 获取最新的设备信息 + auto devices_info = m_device_manager.get_all_device_info(); + + std::vector devices_json; + for (const auto& info : devices_info) { + crow::json::wvalue device_obj; + + // 1. 直接映射的字段 + device_obj["id"] = info.id; + device_obj["type"] = info.type; + device_obj["is_running"] = info.is_running; + + // 2. 将 std::map 转换为一个内嵌的 JSON 对象 + 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); + }); + // ========================================================== } \ No newline at end of file diff --git a/src/web/web_server.h b/src/web/web_server.h index 0e02787..4b786cb 100644 --- a/src/web/web_server.h +++ b/src/web/web_server.h @@ -2,55 +2,37 @@ #ifndef WEB_SERVER_H #define WEB_SERVER_H -#include "crow.h" // 引入 Crow 库的头文件 +// 包含 Crow 核心文件和官方 CORS 中间件头文件 +#include "crow.h" +#include "crow/middlewares/cors.h" + #include "systemMonitor/system_monitor.h" +#include "deviceManager/device_manager.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 的基类模板参数 改为 crow::CORSHandler +class WebServer : public crow::Crow { + +public: + WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager, uint16_t port = 8080); ~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; + DeviceManager& m_device_manager; uint16_t m_port; - std::thread m_thread; // 运行Web服务的后台线程 + std::thread m_thread; }; #endif // WEB_SERVER_H \ No newline at end of file