完善modbus-tcp的配置方法
This commit is contained in:
parent
3aaec6a42c
commit
9890d33e0b
|
|
@ -44,8 +44,9 @@
|
|||
"slave_id": 1,
|
||||
"poll_interval_ms": 1000,
|
||||
"data_points": [
|
||||
{"name": "motor_speed", "address": 100, "type": "UINT16"},
|
||||
{"name": "pressure", "address": 102, "type": "FLOAT32", "scale": 0.01}
|
||||
{"name": "motor_speed", "address": 100, "type": "UINT16", "scale": 1.0},
|
||||
{"name": "pressure", "address": 102, "type": "FLOAT32", "scale": 0.01},
|
||||
{"name": "valve_status","address": 104, "type": "UINT16", "scale": 1.0}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -76,18 +76,22 @@ void DeviceManager::load_and_start(const std::string& config_path) {
|
|||
for (const auto& dev_json : config_json["modbus_tcp_devices"]) {
|
||||
if (!dev_json.value("enabled", false)) continue;
|
||||
|
||||
ModbusDeviceConfig config; // This is the TCP config struct
|
||||
ModbusTcpDeviceConfig config;
|
||||
config.device_id = dev_json.at("device_id").get<std::string>();
|
||||
config.ip_address = dev_json.at("ip_address").get<std::string>();
|
||||
config.port = dev_json.at("port").get<uint16_t>();
|
||||
config.slave_id = dev_json.at("slave_id").get<uint8_t>();
|
||||
config.poll_interval_ms = dev_json.at("poll_interval_ms").get<int>();
|
||||
|
||||
// Note: The current ModbusMasterPoller doesn't use data_points yet.
|
||||
// This is a point for future enhancement to make it also configuration-driven.
|
||||
// For now, we just start it based on connection params.
|
||||
// You would need to refactor ModbusMasterPoller similarly to ModbusRtuPollerService
|
||||
// to make it fully generic.
|
||||
// 解析 data_points
|
||||
for (const auto& dp_json : dev_json.at("data_points")) {
|
||||
config.data_points.push_back({
|
||||
dp_json.at("name").get<std::string>(),
|
||||
(uint16_t)dp_json.at("address").get<int>(),
|
||||
string_to_modbus_data_type(dp_json.at("type").get<std::string>()),
|
||||
dp_json.value("scale", 1.0)
|
||||
});
|
||||
}
|
||||
|
||||
auto poller = std::make_shared<ModbusMasterPoller>(m_io_context, config, m_report_callback);
|
||||
poller->start();
|
||||
|
|
|
|||
|
|
@ -33,4 +33,17 @@ struct ModbusRtuDeviceConfig {
|
|||
std::vector<DataPointConfig> data_points; // 该设备上所有需要采集的数据点
|
||||
};
|
||||
|
||||
// ==================== [ 2025.10.14新增 ] ===================
|
||||
// vvv 描述一个完整的Modbus TCP设备,包含多个采集点 vvv
|
||||
struct ModbusTcpDeviceConfig {
|
||||
std::string device_id;
|
||||
std::string ip_address;
|
||||
uint16_t port;
|
||||
uint8_t slave_id;
|
||||
int poll_interval_ms;
|
||||
std::vector<DataPointConfig> data_points;
|
||||
};
|
||||
//
|
||||
// ==========================================================
|
||||
|
||||
#endif // MODBUS_COMMON_H
|
||||
|
|
@ -2,13 +2,14 @@
|
|||
#include "modbus_master_poller.h"
|
||||
#include "protocol/modbus/modbus_protocol.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "generic_modbus_parser.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <chrono>
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
ModbusMasterPoller::ModbusMasterPoller(boost::asio::io_context& io_context,
|
||||
ModbusDeviceConfig config,
|
||||
ModbusTcpDeviceConfig config,
|
||||
ReportDataCallback report_cb)
|
||||
: m_io_context(io_context),
|
||||
m_socket(io_context),
|
||||
|
|
@ -17,9 +18,14 @@ ModbusMasterPoller::ModbusMasterPoller(boost::asio::io_context& io_context,
|
|||
m_report_callback(std::move(report_cb)) {}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
do_connect();
|
||||
}
|
||||
|
||||
|
||||
void ModbusMasterPoller::do_connect() {
|
||||
auto self = shared_from_this();
|
||||
tcp::resolver resolver(m_io_context);
|
||||
|
|
@ -57,13 +63,25 @@ void ModbusMasterPoller::on_error(const std::string& stage) {
|
|||
do_connect();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// modifier: 2025.10.14 重写do_poll
|
||||
void ModbusMasterPoller::do_poll() {
|
||||
// 1. 创建PDU
|
||||
auto pdu = modbus::protocol::create_read_holding_registers_pdu(m_config.start_address, m_config.quantity);
|
||||
// 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;
|
||||
|
||||
if (max_it->type == ModbusDataType::UINT32 || max_it->type == ModbusDataType::INT32 || max_it->type == ModbusDataType::FLOAT32) {
|
||||
last_address += 1;
|
||||
}
|
||||
uint16_t quantity = last_address - start_address + 1;
|
||||
|
||||
// 2. 创建PDU
|
||||
auto pdu = modbus::protocol::create_read_holding_registers_pdu(start_address, quantity);
|
||||
|
||||
// 2. 构建MBAP头
|
||||
// 3. 构建MBAP头
|
||||
m_write_buffer.clear();
|
||||
m_write_buffer.resize(7 + pdu.size());
|
||||
|
||||
|
|
@ -78,10 +96,10 @@ void ModbusMasterPoller::do_poll() {
|
|||
m_write_buffer[5] = static_cast<uint8_t>(length & 0xFF);
|
||||
m_write_buffer[6] = m_config.slave_id;
|
||||
|
||||
// 3. 附加PDU
|
||||
// 4. 附加PDU
|
||||
std::copy(pdu.begin(), pdu.end(), m_write_buffer.begin() + 7);
|
||||
|
||||
// 4. 发送请求
|
||||
// 5. 发送请求
|
||||
do_write();
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +116,7 @@ void ModbusMasterPoller::do_write() {
|
|||
});
|
||||
}
|
||||
|
||||
// do_read_header() 稍作修改,传递 start_address
|
||||
void ModbusMasterPoller::do_read_header() {
|
||||
auto self = shared_from_this();
|
||||
boost::asio::async_read(m_socket, boost::asio::buffer(m_read_buffer, 7),
|
||||
|
|
@ -114,14 +133,22 @@ void ModbusMasterPoller::do_read_header() {
|
|||
return;
|
||||
}
|
||||
uint16_t pdu_len = (m_read_buffer[4] << 8) | m_read_buffer[5];
|
||||
do_read_pdu(pdu_len - 1); // Length in header includes UnitID
|
||||
|
||||
// <<< MODIFIED: 为了构建地址->值的映射,我们需要知道这次轮询的起始地址
|
||||
// (这段代码可以优化,比如将 poll 请求的信息暂存起来,但为了简单直观,我们重新计算)
|
||||
auto min_it = std::min_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;
|
||||
|
||||
do_read_pdu(pdu_len - 1, start_address); // Length in header includes UnitID
|
||||
});
|
||||
}
|
||||
|
||||
void ModbusMasterPoller::do_read_pdu(std::size_t pdu_length) {
|
||||
// <<< MODIFIED: do_read_pdu() 方法被完全重写
|
||||
void ModbusMasterPoller::do_read_pdu(std::size_t pdu_length, uint16_t poll_start_addr) {
|
||||
auto self = shared_from_this();
|
||||
boost::asio::async_read(m_socket, boost::asio::buffer(m_read_buffer.data() + 7, pdu_length),
|
||||
[this, self, pdu_length](const boost::system::error_code& ec, std::size_t /*length*/) {
|
||||
[this, self, poll_start_addr, pdu_length](const boost::system::error_code& ec, std::size_t /*length*/) {
|
||||
if (ec) {
|
||||
spdlog::error("[{}] Read PDU failed: {}", m_config.device_id, ec.message());
|
||||
on_error("read_pdu");
|
||||
|
|
@ -129,22 +156,26 @@ void ModbusMasterPoller::do_read_pdu(std::size_t pdu_length) {
|
|||
}
|
||||
|
||||
try {
|
||||
// 解析并上报数据
|
||||
// 1. 解析PDU,获取原始寄存器值
|
||||
std::vector<uint8_t> pdu_data(m_read_buffer.data() + 7, m_read_buffer.data() + 7 + pdu_length);
|
||||
auto registers = modbus::protocol::parse_read_holding_registers_response(pdu_data);
|
||||
|
||||
// 将数据转换为JSON
|
||||
nlohmann::json data_j;
|
||||
for(size_t i = 0; i < registers.size(); ++i) {
|
||||
data_j[std::to_string(m_config.start_address + i)] = registers[i];
|
||||
// 2. 将原始数组转换为 (地址 -> 值) 的映射
|
||||
std::map<uint16_t, uint16_t> registers_map;
|
||||
for (uint16_t i = 0; i < registers.size(); ++i) {
|
||||
registers_map[poll_start_addr + i] = registers[i];
|
||||
}
|
||||
|
||||
// 3. 使用通用解析器将映射转换为JSON
|
||||
nlohmann::json data_j = GenericModbusParser::parse(registers_map, m_config.data_points);
|
||||
|
||||
// 4. 封装并上报 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) {
|
||||
if (m_report_callback) {
|
||||
m_report_callback(report_data);
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +183,7 @@ void ModbusMasterPoller::do_read_pdu(std::size_t pdu_length) {
|
|||
spdlog::error("[{}] Failed to parse Modbus response: {}", m_config.device_id, e.what());
|
||||
}
|
||||
|
||||
// 安排下一次轮询
|
||||
// 5. 安排下一次轮询
|
||||
m_timer.expires_after(std::chrono::milliseconds(m_config.poll_interval_ms));
|
||||
m_timer.async_wait([this, self](const boost::system::error_code& /*ec*/) {
|
||||
do_poll();
|
||||
|
|
|
|||
|
|
@ -3,28 +3,19 @@
|
|||
#define MODBUS_MASTER_POLLER_H
|
||||
|
||||
#include "protocol/iprotocol_adapter.h" // For UnifiedData and ReportDataCallback
|
||||
#include "modbus_common.h" // <<< MODIFIED: 包含通用配置定义
|
||||
#include <boost/asio.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// 配置一个Modbus设备的连接和轮询参数
|
||||
struct ModbusDeviceConfig {
|
||||
std::string device_id; // 自定义设备ID,用于MQTT主题
|
||||
std::string ip_address;
|
||||
uint16_t port;
|
||||
uint8_t slave_id;
|
||||
uint16_t start_address;
|
||||
uint16_t quantity;
|
||||
int poll_interval_ms;
|
||||
};
|
||||
#include <vector>
|
||||
|
||||
class ModbusMasterPoller : public std::enable_shared_from_this<ModbusMasterPoller> {
|
||||
public:
|
||||
// <<< MODIFIED: 构造函数接收新的配置结构体
|
||||
ModbusMasterPoller(boost::asio::io_context& io_context,
|
||||
ModbusDeviceConfig config,
|
||||
ModbusTcpDeviceConfig config,
|
||||
ReportDataCallback report_cb);
|
||||
|
||||
// 开始轮询器的工作 (连接和定时)
|
||||
void start();
|
||||
|
||||
private:
|
||||
|
|
@ -32,18 +23,19 @@ private:
|
|||
void do_poll();
|
||||
void do_write();
|
||||
void do_read_header();
|
||||
void do_read_pdu(std::size_t pdu_length);
|
||||
void do_read_pdu(std::size_t pdu_length, uint16_t poll_start_addr); // <<< MODIFIED: 需要知道轮询的起始地址
|
||||
void on_error(const std::string& stage);
|
||||
|
||||
boost::asio::io_context& m_io_context;
|
||||
boost::asio::ip::tcp::socket m_socket;
|
||||
boost::asio::steady_timer m_timer;
|
||||
|
||||
ModbusDeviceConfig m_config;
|
||||
// <<< MODIFIED: 成员变量使用新的配置结构体
|
||||
ModbusTcpDeviceConfig m_config;
|
||||
ReportDataCallback m_report_callback;
|
||||
|
||||
std::vector<uint8_t> m_write_buffer;
|
||||
std::array<uint8_t, 260> m_read_buffer; // Max Modbus TCP frame size
|
||||
std::array<uint8_t, 260> m_read_buffer;
|
||||
uint16_t m_transaction_id = 0;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue