modbus rtu 1.1 plugins
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
# Runtime-loadable Modbus RTU parser + framer plugin
|
||||
add_library(modbus_rtu SHARED
|
||||
plugin_stub.cpp
|
||||
../modbusrtu/parser/ModbusRtuProtocolPlugin.cpp
|
||||
../modbusrtu/codec/ModbusRtuCodec.cpp
|
||||
../modbusrtu/framer/ModbusRtuFramerPlugin.cpp
|
||||
../modbusrtu/framer/ModbusRtuFraming.cpp
|
||||
parser/ModbusRtuProtocolPlugin.cpp
|
||||
mapper/ModbusRtuProtocolMapperPlugin.cpp
|
||||
codec/ModbusRtuCodec.cpp
|
||||
framer/ModbusRtuFramerPlugin.cpp
|
||||
framer/ModbusRtuFraming.cpp
|
||||
)
|
||||
target_include_directories(modbus_rtu PRIVATE
|
||||
${CMAKE_SOURCE_DIR}
|
||||
|
||||
37
plugins/protocols/modbus_rtu/README.md
Normal file
37
plugins/protocols/modbus_rtu/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Modbus RTU 协议模块(`modbus_rtu`)
|
||||
|
||||
本目录实现 **Modbus RTU** 的成帧、编解码与解析插件,供守护进程静态编译与 `plugins/protocols/modbus_rtu` 动态库(`.so`)共用同一套源码。
|
||||
|
||||
## 目录结构
|
||||
|
||||
| 子目录 | 职责 |
|
||||
|--------|------|
|
||||
| `types/` | PDU 数据模型:`ModbusRtuPdu` 及各类请求/响应结构体,供编解码与 `ProtocolEnvelope` 使用。 |
|
||||
| `codec/` | ADU 与 `ModbusRtuPdu` 的互转:`decodeModbusRtu` / `encodeModbusRtu`(依赖 `types` 与 `framer` 中的 CRC 等工具函数)。 |
|
||||
| `framer/` | 字节流成帧:`ModbusRtuFraming`(长度预测、CRC)、`ModbusRtuFramerPlugin`(Stage1 插件会话)。 |
|
||||
| `parser/` | Stage2 协议插件:`ModbusRtuProtocolPlugin`,将完整帧解析为 `ProtocolEnvelope` / 兼容 JSON。 |
|
||||
|
||||
## 依赖方向(建议遵守)
|
||||
|
||||
```
|
||||
types (无本模块内依赖)
|
||||
↑
|
||||
codec、framer (可依赖 types;codec 使用 framer 中的 crc16/crcOk)
|
||||
↑
|
||||
parser (依赖 codec、types;不反向依赖 framer 插件类)
|
||||
```
|
||||
|
||||
成帧与解析在流水线中分属不同阶段;**解析侧只应处理 `CompleteFrame`**,与 `PipelineContext::frameKind` 约定一致。
|
||||
|
||||
## 与动态库的关系
|
||||
|
||||
- 动态库入口:`plugins/protocols/modbus_rtu/plugin_stub.cpp`,导出 `softbus_create_protocol_plugin` / `softbus_create_framer_plugin` 等符号。
|
||||
- 主工程 `CMakeLists.txt` 同样列出本目录下源文件,用于无 `.so` 时的内置链接;路径与 include 均以 `plugins/protocols/modbus_rtu/...` 为准。
|
||||
|
||||
## 配置提示
|
||||
|
||||
在 `daemon_config.json` 的 `plugins.framers` 中配置 `libmodbus_rtu.so` 路径即可加载成帧插件;解析插件加载方式与 `PluginHost` / 工程内注册逻辑一致。
|
||||
|
||||
## 扩展其它协议时的参考
|
||||
|
||||
新增协议(如 Modbus TCP、CANopen)时,建议沿用类似分层:**types → codec/framer → parser**,并在各自 `plugin_stub` 中导出统一 ABI 工厂函数,由 `PluginHost` 注册到 `PluginManager`。
|
||||
428
plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.cpp
Normal file
428
plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.cpp
Normal file
@@ -0,0 +1,428 @@
|
||||
#include "plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h"
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "message_bus/pipeline/PipelineContext.h"
|
||||
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using softbus::message_bus::pipeline::BusDirection;
|
||||
using softbus::message_bus::pipeline::protocol::modbusrtu::crc16;
|
||||
using softbus::message_bus::pipeline::protocol::modbusrtu::crcOk;
|
||||
|
||||
void appendCrcLe(QByteArray& adu)
|
||||
{
|
||||
const auto crc = crc16(reinterpret_cast<const std::uint8_t*>(adu.constData()),
|
||||
static_cast<std::size_t>(adu.size()));
|
||||
adu.append(static_cast<char>(crc & 0xFF));
|
||||
adu.append(static_cast<char>((crc >> 8) & 0xFF));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// 功能 :解码Modbus RTU帧 如果解码失败 则返回false 如果解码成功 则返回true
|
||||
bool decodeModbusRtu(BusDirection dir,
|
||||
const std::uint8_t* p,
|
||||
std::size_t n,
|
||||
ModbusRtuPdu& out,
|
||||
QString* errMsg)
|
||||
{
|
||||
auto fail = [&](const char* msg) {
|
||||
if (errMsg) {
|
||||
*errMsg = QString::fromUtf8(msg);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!p || n < 4) {
|
||||
return fail("adu_too_short");
|
||||
}
|
||||
|
||||
if (p[1] & 0x80) {
|
||||
if (n < 5) {
|
||||
return fail("exception_incomplete");
|
||||
}
|
||||
if (!crcOk(p, 5)) {
|
||||
return fail("exception_crc");
|
||||
}
|
||||
out = ModbusExceptionPdu{p[0], p[1], p[2]};
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::uint8_t unit = p[0];
|
||||
const std::uint8_t fc = p[1];
|
||||
auto parseReadReq = [&]() -> bool {
|
||||
if (n != 8 || !crcOk(p, 8)) {
|
||||
return false;
|
||||
}
|
||||
const std::uint16_t addr = static_cast<std::uint16_t>((p[2] << 8) | p[3]);
|
||||
const std::uint16_t qty = static_cast<std::uint16_t>((p[4] << 8) | p[5]);
|
||||
switch (fc) {
|
||||
case 0x01:
|
||||
out = ReadCoilsRequest{unit, addr, qty};
|
||||
return true;
|
||||
case 0x02:
|
||||
out = ReadDiscreteInputsRequest{unit, addr, qty};
|
||||
return true;
|
||||
case 0x03:
|
||||
out = ReadHoldingRegistersRequest{unit, addr, qty};
|
||||
return true;
|
||||
case 0x04:
|
||||
out = ReadInputRegistersRequest{unit, addr, qty};
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
auto parseWriteSingleReqOrResp = [&]() -> bool {
|
||||
if (n != 8 || !crcOk(p, 8)) {
|
||||
return false;
|
||||
}
|
||||
const std::uint16_t addr = static_cast<std::uint16_t>((p[2] << 8) | p[3]);
|
||||
const std::uint16_t val16 = static_cast<std::uint16_t>((p[4] << 8) | p[5]);
|
||||
if (fc == 0x05) {
|
||||
const std::uint8_t coil = (val16 == 0xFF00) ? 1 : 0;
|
||||
if (dir == BusDirection::Upstream) {
|
||||
out = WriteSingleCoilResponse{unit, addr, coil};
|
||||
} else {
|
||||
out = WriteSingleCoilRequest{unit, addr, coil};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (fc == 0x06) {
|
||||
if (dir == BusDirection::Upstream) {
|
||||
out = WriteSingleRegisterResponse{unit, addr, val16};
|
||||
} else {
|
||||
out = WriteSingleRegisterRequest{unit, addr, val16};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto parseWriteMultiReq = [&]() -> bool {
|
||||
if (n < 9) {
|
||||
return false;
|
||||
}
|
||||
const std::uint16_t start = static_cast<std::uint16_t>((p[2] << 8) | p[3]);
|
||||
const std::uint16_t qty = static_cast<std::uint16_t>((p[4] << 8) | p[5]);
|
||||
const std::uint8_t byteCount = p[6];
|
||||
if (n != static_cast<std::size_t>(9) + byteCount) {
|
||||
return false;
|
||||
}
|
||||
if (!crcOk(p, n)) {
|
||||
return false;
|
||||
}
|
||||
if (fc == 0x0F) {
|
||||
QVector<std::uint8_t> vals;
|
||||
vals.reserve(byteCount);
|
||||
for (std::uint8_t i = 0; i < byteCount; ++i) {
|
||||
vals.append(p[7 + i]);
|
||||
}
|
||||
out = WriteMultipleCoilsRequest{unit, start, qty, std::move(vals)};
|
||||
return true;
|
||||
}
|
||||
if (fc == 0x10) {
|
||||
QVector<std::uint16_t> vals;
|
||||
vals.reserve(byteCount / 2);
|
||||
for (std::uint8_t i = 0; i + 1 < byteCount; i += 2) {
|
||||
vals.append(static_cast<std::uint16_t>((p[7 + i] << 8) | p[7 + i + 1]));
|
||||
}
|
||||
out = WriteMultipleRegistersRequest{unit, start, qty, std::move(vals)};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto parseWriteMultiResp = [&]() -> bool {
|
||||
if (n != 8 || !crcOk(p, 8)) {
|
||||
return false;
|
||||
}
|
||||
const std::uint16_t start = static_cast<std::uint16_t>((p[2] << 8) | p[3]);
|
||||
const std::uint16_t qty = static_cast<std::uint16_t>((p[4] << 8) | p[5]);
|
||||
if (fc == 0x0F) {
|
||||
out = WriteMultipleCoilsResponse{unit, start, qty, {}};
|
||||
return true;
|
||||
}
|
||||
if (fc == 0x10) {
|
||||
out = WriteMultipleRegistersResponse{unit, start, qty, {}};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 功能 :解析Modbus的读取响应帧
|
||||
// function code range 01 02 03 04
|
||||
auto parseReadResp = [&]() -> bool {
|
||||
if (n < 5) {
|
||||
return false;
|
||||
}
|
||||
const std::uint8_t bc = p[2];
|
||||
const std::size_t total = static_cast<std::size_t>(5) + bc;
|
||||
if (n < total || !crcOk(p, total)) {
|
||||
return false;
|
||||
}
|
||||
if (fc == 0x01) {
|
||||
QVector<std::uint8_t> coils;
|
||||
coils.reserve(bc);
|
||||
for (std::uint8_t i = 0; i < bc; ++i) {
|
||||
coils.append(p[3 + i]);
|
||||
}
|
||||
out = ReadCoilsResponse{unit, std::move(coils)};
|
||||
return true;
|
||||
}
|
||||
if (fc == 0x02) {
|
||||
QVector<std::uint8_t> inputs;
|
||||
inputs.reserve(bc);
|
||||
for (std::uint8_t i = 0; i < bc; ++i) {
|
||||
inputs.append(p[3 + i]);
|
||||
}
|
||||
out = ReadDiscreteInputsResponse{unit, std::move(inputs)};
|
||||
return true;
|
||||
}
|
||||
if (fc == 0x03 || fc == 0x04) {
|
||||
QVector<std::uint16_t> regs;
|
||||
regs.reserve(bc / 2);
|
||||
for (std::uint8_t i = 0; i + 1 < bc; i += 2) {
|
||||
regs.append(static_cast<std::uint16_t>((p[3 + i] << 8) | p[3 + i + 1]));
|
||||
}
|
||||
if (fc == 0x03) {
|
||||
out = ReadHoldingRegistersResponse{unit, std::move(regs)};
|
||||
} else {
|
||||
out = ReadInputRegistersResponse{unit, std::move(regs)};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// if (dir == BusDirection::Downstream) {
|
||||
// // 功能 :解析Modbus的下行请求帧
|
||||
// // function code range 01 02 03 04 05 06 0F 10
|
||||
// if (parseReadReq() || parseWriteSingleReqOrResp() || parseWriteMultiReq()) {
|
||||
// return true;
|
||||
// }
|
||||
// out = ModbusRtuUnsupportedPdu{unit, fc};
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// if (dir == BusDirection::Upstream) {
|
||||
// // 功能 :解析Modbus的上行响应帧
|
||||
// // function code range 01 02 03 04 05 06 0F 10
|
||||
// if (parseReadResp() || parseWriteSingleReqOrResp() || parseWriteMultiResp()) {
|
||||
// return true;
|
||||
// }
|
||||
// // 功能 :解析Modbus的下行请求帧
|
||||
// if (parseReadReq() || parseWriteMultiReq()) {
|
||||
// return true;
|
||||
// }
|
||||
// out = ModbusRtuUnsupportedPdu{unit, fc};
|
||||
// return true;
|
||||
// }
|
||||
|
||||
if (parseReadReq() || parseWriteSingleReqOrResp() || parseWriteMultiReq() || parseReadResp()
|
||||
|| parseWriteMultiResp()) {
|
||||
return true;
|
||||
}
|
||||
out = ModbusRtuUnsupportedPdu{unit, fc};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool encodeModbusRtu(const ModbusRtuPdu& pdu, QByteArray& out, QString* errMsg)
|
||||
{
|
||||
auto fail = [&](const char* msg) {
|
||||
if (errMsg) {
|
||||
*errMsg = QString::fromUtf8(msg);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
out.clear();
|
||||
|
||||
if (const auto* ex = std::get_if<ModbusExceptionPdu>(&pdu)) {
|
||||
out.append(static_cast<char>(ex->unitId));
|
||||
out.append(static_cast<char>(ex->function));
|
||||
out.append(static_cast<char>(ex->exceptionCode));
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
auto encodeReadReq = [&](std::uint8_t unit, std::uint8_t fc, std::uint16_t start, std::uint16_t qty) {
|
||||
out.append(static_cast<char>(unit));
|
||||
out.append(static_cast<char>(fc));
|
||||
out.append(static_cast<char>((start >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(start & 0xFF));
|
||||
out.append(static_cast<char>((qty >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(qty & 0xFF));
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
};
|
||||
auto encodeReadRegResp = [&](std::uint8_t unit, std::uint8_t fc, const QVector<std::uint16_t>& regs) {
|
||||
if (regs.size() > 127) {
|
||||
return false;
|
||||
}
|
||||
out.append(static_cast<char>(unit));
|
||||
out.append(static_cast<char>(fc));
|
||||
out.append(static_cast<char>(regs.size() * 2));
|
||||
for (std::uint16_t v : regs) {
|
||||
out.append(static_cast<char>((v >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(v & 0xFF));
|
||||
}
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
};
|
||||
auto encodeReadBitsResp = [&](std::uint8_t unit, std::uint8_t fc, const QVector<std::uint8_t>& bytes) {
|
||||
if (bytes.size() > 255) {
|
||||
return false;
|
||||
}
|
||||
out.append(static_cast<char>(unit));
|
||||
out.append(static_cast<char>(fc));
|
||||
out.append(static_cast<char>(bytes.size()));
|
||||
for (std::uint8_t b : bytes) {
|
||||
out.append(static_cast<char>(b));
|
||||
}
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
};
|
||||
if (const auto* req = std::get_if<ReadCoilsRequest>(&pdu)) {
|
||||
return encodeReadReq(req->unitId, 0x01, req->startAddress, req->quantity);
|
||||
}
|
||||
if (const auto* req = std::get_if<ReadDiscreteInputsRequest>(&pdu)) {
|
||||
return encodeReadReq(req->unitId, 0x02, req->startAddress, req->quantity);
|
||||
}
|
||||
if (const auto* req = std::get_if<ReadHoldingRegistersRequest>(&pdu)) {
|
||||
return encodeReadReq(req->unitId, 0x03, req->startAddress, req->quantity);
|
||||
}
|
||||
if (const auto* req = std::get_if<ReadInputRegistersRequest>(&pdu)) {
|
||||
return encodeReadReq(req->unitId, 0x04, req->startAddress, req->quantity);
|
||||
}
|
||||
if (const auto* resp = std::get_if<ReadHoldingRegistersResponse>(&pdu)) {
|
||||
if (!encodeReadRegResp(resp->unitId, 0x03, resp->registers)) {
|
||||
return fail("too_many_registers");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (const auto* resp = std::get_if<ReadInputRegistersResponse>(&pdu)) {
|
||||
if (!encodeReadRegResp(resp->unitId, 0x04, resp->inputRegisters)) {
|
||||
return fail("too_many_registers");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (const auto* resp = std::get_if<ReadCoilsResponse>(&pdu)) {
|
||||
if (!encodeReadBitsResp(resp->unitId, 0x01, resp->coils)) {
|
||||
return fail("too_many_bytes");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (const auto* resp = std::get_if<ReadDiscreteInputsResponse>(&pdu)) {
|
||||
if (!encodeReadBitsResp(resp->unitId, 0x02, resp->discreteInputs)) {
|
||||
return fail("too_many_bytes");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (const auto* req = std::get_if<WriteSingleCoilRequest>(&pdu)) {
|
||||
out.append(static_cast<char>(req->unitId));
|
||||
out.append(static_cast<char>(0x05));
|
||||
out.append(static_cast<char>((req->address >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(req->address & 0xFF));
|
||||
const std::uint16_t coilWord = req->value ? 0xFF00 : 0x0000;
|
||||
out.append(static_cast<char>((coilWord >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(coilWord & 0xFF));
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
if (const auto* resp = std::get_if<WriteSingleCoilResponse>(&pdu)) {
|
||||
out.append(static_cast<char>(resp->unitId));
|
||||
out.append(static_cast<char>(0x05));
|
||||
out.append(static_cast<char>((resp->address >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(resp->address & 0xFF));
|
||||
const std::uint16_t coilWord = resp->value ? 0xFF00 : 0x0000;
|
||||
out.append(static_cast<char>((coilWord >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(coilWord & 0xFF));
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
if (const auto* req = std::get_if<WriteSingleRegisterRequest>(&pdu)) {
|
||||
out.append(static_cast<char>(req->unitId));
|
||||
out.append(static_cast<char>(0x06));
|
||||
out.append(static_cast<char>((req->address >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(req->address & 0xFF));
|
||||
out.append(static_cast<char>((req->value >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(req->value & 0xFF));
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
if (const auto* resp = std::get_if<WriteSingleRegisterResponse>(&pdu)) {
|
||||
out.append(static_cast<char>(resp->unitId));
|
||||
out.append(static_cast<char>(0x06));
|
||||
out.append(static_cast<char>((resp->address >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(resp->address & 0xFF));
|
||||
out.append(static_cast<char>((resp->value >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(resp->value & 0xFF));
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
if (const auto* req = std::get_if<WriteMultipleCoilsRequest>(&pdu)) {
|
||||
if (req->values.size() > 255) {
|
||||
return fail("too_many_bytes");
|
||||
}
|
||||
out.append(static_cast<char>(req->unitId));
|
||||
out.append(static_cast<char>(0x0F));
|
||||
out.append(static_cast<char>((req->startAddress >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(req->startAddress & 0xFF));
|
||||
out.append(static_cast<char>((req->quantity >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(req->quantity & 0xFF));
|
||||
out.append(static_cast<char>(req->values.size()));
|
||||
for (std::uint8_t b : req->values) {
|
||||
out.append(static_cast<char>(b));
|
||||
}
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
if (const auto* resp = std::get_if<WriteMultipleCoilsResponse>(&pdu)) {
|
||||
out.append(static_cast<char>(resp->unitId));
|
||||
out.append(static_cast<char>(0x0F));
|
||||
out.append(static_cast<char>((resp->startAddress >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(resp->startAddress & 0xFF));
|
||||
out.append(static_cast<char>((resp->quantity >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(resp->quantity & 0xFF));
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
if (const auto* req = std::get_if<WriteMultipleRegistersRequest>(&pdu)) {
|
||||
if (req->values.size() > 127) {
|
||||
return fail("too_many_registers");
|
||||
}
|
||||
out.append(static_cast<char>(req->unitId));
|
||||
out.append(static_cast<char>(0x10));
|
||||
out.append(static_cast<char>((req->startAddress >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(req->startAddress & 0xFF));
|
||||
out.append(static_cast<char>((req->quantity >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(req->quantity & 0xFF));
|
||||
out.append(static_cast<char>(req->values.size() * 2));
|
||||
for (std::uint16_t v : req->values) {
|
||||
out.append(static_cast<char>((v >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(v & 0xFF));
|
||||
}
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
if (const auto* resp = std::get_if<WriteMultipleRegistersResponse>(&pdu)) {
|
||||
out.append(static_cast<char>(resp->unitId));
|
||||
out.append(static_cast<char>(0x10));
|
||||
out.append(static_cast<char>((resp->startAddress >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(resp->startAddress & 0xFF));
|
||||
out.append(static_cast<char>((resp->quantity >> 8) & 0xFF));
|
||||
out.append(static_cast<char>(resp->quantity & 0xFF));
|
||||
appendCrcLe(out);
|
||||
return true;
|
||||
}
|
||||
return fail("unsupported_encode");
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol
|
||||
27
plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h
Normal file
27
plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h"
|
||||
|
||||
namespace softbus::message_bus::pipeline
|
||||
{
|
||||
enum class BusDirection;
|
||||
}
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol
|
||||
{
|
||||
|
||||
bool decodeModbusRtu(softbus::message_bus::pipeline::BusDirection dir,
|
||||
const std::uint8_t* p,
|
||||
std::size_t n,
|
||||
ModbusRtuPdu& out,
|
||||
QString* errMsg = nullptr);
|
||||
|
||||
bool encodeModbusRtu(const ModbusRtuPdu& pdu, QByteArray& out, QString* errMsg = nullptr);
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol
|
||||
172
plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.cpp
Normal file
172
plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class ModbusRtuFramerSession final : public softbus::core::plugin_system::IProtocolFramerSession
|
||||
{
|
||||
public:
|
||||
ModbusRtuFramerSession(std::shared_ptr<softbus::core::memory::MemoryPool> pool,
|
||||
ModbusRtuFramerPluginConfig cfg,
|
||||
QString endpoint)
|
||||
: m_pool(std::move(pool))
|
||||
, m_cfg(cfg)
|
||||
, m_endpoint(std::move(endpoint))
|
||||
{
|
||||
m_buf.resize(m_cfg.maxAdu);
|
||||
}
|
||||
|
||||
void feedChunk(softbus::message_bus::pipeline::PipelineContext& in,
|
||||
std::vector<softbus::message_bus::pipeline::PipelineContext>& out) override
|
||||
{
|
||||
using softbus::message_bus::pipeline::FrameKind;
|
||||
if (!m_pool || !in.payload.valid()) {
|
||||
return;
|
||||
}
|
||||
const std::uint8_t* p = in.payload.bytes();
|
||||
const std::size_t n = in.payloadSize ? in.payloadSize : in.payload.length;
|
||||
if (!p || n == 0) {
|
||||
return;
|
||||
}
|
||||
m_lastRx = std::chrono::steady_clock::now();
|
||||
|
||||
if (m_cfg.interFrameTimeoutMs > 0 && m_len > 0) {
|
||||
const auto age = std::chrono::duration_cast<std::chrono::milliseconds>(m_lastRx - m_lastActivity).count();
|
||||
if (age >= m_cfg.interFrameTimeoutMs) {
|
||||
m_len = 0;
|
||||
}
|
||||
}
|
||||
m_lastActivity = m_lastRx;
|
||||
|
||||
if (m_len + n > m_cfg.maxAdu) {
|
||||
LOG_WARNING() << "ModbusRtuFramerPlugin: overflow endpoint=" << m_endpoint;
|
||||
m_len = 0;
|
||||
return;
|
||||
}
|
||||
std::memcpy(m_buf.data() + m_len, p, n);
|
||||
m_len += n;
|
||||
|
||||
while (m_len > 0) {
|
||||
auto pred = predictAduLength(m_buf.data(), m_len, m_cfg.maxAdu);
|
||||
if (!pred || m_len < *pred) {
|
||||
break;
|
||||
}
|
||||
if (!crcOk(m_buf.data(), *pred)) {
|
||||
if (m_len <= 1) {
|
||||
m_len = 0;
|
||||
break;
|
||||
}
|
||||
std::memmove(m_buf.data(), m_buf.data() + 1, m_len - 1);
|
||||
--m_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto blk = m_pool->allocate(*pred);
|
||||
if (!blk || !blk->data || blk->capacity < *pred) {
|
||||
return;
|
||||
}
|
||||
std::memcpy(blk->data, m_buf.data(), *pred);
|
||||
blk->size = *pred;
|
||||
|
||||
softbus::message_bus::pipeline::PipelineContext framed;
|
||||
framed.deviceId = in.deviceId;
|
||||
framed.stableKey = in.stableKey;
|
||||
framed.endpoint = in.endpoint;
|
||||
framed.timestamp = in.timestamp;
|
||||
framed.protocolHint = in.protocolHint;
|
||||
framed.traceId = in.traceId;
|
||||
framed.direction = in.direction;
|
||||
framed.frameKind = FrameKind::CompleteFrame;
|
||||
framed.payload.block = std::move(blk);
|
||||
framed.payload.offset = 0;
|
||||
framed.payload.length = *pred;
|
||||
framed.payloadSize = *pred;
|
||||
out.push_back(std::move(framed));
|
||||
|
||||
const std::size_t rest = m_len - *pred;
|
||||
if (rest > 0) {
|
||||
std::memmove(m_buf.data(), m_buf.data() + *pred, rest);
|
||||
}
|
||||
m_len = rest;
|
||||
}
|
||||
|
||||
in.payload = {};
|
||||
in.payloadSize = 0;
|
||||
in.frameKind = FrameKind::StreamChunk;
|
||||
}
|
||||
|
||||
void reset() override { m_len = 0; }
|
||||
void shutdown() override { m_len = 0; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
|
||||
ModbusRtuFramerPluginConfig m_cfg;
|
||||
QString m_endpoint;
|
||||
std::vector<std::uint8_t> m_buf;
|
||||
std::size_t m_len{0};
|
||||
std::chrono::steady_clock::time_point m_lastRx{std::chrono::steady_clock::now()};
|
||||
std::chrono::steady_clock::time_point m_lastActivity{std::chrono::steady_clock::now()};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
ModbusRtuFramerPlugin::ModbusRtuFramerPlugin(std::shared_ptr<softbus::core::memory::MemoryPool> pool,
|
||||
ModbusRtuFramerPluginConfig cfg)
|
||||
: m_pool(std::move(pool))
|
||||
, m_cfg(cfg)
|
||||
{
|
||||
}
|
||||
|
||||
QString ModbusRtuFramerPlugin::pluginId() const
|
||||
{
|
||||
return QStringLiteral("modbus_rtu_framer");
|
||||
}
|
||||
|
||||
bool ModbusRtuFramerPlugin::supports(const QString& protocolHint) const
|
||||
{
|
||||
return isModbusRtuHint(protocolHint);
|
||||
}
|
||||
|
||||
void ModbusRtuFramerPlugin::bindMemoryPool(std::shared_ptr<softbus::core::memory::MemoryPool> pool)
|
||||
{
|
||||
m_pool = std::move(pool);
|
||||
}
|
||||
|
||||
std::unique_ptr<softbus::core::plugin_system::IProtocolFramerSession>
|
||||
ModbusRtuFramerPlugin::createFramerSession(const QString& endpoint, const QJsonObject& params)
|
||||
{
|
||||
if (!m_pool) {
|
||||
return {};
|
||||
}
|
||||
ModbusRtuFramerPluginConfig cfg = m_cfg;
|
||||
if (params.contains(QStringLiteral("modbusRtuMaxFrameBytes"))) {
|
||||
cfg.maxAdu = static_cast<std::size_t>(
|
||||
std::max(8, params.value(QStringLiteral("modbusRtuMaxFrameBytes")).toInt(static_cast<int>(cfg.maxAdu))));
|
||||
}
|
||||
if (params.contains(QStringLiteral("modbusRtuInterFrameTimeoutMs"))) {
|
||||
cfg.interFrameTimeoutMs = params.value(QStringLiteral("modbusRtuInterFrameTimeoutMs")).toInt(cfg.interFrameTimeoutMs);
|
||||
}
|
||||
return std::make_unique<ModbusRtuFramerSession>(m_pool, cfg, endpoint);
|
||||
}
|
||||
|
||||
std::shared_ptr<softbus::core::plugin_system::IProtocolFramerPlugin> makeModbusRtuFramerPlugin(
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool, ModbusRtuFramerPluginConfig cfg)
|
||||
{
|
||||
return std::make_shared<ModbusRtuFramerPlugin>(std::move(pool), cfg);
|
||||
}
|
||||
|
||||
softbus::core::plugin_system::IProtocolFramerPlugin* createModbusRtuFramerPluginRaw()
|
||||
{
|
||||
return new ModbusRtuFramerPlugin(nullptr, {});
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
44
plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.h
Normal file
44
plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "core/plugin_system/IProtocolFramerPlugin.h"
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
{
|
||||
|
||||
struct ModbusRtuFramerPluginConfig
|
||||
{
|
||||
std::size_t maxAdu{256};
|
||||
int interFrameTimeoutMs{0};
|
||||
};
|
||||
|
||||
class ModbusRtuFramerPlugin final : public softbus::core::plugin_system::IProtocolFramerPlugin
|
||||
{
|
||||
public:
|
||||
explicit ModbusRtuFramerPlugin(std::shared_ptr<softbus::core::memory::MemoryPool> pool,
|
||||
ModbusRtuFramerPluginConfig cfg = {});
|
||||
|
||||
QString pluginId() const override;
|
||||
bool supports(const QString& protocolHint) const override;
|
||||
void bindMemoryPool(std::shared_ptr<softbus::core::memory::MemoryPool> pool) override;
|
||||
std::unique_ptr<softbus::core::plugin_system::IProtocolFramerSession> createFramerSession(
|
||||
const QString& endpoint, const QJsonObject& params) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
|
||||
ModbusRtuFramerPluginConfig m_cfg;
|
||||
};
|
||||
|
||||
std::shared_ptr<softbus::core::plugin_system::IProtocolFramerPlugin> makeModbusRtuFramerPlugin(
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool, ModbusRtuFramerPluginConfig cfg = {});
|
||||
softbus::core::plugin_system::IProtocolFramerPlugin* createModbusRtuFramerPluginRaw();
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
88
plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.cpp
Normal file
88
plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
{
|
||||
|
||||
std::uint16_t crc16(const std::uint8_t* data, std::size_t len)
|
||||
{
|
||||
std::uint16_t crc = 0xFFFF;
|
||||
for (std::size_t i = 0; i < len; ++i) {
|
||||
crc ^= data[i];
|
||||
for (int k = 0; k < 8; ++k) {
|
||||
crc = (crc & 1) ? static_cast<std::uint16_t>((crc >> 1) ^ 0xA001)
|
||||
: static_cast<std::uint16_t>(crc >> 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool crcOk(const std::uint8_t* adu, std::size_t aduLen)
|
||||
{
|
||||
if (!adu || aduLen < 4) {
|
||||
return false;
|
||||
}
|
||||
const std::size_t body = aduLen - 2;
|
||||
const std::uint16_t got =
|
||||
static_cast<std::uint16_t>(adu[body] | (static_cast<std::uint16_t>(adu[body + 1]) << 8));
|
||||
return got == crc16(adu, body);
|
||||
}
|
||||
|
||||
static bool isReadFc(std::uint8_t fc)
|
||||
{
|
||||
return fc == 0x01 || fc == 0x02 || fc == 0x03 || fc == 0x04;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> predictAduLength(const std::uint8_t* buf, std::size_t len, std::size_t maxAdu)
|
||||
{
|
||||
if (!buf || len < 2) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::uint8_t fc = buf[1];
|
||||
if (fc & 0x80) {
|
||||
return len >= 5 ? std::optional<std::size_t>(5) : std::nullopt;
|
||||
}
|
||||
|
||||
if (isReadFc(fc)) {
|
||||
if (len >= 8 && crcOk(buf, 8)) {
|
||||
return std::optional<std::size_t>(8); // read request
|
||||
}
|
||||
if (len < 3) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::size_t total = static_cast<std::size_t>(5) + buf[2];
|
||||
if (total > maxAdu || len < total) {
|
||||
return std::nullopt; // wait more bytes
|
||||
}
|
||||
return crcOk(buf, total) ? std::optional<std::size_t>(total) : std::nullopt;
|
||||
}
|
||||
|
||||
if (fc == 0x05 || fc == 0x06) {
|
||||
return len >= 8 ? std::optional<std::size_t>(8) : std::nullopt;
|
||||
}
|
||||
if (fc == 0x16) {
|
||||
return len >= 10 ? std::optional<std::size_t>(10) : std::nullopt;
|
||||
}
|
||||
if (fc == 0x0F || fc == 0x10) {
|
||||
if (len >= 8 && crcOk(buf, 8)) {
|
||||
return std::optional<std::size_t>(8); // response
|
||||
}
|
||||
if (len < 7) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::size_t req = static_cast<std::size_t>(9) + buf[6];
|
||||
if (req > maxAdu || len < req) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return crcOk(buf, req) ? std::optional<std::size_t>(req) : std::nullopt;
|
||||
}
|
||||
return len >= 8 ? std::optional<std::size_t>(8) : std::nullopt;
|
||||
}
|
||||
|
||||
bool isModbusRtuHint(const QString& hint)
|
||||
{
|
||||
const QString h = hint.trimmed().toLower();
|
||||
return h == QStringLiteral("modbus_rtu") || h == QStringLiteral("modbus-rtu")
|
||||
|| h == QStringLiteral("modbusrtu");
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
17
plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h
Normal file
17
plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
{
|
||||
|
||||
std::uint16_t crc16(const std::uint8_t* data, std::size_t len);
|
||||
bool crcOk(const std::uint8_t* adu, std::size_t aduLen);
|
||||
std::optional<std::size_t> predictAduLength(const std::uint8_t* buf, std::size_t len, std::size_t maxAdu);
|
||||
bool isModbusRtuHint(const QString& hint);
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "plugins/protocols/modbus_rtu/mapper/ModbusRtuProtocolMapperPlugin.h"
|
||||
|
||||
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class ModbusRtuMapperSession final : public softbus::core::plugin_system::IProtocolMapperSession
|
||||
{
|
||||
public:
|
||||
std::vector<softbus::core::models::DOMMessage> map(
|
||||
const softbus::message_bus::pipeline::PipelineContext& rawContext,
|
||||
const softbus::message_bus::pipeline::protocol::ProtocolEnvelope& envelope) override
|
||||
{
|
||||
std::vector<softbus::core::models::DOMMessage> out;
|
||||
if (envelope.family != softbus::message_bus::pipeline::protocol::ProtocolFamily::ModbusRtu) {
|
||||
return out;
|
||||
}
|
||||
const auto* pdu = std::get_if<softbus::message_bus::pipeline::protocol::ModbusRtuPdu>(&envelope.pdu);
|
||||
if (!pdu) {
|
||||
return out;
|
||||
}
|
||||
|
||||
if (const auto* resp =
|
||||
std::get_if<softbus::message_bus::pipeline::protocol::ReadHoldingRegistersResponse>(pdu)) {
|
||||
out.reserve(static_cast<std::size_t>(resp->registers.size()));
|
||||
for (int i = 0; i < resp->registers.size(); ++i) {
|
||||
softbus::core::models::DOMMessage msg;
|
||||
msg.messageId = QStringLiteral("%1.reg.%2")
|
||||
.arg(rawContext.stableKey.isEmpty() ? QString::number(rawContext.deviceId)
|
||||
: rawContext.stableKey)
|
||||
.arg(i);
|
||||
msg.timestamp = rawContext.timestamp.toMSecsSinceEpoch();
|
||||
msg.value = static_cast<std::uint32_t>(resp->registers[static_cast<qsizetype>(i)]);
|
||||
msg.quality = softbus::core::models::DataQuality::GOOD;
|
||||
msg.attributes.insert(QStringLiteral("sourceEndpoint"), rawContext.endpoint);
|
||||
out.push_back(std::move(msg));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
if (const auto* req =
|
||||
std::get_if<softbus::message_bus::pipeline::protocol::ReadHoldingRegistersRequest>(pdu)) {
|
||||
softbus::core::models::DOMMessage msg;
|
||||
msg.messageId = QStringLiteral("%1.req.read_holding")
|
||||
.arg(rawContext.stableKey.isEmpty() ? QString::number(rawContext.deviceId)
|
||||
: rawContext.stableKey);
|
||||
msg.timestamp = rawContext.timestamp.toMSecsSinceEpoch();
|
||||
msg.value = static_cast<std::uint32_t>(req->quantity);
|
||||
msg.quality = softbus::core::models::DataQuality::GOOD;
|
||||
msg.attributes.insert(QStringLiteral("sourceEndpoint"), rawContext.endpoint);
|
||||
msg.attributes.insert(QStringLiteral("frameType"), QStringLiteral("request"));
|
||||
msg.attributes.insert(QStringLiteral("startAddress"), static_cast<int>(req->startAddress));
|
||||
msg.attributes.insert(QStringLiteral("quantity"), static_cast<int>(req->quantity));
|
||||
out.push_back(std::move(msg));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void reset() override {}
|
||||
};
|
||||
|
||||
class ModbusRtuProtocolMapperPlugin final : public softbus::core::plugin_system::IProtocolMapperPlugin
|
||||
{
|
||||
public:
|
||||
QString pluginId() const override { return QStringLiteral("modbus_rtu_mapper"); }
|
||||
bool supports(const QString& protocolHint) const override
|
||||
{
|
||||
return isModbusRtuHint(protocolHint);
|
||||
}
|
||||
std::unique_ptr<softbus::core::plugin_system::IProtocolMapperSession> createMapperSession() override
|
||||
{
|
||||
return std::make_unique<ModbusRtuMapperSession>();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<softbus::core::plugin_system::IProtocolMapperPlugin> makeModbusRtuProtocolMapperPlugin()
|
||||
{
|
||||
return std::make_shared<ModbusRtuProtocolMapperPlugin>();
|
||||
}
|
||||
|
||||
softbus::core::plugin_system::IProtocolMapperPlugin* createModbusRtuProtocolMapperPluginRaw()
|
||||
{
|
||||
return new ModbusRtuProtocolMapperPlugin();
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/plugin_system/IProtocolMapperPlugin.h"
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
{
|
||||
|
||||
std::shared_ptr<softbus::core::plugin_system::IProtocolMapperPlugin> makeModbusRtuProtocolMapperPlugin();
|
||||
softbus::core::plugin_system::IProtocolMapperPlugin* createModbusRtuProtocolMapperPluginRaw();
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu
|
||||
|
||||
107
plugins/protocols/modbus_rtu/parser/ModbusRtuProtocolPlugin.cpp
Normal file
107
plugins/protocols/modbus_rtu/parser/ModbusRtuProtocolPlugin.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "plugins/protocols/modbus_rtu/parser/ModbusRtuProtocolPlugin.h"
|
||||
|
||||
#include "core/plugin_system/IProtocolPlugin.h"
|
||||
#include "message_bus/pipeline/PipelineContext.h"
|
||||
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
|
||||
#include "plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h"
|
||||
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace softbus::core::plugin_system
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using softbus::message_bus::pipeline::protocol::ModbusExceptionPdu;
|
||||
using softbus::message_bus::pipeline::protocol::ModbusRtuPdu;
|
||||
using softbus::message_bus::pipeline::protocol::ModbusRtuUnsupportedPdu;
|
||||
using softbus::message_bus::pipeline::protocol::ProtocolFamily;
|
||||
using softbus::message_bus::pipeline::protocol::ReadCoilsRequest;
|
||||
using softbus::message_bus::pipeline::protocol::ReadCoilsResponse;
|
||||
using softbus::message_bus::pipeline::protocol::ReadDiscreteInputsRequest;
|
||||
using softbus::message_bus::pipeline::protocol::ReadDiscreteInputsResponse;
|
||||
using softbus::message_bus::pipeline::protocol::ReadHoldingRegistersRequest;
|
||||
using softbus::message_bus::pipeline::protocol::ReadHoldingRegistersResponse;
|
||||
using softbus::message_bus::pipeline::protocol::ReadInputRegistersRequest;
|
||||
using softbus::message_bus::pipeline::protocol::ReadInputRegistersResponse;
|
||||
using softbus::message_bus::pipeline::protocol::WriteSingleCoilRequest;
|
||||
using softbus::message_bus::pipeline::protocol::WriteSingleCoilResponse;
|
||||
using softbus::message_bus::pipeline::protocol::WriteSingleRegisterRequest;
|
||||
using softbus::message_bus::pipeline::protocol::WriteSingleRegisterResponse;
|
||||
using softbus::message_bus::pipeline::protocol::WriteMultipleCoilsRequest;
|
||||
using softbus::message_bus::pipeline::protocol::WriteMultipleCoilsResponse;
|
||||
using softbus::message_bus::pipeline::protocol::WriteMultipleRegistersRequest;
|
||||
using softbus::message_bus::pipeline::protocol::WriteMultipleRegistersResponse;
|
||||
using softbus::message_bus::pipeline::protocol::decodeModbusRtu;
|
||||
|
||||
template <class... Ts>
|
||||
struct overloaded : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template <class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
class ModbusRtuSession final : public IProtocolSession
|
||||
{
|
||||
public:
|
||||
bool parse(const softbus::message_bus::pipeline::PipelineContext& ctx,
|
||||
softbus::message_bus::pipeline::protocol::ProtocolEnvelope& out) override
|
||||
{
|
||||
using softbus::message_bus::pipeline::FrameKind;
|
||||
if (ctx.frameKind != FrameKind::CompleteFrame || !ctx.payload.valid()) {
|
||||
return false;
|
||||
}
|
||||
const std::size_t n = ctx.payloadSize ? ctx.payloadSize : ctx.payload.length;
|
||||
const std::uint8_t* p = ctx.payload.bytes();
|
||||
if (!p || n < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ModbusRtuPdu pdu;
|
||||
if (!decodeModbusRtu(ctx.direction, p, n, pdu, nullptr)) {
|
||||
out.family = ProtocolFamily::None;
|
||||
out.pdu = {};
|
||||
return false;
|
||||
}
|
||||
out.family = ProtocolFamily::ModbusRtu;
|
||||
out.pdu = pdu;
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset() override {}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class ModbusRtuProtocolPlugin final : public IProtocolPlugin
|
||||
{
|
||||
public:
|
||||
QString pluginId() const override { return QStringLiteral("modbus_rtu"); }
|
||||
|
||||
bool supports(const QString& protocolHint) const override
|
||||
{
|
||||
return softbus::message_bus::pipeline::protocol::modbusrtu::isModbusRtuHint(protocolHint);
|
||||
}
|
||||
|
||||
std::unique_ptr<IProtocolSession> createSession() override
|
||||
{
|
||||
return std::make_unique<ModbusRtuSession>();
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<IProtocolPlugin> makeModbusRtuProtocolPlugin()
|
||||
{
|
||||
return std::make_shared<ModbusRtuProtocolPlugin>();
|
||||
}
|
||||
|
||||
IProtocolPlugin* createModbusRtuProtocolPluginRaw()
|
||||
{
|
||||
return new ModbusRtuProtocolPlugin();
|
||||
}
|
||||
|
||||
} // namespace softbus::core::plugin_system
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
// Modbus RTU 协议解析器插件
|
||||
#include <memory>
|
||||
|
||||
#include "core/plugin_system/IProtocolPlugin.h"
|
||||
|
||||
namespace softbus::core::plugin_system
|
||||
{
|
||||
|
||||
std::shared_ptr<IProtocolPlugin> makeModbusRtuProtocolPlugin();
|
||||
IProtocolPlugin* createModbusRtuProtocolPluginRaw();
|
||||
|
||||
} // namespace softbus::core::plugin_system
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFramerPlugin.h"
|
||||
#include "plugins/protocols/modbusrtu/parser/ModbusRtuProtocolPlugin.h"
|
||||
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.h"
|
||||
#include "plugins/protocols/modbus_rtu/mapper/ModbusRtuProtocolMapperPlugin.h"
|
||||
#include "plugins/protocols/modbus_rtu/parser/ModbusRtuProtocolPlugin.h"
|
||||
|
||||
extern "C" int softbus_plugin_abi_version()
|
||||
{
|
||||
@@ -15,3 +16,8 @@ extern "C" softbus::core::plugin_system::IProtocolFramerPlugin* softbus_create_f
|
||||
{
|
||||
return softbus::message_bus::pipeline::protocol::modbusrtu::createModbusRtuFramerPluginRaw();
|
||||
}
|
||||
|
||||
extern "C" softbus::core::plugin_system::IProtocolMapperPlugin* softbus_create_mapper_plugin()
|
||||
{
|
||||
return softbus::message_bus::pipeline::protocol::modbusrtu::createModbusRtuProtocolMapperPluginRaw();
|
||||
}
|
||||
|
||||
304
plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h
Normal file
304
plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h
Normal file
@@ -0,0 +1,304 @@
|
||||
#pragma once
|
||||
// 定义ModbusRTU PDU 类型 包括
|
||||
// 异常响应、
|
||||
// 读保持寄存器请求、
|
||||
// 读保持寄存器响应、不支持的PDU
|
||||
#include <cstdint>
|
||||
|
||||
#include <QChar>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <type_traits>
|
||||
#include <variant> // 用于定义variant类型 variant类型可以存储多种类型的数据
|
||||
|
||||
namespace softbus::message_bus::pipeline::protocol
|
||||
{
|
||||
|
||||
struct ModbusExceptionPdu
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint8_t function{0};
|
||||
std::uint8_t exceptionCode{0};
|
||||
};
|
||||
// 01 功能码 读线圈
|
||||
struct ReadCoilsRequest
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t startAddress{0};
|
||||
std::uint16_t quantity{0};
|
||||
};
|
||||
// 01 功能码 读线圈响应
|
||||
struct ReadCoilsResponse
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
QVector<std::uint8_t> coils;
|
||||
};
|
||||
|
||||
// 02 功能码 读离散输入
|
||||
struct ReadDiscreteInputsRequest
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t startAddress{0};
|
||||
std::uint16_t quantity{0};
|
||||
};
|
||||
// 02 功能码 读离散输入响应
|
||||
struct ReadDiscreteInputsResponse
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
QVector<std::uint8_t> discreteInputs;
|
||||
};
|
||||
|
||||
// 03 功能码 读保持寄存器
|
||||
struct ReadHoldingRegistersRequest
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t startAddress{0};
|
||||
std::uint16_t quantity{0};
|
||||
};
|
||||
|
||||
// 03 功能码 读保持寄存器响应
|
||||
struct ReadHoldingRegistersResponse
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
QVector<std::uint16_t> registers;
|
||||
};
|
||||
|
||||
// 04 功能码 读输入寄存器
|
||||
struct ReadInputRegistersRequest
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t startAddress{0};
|
||||
std::uint16_t quantity{0};
|
||||
};
|
||||
// 04 功能码 读输入寄存器响应
|
||||
struct ReadInputRegistersResponse
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
QVector<std::uint16_t> inputRegisters;
|
||||
};
|
||||
|
||||
// 05 功能码 写单个线圈
|
||||
struct WriteSingleCoilRequest
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t address{0};
|
||||
std::uint8_t value{0};
|
||||
};
|
||||
// 05 功能码 写单个线圈响应
|
||||
struct WriteSingleCoilResponse
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t address{0};
|
||||
std::uint8_t value{0};
|
||||
};
|
||||
// 06 功能码 写单个寄存器
|
||||
struct WriteSingleRegisterRequest
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t address{0};
|
||||
std::uint16_t value{0};
|
||||
};
|
||||
// 06 功能码 写单个寄存器响应
|
||||
struct WriteSingleRegisterResponse
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t address{0};
|
||||
std::uint16_t value{0};
|
||||
};
|
||||
// 15 功能码 写多个线圈
|
||||
struct WriteMultipleCoilsRequest
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t startAddress{0};
|
||||
std::uint16_t quantity{0};
|
||||
QVector<std::uint8_t> values;
|
||||
};
|
||||
// 15 功能码 写多个线圈响应
|
||||
struct WriteMultipleCoilsResponse
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t startAddress{0};
|
||||
std::uint16_t quantity{0};
|
||||
QVector<std::uint8_t> values;
|
||||
};
|
||||
|
||||
// 16 功能码 写多个寄存器
|
||||
struct WriteMultipleRegistersRequest
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t startAddress{0};
|
||||
std::uint16_t quantity{0};
|
||||
QVector<std::uint16_t> values;
|
||||
};
|
||||
// 16 功能码 写多个寄存器响应
|
||||
struct WriteMultipleRegistersResponse
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint16_t startAddress{0};
|
||||
std::uint16_t quantity{0};
|
||||
QVector<std::uint16_t> values;
|
||||
};
|
||||
|
||||
struct ModbusRtuUnsupportedPdu
|
||||
{
|
||||
std::uint8_t unitId{0};
|
||||
std::uint8_t functionCode{0};
|
||||
};
|
||||
|
||||
using ModbusRtuPdu = std::variant<ModbusExceptionPdu,
|
||||
ReadCoilsRequest,
|
||||
ReadCoilsResponse,
|
||||
ReadDiscreteInputsRequest,
|
||||
ReadDiscreteInputsResponse,
|
||||
ReadHoldingRegistersRequest,
|
||||
ReadHoldingRegistersResponse,
|
||||
ReadInputRegistersRequest,
|
||||
ReadInputRegistersResponse,
|
||||
WriteSingleCoilRequest,
|
||||
WriteSingleCoilResponse,
|
||||
WriteSingleRegisterRequest,
|
||||
WriteSingleRegisterResponse,
|
||||
WriteMultipleCoilsRequest,
|
||||
WriteMultipleCoilsResponse,
|
||||
WriteMultipleRegistersRequest,
|
||||
WriteMultipleRegistersResponse,
|
||||
ModbusRtuUnsupportedPdu>;
|
||||
//
|
||||
|
||||
inline QString toText(const ModbusRtuPdu& pdu)
|
||||
{
|
||||
return std::visit([](const auto& value) -> QString {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
|
||||
auto bytesToHex = [](const QVector<std::uint8_t>& bytes) -> QString {
|
||||
QStringList parts;
|
||||
parts.reserve(bytes.size());
|
||||
for (const std::uint8_t b : bytes) {
|
||||
parts.push_back(QStringLiteral("0x%1").arg(b, 2, 16, QChar('0')));
|
||||
}
|
||||
return QStringLiteral("[%1]").arg(parts.join(QStringLiteral(", ")));
|
||||
};
|
||||
auto wordsToHex = [](const QVector<std::uint16_t>& words) -> QString {
|
||||
QStringList parts;
|
||||
parts.reserve(words.size());
|
||||
for (const std::uint16_t w : words) {
|
||||
parts.push_back(QStringLiteral("0x%1").arg(w, 4, 16, QChar('0')));
|
||||
}
|
||||
return QStringLiteral("[%1]").arg(parts.join(QStringLiteral(", ")));
|
||||
};
|
||||
|
||||
if constexpr (std::is_same_v<T, ModbusExceptionPdu>) {
|
||||
return QStringLiteral("ModbusExceptionPdu: unitId=%1, function=0x%2, exceptionCode=0x%3")
|
||||
.arg(value.unitId)
|
||||
.arg(value.function, 2, 16, QChar('0'))
|
||||
.arg(value.exceptionCode, 2, 16, QChar('0'));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ReadCoilsRequest>) {
|
||||
const ReadCoilsRequest& request = value;
|
||||
return QStringLiteral("ReadCoilsRequest: unitId=%1, startAddress=%2, quantity=%3")
|
||||
.arg(request.unitId)
|
||||
.arg(request.startAddress)
|
||||
.arg(request.quantity);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ReadCoilsResponse>) {
|
||||
return QStringLiteral("ReadCoilsResponse: unitId=%1, coils=%2")
|
||||
.arg(value.unitId)
|
||||
.arg(bytesToHex(value.coils));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ReadDiscreteInputsRequest>) {
|
||||
return QStringLiteral("ReadDiscreteInputsRequest: unitId=%1, startAddress=%2, quantity=%3")
|
||||
.arg(value.unitId)
|
||||
.arg(value.startAddress)
|
||||
.arg(value.quantity);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ReadDiscreteInputsResponse>) {
|
||||
return QStringLiteral("ReadDiscreteInputsResponse: unitId=%1, discreteInputs=%2")
|
||||
.arg(value.unitId)
|
||||
.arg(bytesToHex(value.discreteInputs));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ReadHoldingRegistersRequest>) {
|
||||
return QStringLiteral("ReadHoldingRegistersRequest: unitId=%1, startAddress=%2, quantity=%3")
|
||||
.arg(value.unitId)
|
||||
.arg(value.startAddress)
|
||||
.arg(value.quantity);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ReadHoldingRegistersResponse>) {
|
||||
return QStringLiteral("ReadHoldingRegistersResponse: unitId=%1, registers=%2")
|
||||
.arg(value.unitId)
|
||||
.arg(wordsToHex(value.registers));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ReadInputRegistersRequest>) {
|
||||
return QStringLiteral("ReadInputRegistersRequest: unitId=%1, startAddress=%2, quantity=%3")
|
||||
.arg(value.unitId)
|
||||
.arg(value.startAddress)
|
||||
.arg(value.quantity);
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ReadInputRegistersResponse>) {
|
||||
return QStringLiteral("ReadInputRegistersResponse: unitId=%1, inputRegisters=%2")
|
||||
.arg(value.unitId)
|
||||
.arg(wordsToHex(value.inputRegisters));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, WriteSingleCoilRequest>) {
|
||||
return QStringLiteral("WriteSingleCoilRequest: unitId=%1, address=%2, value=0x%3")
|
||||
.arg(value.unitId)
|
||||
.arg(value.address)
|
||||
.arg(value.value, 2, 16, QChar('0'));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, WriteSingleCoilResponse>) {
|
||||
return QStringLiteral("WriteSingleCoilResponse: unitId=%1, address=%2, value=0x%3")
|
||||
.arg(value.unitId)
|
||||
.arg(value.address)
|
||||
.arg(value.value, 2, 16, QChar('0'));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, WriteSingleRegisterRequest>) {
|
||||
return QStringLiteral("WriteSingleRegisterRequest: unitId=%1, address=%2, value=0x%3")
|
||||
.arg(value.unitId)
|
||||
.arg(value.address)
|
||||
.arg(value.value, 4, 16, QChar('0'));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, WriteSingleRegisterResponse>) {
|
||||
return QStringLiteral("WriteSingleRegisterResponse: unitId=%1, address=%2, value=0x%3")
|
||||
.arg(value.unitId)
|
||||
.arg(value.address)
|
||||
.arg(value.value, 4, 16, QChar('0'));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, WriteMultipleCoilsRequest>) {
|
||||
return QStringLiteral("WriteMultipleCoilsRequest: unitId=%1, startAddress=%2, quantity=%3, values=%4")
|
||||
.arg(value.unitId)
|
||||
.arg(value.startAddress)
|
||||
.arg(value.quantity)
|
||||
.arg(bytesToHex(value.values));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, WriteMultipleCoilsResponse>) {
|
||||
return QStringLiteral("WriteMultipleCoilsResponse: unitId=%1, startAddress=%2, quantity=%3, values=%4")
|
||||
.arg(value.unitId)
|
||||
.arg(value.startAddress)
|
||||
.arg(value.quantity)
|
||||
.arg(bytesToHex(value.values));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, WriteMultipleRegistersRequest>) {
|
||||
return QStringLiteral("WriteMultipleRegistersRequest: unitId=%1, startAddress=%2, quantity=%3, values=%4")
|
||||
.arg(value.unitId)
|
||||
.arg(value.startAddress)
|
||||
.arg(value.quantity)
|
||||
.arg(wordsToHex(value.values));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, WriteMultipleRegistersResponse>) {
|
||||
return QStringLiteral("WriteMultipleRegistersResponse: unitId=%1, startAddress=%2, quantity=%3, values=%4")
|
||||
.arg(value.unitId)
|
||||
.arg(value.startAddress)
|
||||
.arg(value.quantity)
|
||||
.arg(wordsToHex(value.values));
|
||||
}
|
||||
if constexpr (std::is_same_v<T, ModbusRtuUnsupportedPdu>) {
|
||||
return QStringLiteral("ModbusRtuUnsupportedPdu: unitId=%1, functionCode=0x%2")
|
||||
.arg(value.unitId)
|
||||
.arg(value.functionCode, 2, 16, QChar('0'));
|
||||
}
|
||||
return QStringLiteral("Unknown");
|
||||
}, pdu);
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::pipeline::protocol
|
||||
|
||||
Reference in New Issue
Block a user