2025-10-14 16:06:43 +08:00
|
|
|
|
// 文件名: src/modbus/modbus_rtu_poller_service.cc
|
|
|
|
|
|
#include "modbus_rtu_poller_service.h"
|
|
|
|
|
|
#include "generic_modbus_parser.h" // 使用通用解析器
|
|
|
|
|
|
#include "spdlog/spdlog.h"
|
|
|
|
|
|
#include <chrono>
|
|
|
|
|
|
#include <algorithm> // for std::minmax_element
|
|
|
|
|
|
|
|
|
|
|
|
ModbusRtuPollerService::ModbusRtuPollerService(ModbusRtuDeviceConfig config, ReportDataCallback report_cb)
|
|
|
|
|
|
: m_config(std::move(config)),
|
|
|
|
|
|
m_report_callback(std::move(report_cb)) {}
|
|
|
|
|
|
|
|
|
|
|
|
ModbusRtuPollerService::~ModbusRtuPollerService() {
|
|
|
|
|
|
stop();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ModbusRtuPollerService::start() {
|
|
|
|
|
|
if (m_thread.joinable()) {
|
|
|
|
|
|
spdlog::warn("[Modbus RTU Service] Poller for device '{}' is already running.", m_config.device_id);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
m_stop_flag = false;
|
|
|
|
|
|
// 启动一个新线程,并将 run() 方法作为入口点
|
|
|
|
|
|
// 'this' 指针被传递,以便新线程可以调用类的成员函数
|
|
|
|
|
|
m_thread = std::thread(&ModbusRtuPollerService::run, this);
|
|
|
|
|
|
spdlog::info("[Modbus RTU Service] Poller for device '{}' started in a background thread.", m_config.device_id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ModbusRtuPollerService::stop() {
|
|
|
|
|
|
m_stop_flag = true;
|
|
|
|
|
|
if (m_thread.joinable()) {
|
|
|
|
|
|
m_thread.join(); // 等待线程安全退出
|
|
|
|
|
|
spdlog::info("[Modbus RTU Service] Poller for device '{}' has been stopped.", m_config.device_id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 11:07:24 +08:00
|
|
|
|
const ModbusRtuDeviceConfig& ModbusRtuPollerService::get_config() const {
|
|
|
|
|
|
return m_config;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool ModbusRtuPollerService::is_running() const {
|
|
|
|
|
|
return !m_stop_flag && m_thread.joinable();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 16:06:43 +08:00
|
|
|
|
void ModbusRtuPollerService::run() {
|
|
|
|
|
|
// 检查是否有数据点被配置
|
|
|
|
|
|
if (m_config.data_points.empty()) {
|
|
|
|
|
|
spdlog::warn("[Modbus RTU] Device '{}' has no data points configured. Thread will not run.", m_config.device_id);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置并打开串口
|
|
|
|
|
|
if (!m_client.setPortSettings(m_config.port_path, m_config.baud_rate)) {
|
|
|
|
|
|
spdlog::error("[Modbus RTU] Failed to set up serial port '{}' for device '{}'. Thread exiting.", m_config.port_path, m_config.device_id);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 为了提高效率,我们计算出需要一次性读取的连续寄存器范围
|
|
|
|
|
|
auto [min_it, max_it] = std::minmax_element(m_config.data_points.begin(), m_config.data_points.end(),
|
|
|
|
|
|
[](const DataPointConfig& a, const DataPointConfig& b) {
|
|
|
|
|
|
return a.address < b.address;
|
|
|
|
|
|
});
|
|
|
|
|
|
uint16_t start_address = min_it->address;
|
|
|
|
|
|
uint16_t last_address = max_it->address;
|
|
|
|
|
|
|
|
|
|
|
|
// 考虑多寄存器数据类型(如FLOAT32)会占用额外的寄存器
|
|
|
|
|
|
if (max_it->type == ModbusDataType::UINT32 || max_it->type == ModbusDataType::INT32 || max_it->type == ModbusDataType::FLOAT32) {
|
|
|
|
|
|
last_address += 1; // 32位数据占用2个16位寄存器
|
|
|
|
|
|
}
|
|
|
|
|
|
uint16_t quantity = last_address - start_address + 1;
|
|
|
|
|
|
|
|
|
|
|
|
spdlog::info("[Modbus RTU] Device '{}' will poll {} registers starting from address {}.", m_config.device_id, quantity, start_address);
|
|
|
|
|
|
|
|
|
|
|
|
// 线程主循环
|
|
|
|
|
|
while (!m_stop_flag) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 2. 一次性读取所有需要的连续寄存器
|
|
|
|
|
|
std::vector<uint16_t> raw_registers = m_client.readHoldingRegisters(m_config.slave_id, start_address, quantity);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 将返回的寄存器数组转换为 (地址 -> 值) 的映射,方便解析器按地址查找
|
|
|
|
|
|
std::map<uint16_t, uint16_t> registers_map;
|
|
|
|
|
|
for (uint16_t i = 0; i < raw_registers.size(); ++i) {
|
|
|
|
|
|
registers_map[start_address + i] = raw_registers[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 使用通用的、配置驱动的解析器进行解析
|
|
|
|
|
|
nlohmann::json data_j = GenericModbusParser::parse(registers_map, m_config.data_points);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 封装成 UnifiedData 结构并上报
|
|
|
|
|
|
UnifiedData report_data;
|
|
|
|
|
|
report_data.device_id = m_config.device_id;
|
|
|
|
|
|
report_data.timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
|
|
|
|
report_data.data_json = data_j.dump();
|
|
|
|
|
|
|
|
|
|
|
|
if (m_report_callback) {
|
|
|
|
|
|
m_report_callback(report_data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
|
spdlog::error("[Modbus RTU] Error during communication with device '{}': {}", m_config.device_id, e.what());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在本线程中等待,不影响主线程的事件循环
|
2025-10-15 14:34:53 +08:00
|
|
|
|
auto wake_up_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(m_config.poll_interval_ms);
|
|
|
|
|
|
while (std::chrono::steady_clock::now() < wake_up_time) {
|
|
|
|
|
|
if (m_stop_flag) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 每次只“打盹”100毫秒
|
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
|
}
|
2025-10-14 16:06:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 线程退出前关闭串口
|
|
|
|
|
|
m_client.closePort();
|
2025-10-15 15:07:33 +08:00
|
|
|
|
spdlog::info("[RTU Poller {}] Run loop finished. Thread exiting.", m_config.device_id);
|
2025-10-14 16:06:43 +08:00
|
|
|
|
}
|