完善webserver功能,以及modbus功能,增加modbus设备状态查询模块
This commit is contained in:
parent
af853a3146
commit
3f4f662951
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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 线程的主循环函数
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
// ==========================================================
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue