增加了modbus tcp的轮询主站模块

This commit is contained in:
GuanYuankai 2025-10-14 15:29:13 +08:00
parent 4a633e4f5f
commit 6d6899549b
13 changed files with 357 additions and 28 deletions

View File

@ -35,13 +35,17 @@ add_library(edge_proxy_lib STATIC
# --- ---
src/protocol/protocol.cc
src/protocol/private_protocol_adapter.cc
src/protocol/protocol_factory.cc
#
src/systemMonitor/system_monitor.cc
#modbus
src/modbus/modbus_data_parser.cc
src/modbus/modbus_temperature_humidity.cc
src/modbus/modbus_rtu_client.cc
# --- Modbus Master ---
src/protocol/modbus/modbus_protocol.cc
src/modbus/modbus_master_poller.cc
)
target_include_directories(edge_proxy_lib PUBLIC

View File

@ -3,6 +3,7 @@
#include "mqtt/mqtt_router.h"
#include "systemMonitor/system_monitor.h"
#include "spdlog/spdlog.h"
#include "modbus/modbus_master_poller.h"
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <csignal>
@ -70,7 +71,7 @@ int main(int argc, char* argv[]) {
constexpr uint16_t tcp_port = 8888;
MqttClient mqtt_client("tcp://mqtt-broker:1883", "edge-proxy-main-client");
MqttRouter mqtt_router(mqtt_client);
std::vector<uint16_t> listen_ports = { 8888, 502 };
std::vector<uint16_t> listen_ports = { 8888 };
TCPServer tcp_server(g_io_context, listen_ports, mqtt_client);
SystemMonitor::SystemMonitor monitor;
@ -81,6 +82,27 @@ int main(int argc, char* argv[]) {
boost::asio::steady_timer timer(g_io_context, std::chrono::seconds(15));
timer.async_wait(std::bind(poll_system_metrics, std::ref(timer), std::ref(monitor), std::ref(mqtt_client)));
auto report_to_mqtt = [&](const UnifiedData& data) {
std::string topic = "devices/" + data.device_id + "/data";
mqtt_client.publish(topic, data.data_json, 1, false);
};
// 配置并启动Modbus轮询器
ModbusDeviceConfig temp_sensor_config = {
.device_id = "temp_sensor_workshop1",
.ip_address = "192.168.1.120", // 您的Modbus设备的IP地址
.port = 502, // Modbus TCP标准端口
.slave_id = 1, // 设备的从站ID
.start_address = 0, // 要读取的第一个寄存器的地址
.quantity = 8, // 从起始地址开始,总共读取多少个寄存器
.poll_interval_ms = 2000 // 每2000毫秒轮询一次
};
// =========================================================================
// 创建轮询器实例 (使用std::make_shared确保生命周期)
auto poller = std::make_shared<ModbusMasterPoller>(g_io_context, temp_sensor_config, report_to_mqtt);
poller->start();
spdlog::info("All services are running. Press Ctrl+C to exit.");
g_io_context.run();

View File

@ -0,0 +1,161 @@
// 文件名: src/modbus/modbus_master_poller.cc
#include "modbus_master_poller.h"
#include "protocol/modbus/modbus_protocol.h"
#include "spdlog/spdlog.h"
#include <nlohmann/json.hpp>
#include <chrono>
using boost::asio::ip::tcp;
ModbusMasterPoller::ModbusMasterPoller(boost::asio::io_context& io_context,
ModbusDeviceConfig config,
ReportDataCallback report_cb)
: m_io_context(io_context),
m_socket(io_context),
m_timer(io_context),
m_config(std::move(config)),
m_report_callback(std::move(report_cb)) {}
void ModbusMasterPoller::start() {
do_connect();
}
void ModbusMasterPoller::do_connect() {
auto self = shared_from_this();
tcp::resolver resolver(m_io_context);
spdlog::info("[{}] Connecting to {}:{}...", m_config.device_id, m_config.ip_address, m_config.port);
resolver.async_resolve(m_config.ip_address, std::to_string(m_config.port),
[this, self](const boost::system::error_code& ec, tcp::resolver::results_type endpoints) {
if (ec) {
spdlog::error("[{}] Resolve failed: {}", m_config.device_id, ec.message());
on_error("resolve");
return;
}
boost::asio::async_connect(m_socket, endpoints,
[this, self](const boost::system::error_code& ec, const tcp::endpoint& /*endpoint*/) {
if (ec) {
spdlog::error("[{}] Connect failed: {}", m_config.device_id, ec.message());
on_error("connect");
return;
}
spdlog::info("[{}] Connection successful.", m_config.device_id);
do_poll(); // 立即开始第一次轮询
});
});
}
void ModbusMasterPoller::on_error(const std::string& stage) {
if (m_socket.is_open()) {
m_socket.close();
}
// 5秒后尝试重连
spdlog::info("[{}] Reconnecting in 5 seconds after error at stage: {}", m_config.device_id, stage);
m_timer.expires_after(std::chrono::seconds(5));
m_timer.async_wait([this, self=shared_from_this()](const boost::system::error_code& /*ec*/) {
do_connect();
});
}
void ModbusMasterPoller::do_poll() {
// 1. 创建PDU
auto pdu = modbus::protocol::create_read_holding_registers_pdu(m_config.start_address, m_config.quantity);
// 2. 构建MBAP头
m_write_buffer.clear();
m_write_buffer.resize(7 + pdu.size());
m_transaction_id++;
uint16_t length = 1 + pdu.size(); // UnitID + PDU
m_write_buffer[0] = static_cast<uint8_t>((m_transaction_id >> 8) & 0xFF);
m_write_buffer[1] = static_cast<uint8_t>(m_transaction_id & 0xFF);
m_write_buffer[2] = 0; // Protocol ID
m_write_buffer[3] = 0;
m_write_buffer[4] = static_cast<uint8_t>((length >> 8) & 0xFF);
m_write_buffer[5] = static_cast<uint8_t>(length & 0xFF);
m_write_buffer[6] = m_config.slave_id;
// 3. 附加PDU
std::copy(pdu.begin(), pdu.end(), m_write_buffer.begin() + 7);
// 4. 发送请求
do_write();
}
void ModbusMasterPoller::do_write() {
auto self = shared_from_this();
boost::asio::async_write(m_socket, boost::asio::buffer(m_write_buffer),
[this, self](const boost::system::error_code& ec, std::size_t /*length*/) {
if (ec) {
spdlog::error("[{}] Write failed: {}", m_config.device_id, ec.message());
on_error("write");
return;
}
do_read_header();
});
}
void ModbusMasterPoller::do_read_header() {
auto self = shared_from_this();
boost::asio::async_read(m_socket, boost::asio::buffer(m_read_buffer, 7),
[this, self](const boost::system::error_code& ec, std::size_t length) {
if (ec) {
spdlog::error("[{}] Read header failed: {}", m_config.device_id, ec.message());
on_error("read_header");
return;
}
uint16_t tid = (m_read_buffer[0] << 8) | m_read_buffer[1];
if (tid != m_transaction_id) {
spdlog::warn("[{}] Transaction ID mismatch! Sent {}, Rcvd {}", m_config.device_id, m_transaction_id, tid);
on_error("tid_mismatch");
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
});
}
void ModbusMasterPoller::do_read_pdu(std::size_t pdu_length) {
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*/) {
if (ec) {
spdlog::error("[{}] Read PDU failed: {}", m_config.device_id, ec.message());
on_error("read_pdu");
return;
}
try {
// 解析并上报数据
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];
}
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 modbus::protocol::exception& e) {
spdlog::error("[{}] Failed to parse Modbus response: {}", m_config.device_id, e.what());
}
// 安排下一次轮询
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();
});
});
}

View File

@ -0,0 +1,50 @@
// 文件名: src/modbus/modbus_master_poller.h
#ifndef MODBUS_MASTER_POLLER_H
#define MODBUS_MASTER_POLLER_H
#include "protocol/iprotocol_adapter.h" // For UnifiedData and ReportDataCallback
#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;
};
class ModbusMasterPoller : public std::enable_shared_from_this<ModbusMasterPoller> {
public:
ModbusMasterPoller(boost::asio::io_context& io_context,
ModbusDeviceConfig config,
ReportDataCallback report_cb);
// 开始轮询器的工作 (连接和定时)
void start();
private:
void do_connect();
void do_poll();
void do_write();
void do_read_header();
void do_read_pdu(std::size_t pdu_length);
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;
ReportDataCallback m_report_callback;
std::vector<uint8_t> m_write_buffer;
std::array<uint8_t, 260> m_read_buffer; // Max Modbus TCP frame size
uint16_t m_transaction_id = 0;
};
#endif // MODBUS_MASTER_POLLER_H

View File

@ -6,21 +6,9 @@
#include <cstdint> // For uint8_t, uint16_t
#include <stdexcept> // For std::runtime_error, std::invalid_argument
// --- Forward Declarations ---
// Forward declare ModbusRTUClient to allow ModbusRequest and ModbusResponse to be declared
// without circular includes if they needed to reference ModbusRTUClient directly.
// In this case, ModbusRequest and ModbusResponse don't directly reference ModbusRTUClient,
// but it's good practice if there were more complex interdependencies.
// --- Modbus CRC16 Calculation ---
// Declaration for CRC16 calculation function
//计算Modbus RTU帧的16位CRC校验码。
uint16_t calculateModbusCRC(const std::vector<uint8_t>& data);
// --- Modbus Frame Structures ---
// Structure for Modbus Request Parameters (specifically for building a request)
//封装一个Modbus请求的所有参数
struct ModbusRequest {
uint8_t slave_id;
uint8_t function_code;

View File

@ -1,4 +1,4 @@
#include "modbus_data_parser.h"
#include "modbus_temperature_humidity.h"
// --- Generic Data Conversion Functions ---
int16_t bytesToSignedInt16(uint16_t val) {

View File

@ -11,9 +11,6 @@
// --- Generic Data Conversion Functions ---
int16_t bytesToSignedInt16(uint16_t val); // Converts a 16-bit unsigned value to signed
// --- Specific Sensor Data Parsing Functions (e.g., for JWST-20 sensor) ---
// This map could be static const in the .cpp if it's truly internal,
// or defined here if parts of it are needed by other header files.
const std::map<uint16_t, uint32_t> BAUDRATE_MAP = {
{0, 1200}, {1, 2400}, {2, 4800}, {3, 9600},
{4, 19200}, {5, 38400}, {6, 57600}
@ -22,7 +19,6 @@ const std::map<uint16_t, uint32_t> BAUDRATE_MAP = {
// Function to print and interpret a vector of Modbus registers
void printRegisters(const std::string& description, const std::vector<uint16_t>& registers);
// You can add more specific parsing functions as needed, e.g.:
float parseTemperature(uint16_t raw_value);
float parseHumidity(uint16_t raw_value);
std::string parseAlarmStatus(uint16_t raw_value);

View File

@ -6,8 +6,8 @@
#include <memory>
#include <vector>
#include <map>
#include "iprotocol_adapter.h"
#include "mqtt_client.h"
#include "protocol/iprotocol_adapter.h"
#include "mqtt/mqtt_client.h"
using boost::asio::ip::tcp;

View File

@ -0,0 +1,72 @@
// 文件名: src/protocol/modbus/modbus_protocol.cc
#include "modbus_protocol.h"
namespace { // 匿名命名空间,辅助函数仅在本文件可见
// 将16位整数写入大端字节序的vector
void write_be16(std::vector<uint8_t>& buffer, uint16_t value) {
buffer.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
buffer.push_back(static_cast<uint8_t>(value & 0xFF));
}
// 从大端字节流读取16位整数
uint16_t read_be16(const uint8_t* p) {
return (static_cast<uint16_t>(p[0]) << 8) | p[1];
}
} // namespace
namespace modbus::protocol {
std::vector<uint8_t> create_read_holding_registers_pdu(uint16_t start_address, uint16_t quantity) {
std::vector<uint8_t> pdu;
pdu.reserve(5);
pdu.push_back(0x03); // Function Code
write_be16(pdu, start_address);
write_be16(pdu, quantity);
return pdu;
}
std::vector<uint16_t> parse_read_holding_registers_response(const std::vector<uint8_t>& response_pdu) {
if (response_pdu.empty()) {
throw exception("Response PDU is empty.");
}
const uint8_t function_code = response_pdu[0];
// 检查异常响应
if (function_code & 0x80) {
if (response_pdu.size() < 2) {
throw exception("Invalid exception response size.");
}
throw exception("Modbus exception received. FC=" + std::to_string(function_code & 0x7F) + " Code=" + std::to_string(response_pdu[1]));
}
if (function_code != 0x03) {
throw exception("Unexpected function code in response: " + std::to_string(function_code));
}
if (response_pdu.size() < 2) {
throw exception("Response PDU too short for byte count.");
}
const uint8_t byte_count = response_pdu[1];
if (response_pdu.size() != 2 + byte_count) {
throw exception("Byte count does not match PDU size.");
}
if (byte_count % 2 != 0) {
throw exception("Byte count is not an even number.");
}
std::vector<uint16_t> registers;
registers.reserve(byte_count / 2);
for (size_t i = 0; i < byte_count; i += 2) {
registers.push_back(read_be16(response_pdu.data() + 2 + i));
}
return registers;
}
} // namespace modbus::protocol

View File

@ -0,0 +1,37 @@
// 文件名: src/protocol/modbus/modbus_protocol.h
#ifndef MODBUS_PROTOCOL_H
#define MODBUS_PROTOCOL_H
#include <vector>
#include <cstdint>
#include <string>
#include <stdexcept>
namespace modbus::protocol {
// 自定义异常类型,方便上层捕获
class exception : public std::runtime_error {
public:
explicit exception(const std::string& message) : std::runtime_error(message) {}
};
/**
* @brief "读保持寄存器" (0x03) PDU
* @param start_address
* @param quantity
* @return PDU字节流 (MBAP头)
*/
std::vector<uint8_t> create_read_holding_registers_pdu(uint16_t start_address, uint16_t quantity);
/**
* @brief "读保持寄存器"PDU
* @param response_pdu PDU字节流
* @return
* @throws modbus::protocol::exception ()
*/
std::vector<uint16_t> parse_read_holding_registers_response(const std::vector<uint8_t>& response_pdu);
} // namespace modbus::protocol
#endif // MODBUS_PROTOCOL_H

View File

@ -217,7 +217,6 @@ void PrivateProtocolAdapter::on_message(proto::Message msg) {
}
}
// 发送消息 (与您原来的逻辑完全相同)
void PrivateProtocolAdapter::send_message(uint16_t msg_type, const std::vector<uint8_t>& payload) {
std::vector<uint8_t> frame;
frame.reserve(proto::ProtocolConfig::kHeaderLen + payload.size() + proto::ProtocolConfig::kCrcLen);

View File

@ -8,14 +8,14 @@ std::unique_ptr<IProtocolAdapter> ProtocolFactory::create_adapter(uint16_t port,
spdlog::info("Creating protocol adapter for port {}", port);
// 根据端口号决定创建哪个适配器
if (port == 8888) { // 假设 8001 是您的私有协议端口
if (port == 8888) {
return std::make_unique<PrivateProtocolAdapter>(std::move(report_cb));
}
else if (port == 502) {
//TODO:这里没有写modbus协议的Adapter快去写
return std::make_unique<PrivateProtocolAdapter>(std::move(report_cb));
}
// !!modbus大部分情况下是master式的不适用这里slave式的协议
// else if (port == 502) {
// return std::make_unique<PrivateProtocolAdapter>(std::move(report_cb));
// }
// 如果没有匹配的端口,返回空指针
spdlog::error("No protocol adapter configured for port {}", port);

View File

@ -8,7 +8,7 @@
// Include the new header files
#include "modbus/modbus_rtu_client.h"
#include "modbus/modbus_data_parser.h"
#include "modbus/modbus_temperature_humidity.h"
int main(int argc, char* argv[]) {
std::cout << "--- Modbus RTU Sensor Client ---" << std::endl;