diff --git a/config/devices.json b/config/devices.json index 6247752..38bb508 100644 --- a/config/devices.json +++ b/config/devices.json @@ -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} ] } ] diff --git a/src/device_manager.cc b/src/device_manager.cc index 2561277..b50c3b9 100644 --- a/src/device_manager.cc +++ b/src/device_manager.cc @@ -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(); config.ip_address = dev_json.at("ip_address").get(); config.port = dev_json.at("port").get(); config.slave_id = dev_json.at("slave_id").get(); config.poll_interval_ms = dev_json.at("poll_interval_ms").get(); - // 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(), + (uint16_t)dp_json.at("address").get(), + string_to_modbus_data_type(dp_json.at("type").get()), + dp_json.value("scale", 1.0) + }); + } auto poller = std::make_shared(m_io_context, config, m_report_callback); poller->start(); diff --git a/src/modbus/modbus_common.h b/src/modbus/modbus_common.h index 29ee0dd..36b2e40 100644 --- a/src/modbus/modbus_common.h +++ b/src/modbus/modbus_common.h @@ -33,4 +33,17 @@ struct ModbusRtuDeviceConfig { std::vector 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 data_points; +}; +// +// ========================================================== + #endif // MODBUS_COMMON_H \ No newline at end of file diff --git a/src/modbus/modbus_master_poller.cc b/src/modbus/modbus_master_poller.cc index ec3778f..292b834 100644 --- a/src/modbus/modbus_master_poller.cc +++ b/src/modbus/modbus_master_poller.cc @@ -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 #include 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(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 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 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::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(); diff --git a/src/modbus/modbus_master_poller.h b/src/modbus/modbus_master_poller.h index ed9f5af..8478373 100644 --- a/src/modbus/modbus_master_poller.h +++ b/src/modbus/modbus_master_poller.h @@ -3,28 +3,19 @@ #define MODBUS_MASTER_POLLER_H #include "protocol/iprotocol_adapter.h" // For UnifiedData and ReportDataCallback +#include "modbus_common.h" // <<< MODIFIED: 包含通用配置定义 #include #include #include - -// 配置一个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 class ModbusMasterPoller : public std::enable_shared_from_this { 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 m_write_buffer; - std::array m_read_buffer; // Max Modbus TCP frame size + std::array m_read_buffer; uint16_t m_transaction_id = 0; };