完善webserver功能,以及modbus功能,增加modbus设备状态查询模块

This commit is contained in:
GuanYuankai 2025-10-16 03:07:24 +00:00
parent af853a3146
commit 3f4f662951
8 changed files with 191 additions and 50 deletions

View File

@ -33,6 +33,7 @@ DeviceManager::~DeviceManager() {
}
void DeviceManager::load_and_start(const std::string& config_path) {
std::lock_guard<std::mutex> 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<std::mutex> 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<DeviceInfo> DeviceManager::get_all_device_info() const {
// 使用 lock_guard 确保在函数返回前互斥锁能被自动释放
std::lock_guard<std::mutex> lock(m_mutex);
std::vector<DeviceInfo> 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;
}

View File

@ -9,6 +9,21 @@
#include <vector>
#include <memory>
#include <string>
#include <mutex>
/**
* @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<std::string, std::string> 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<DeviceInfo> get_all_device_info() const;
/**
* @brief
@ -42,6 +62,10 @@ private:
// 用于存储正在运行的服务实例,以管理其生命周期
std::vector<std::unique_ptr<ModbusRtuPollerService>> m_rtu_services;
std::vector<std::shared_ptr<ModbusMasterPoller>> m_tcp_pollers;
// --- <<< 新增成员 >>> ---
// mutable 关键字允许在 const 成员函数中修改它 (例如在 get_all_device_info 中加锁)
mutable std::mutex m_mutex;
};
#endif // DEVICE_MANAGER_H

View File

@ -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();

View File

@ -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<uint8_t> m_write_buffer;
std::array<uint8_t, 260> m_read_buffer;
uint16_t m_transaction_id = 0;
std::atomic<bool> m_is_running{false};
};
#endif // MODBUS_MASTER_POLLER_H

View File

@ -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()) {

View File

@ -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 线

View File

@ -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::CORSHandler>()
: crow::Crow<crow::CORSHandler>(), m_monitor(monitor),m_device_manager(deviceManager), m_port(port)
{
// Crow默认会输出很多调试信息我们可以将其日志级别调高
// 以便只显示警告和错误,让我们的终端更干净。
m_app.loglevel(crow::LogLevel::Warning);
// ================= [ 新增 CORS 配置 ] =================
// 获取 CORS 中间件的引用
auto& cors = this->get_middleware<crow::CORSHandler>();
// 调用函数来设置所有的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<crow::CORSHandler>::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<double>(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<crow::json::wvalue> 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);
});
// ==========================================================
}

View File

@ -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 <thread>
/**
* @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<crow::CORSHandler> {
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