modbus rtu

This commit is contained in:
flower_linux
2026-04-03 16:23:56 +08:00
parent 1da5fe12c3
commit 28b5082742
32 changed files with 3231 additions and 1 deletions

View File

@@ -0,0 +1,144 @@
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFramer.h"
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFraming.h"
#include <QtTest/QtTest>
#include <cstring>
#include <vector>
using softbus::core::memory::MemoryPool;
using softbus::message_bus::pipeline::FrameKind;
using softbus::message_bus::pipeline::PipelineContext;
using softbus::message_bus::pipeline::stages::framers::ModbusRtuFramer;
using softbus::message_bus::pipeline::stages::framers::ModbusRtuFramerConfig;
using softbus::message_bus::pipeline::stages::framers::modbusRtuCrc16;
namespace
{
void appendCrcLe(std::vector<std::uint8_t>& v)
{
const std::uint16_t c = modbusRtuCrc16(v.data(), v.size());
v.push_back(static_cast<std::uint8_t>(c & 0xFF));
v.push_back(static_cast<std::uint8_t>(c >> 8));
}
MemoryPool::BlockPtr makeBlock(MemoryPool& pool, const std::vector<std::uint8_t>& bytes)
{
auto blk = pool.allocate(bytes.size());
if (!blk || !blk->data) {
QTest::qFail("pool.allocate failed", __FILE__, __LINE__);
return {};
}
std::memcpy(blk->data, bytes.data(), bytes.size());
blk->size = bytes.size();
return blk;
}
} // namespace
class ModbusRtuFramerTest : public QObject
{
Q_OBJECT
private slots:
void half_then_rest_emits_one_frame();
void sticky_two_frames_in_one_chunk();
void crc_error_triggers_resync_and_recovery();
};
void ModbusRtuFramerTest::half_then_rest_emits_one_frame()
{
auto pool = std::make_shared<MemoryPool>(512, 64);
ModbusRtuFramer framer(pool, ModbusRtuFramerConfig{}, QStringLiteral("/ttyUSB0"));
std::vector<std::uint8_t> a{0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78};
appendCrcLe(a);
std::vector<std::uint8_t> part1(a.begin(), a.begin() + 4);
std::vector<std::uint8_t> part2(a.begin() + 4, a.end());
PipelineContext c1;
c1.endpoint = QStringLiteral("/ttyUSB0");
c1.protocolHint = QStringLiteral("modbus_rtu");
c1.frameKind = FrameKind::StreamChunk;
c1.payload.block = makeBlock(*pool, part1);
c1.payload.offset = 0;
c1.payload.length = part1.size();
c1.payloadSize = part1.size();
std::vector<PipelineContext> out;
framer.feed(c1, out);
QCOMPARE(int(out.size()), 0);
PipelineContext c2;
c2.endpoint = QStringLiteral("/ttyUSB0");
c2.protocolHint = QStringLiteral("modbus_rtu");
c2.frameKind = FrameKind::StreamChunk;
c2.payload.block = makeBlock(*pool, part2);
c2.payload.offset = 0;
c2.payload.length = part2.size();
c2.payloadSize = part2.size();
framer.feed(c2, out);
QCOMPARE(int(out.size()), 1);
QCOMPARE(out[0].frameKind, FrameKind::CompleteFrame);
QCOMPARE(out[0].payloadSize, a.size());
}
void ModbusRtuFramerTest::sticky_two_frames_in_one_chunk()
{
auto pool = std::make_shared<MemoryPool>(512, 64);
ModbusRtuFramer framer(pool, ModbusRtuFramerConfig{}, QStringLiteral("/ttyUSB0"));
std::vector<std::uint8_t> f;
f.insert(f.end(), {0x01, 0x03, 0x02, 0x00, 0x01});
appendCrcLe(f);
std::vector<std::uint8_t> g;
g.insert(g.end(), {0x02, 0x03, 0x04, 0xAA, 0xBB, 0xCC, 0xDD});
appendCrcLe(g);
std::vector<std::uint8_t> both;
both.insert(both.end(), f.begin(), f.end());
both.insert(both.end(), g.begin(), g.end());
PipelineContext c;
c.endpoint = QStringLiteral("/ttyUSB0");
c.protocolHint = QStringLiteral("modbus_rtu");
c.frameKind = FrameKind::StreamChunk;
c.payload.block = makeBlock(*pool, both);
c.payload.offset = 0;
c.payload.length = both.size();
c.payloadSize = both.size();
std::vector<PipelineContext> out;
framer.feed(c, out);
QCOMPARE(int(out.size()), 2);
}
void ModbusRtuFramerTest::crc_error_triggers_resync_and_recovery()
{
auto pool = std::make_shared<MemoryPool>(512, 64);
ModbusRtuFramer framer(pool, ModbusRtuFramerConfig{}, QStringLiteral("/ttyUSB0"));
std::vector<std::uint8_t> bad{0x01, 0x03, 0x02, 0x00, 0x00, 0xDE, 0xAD};
std::vector<std::uint8_t> good;
good.insert(good.end(), {0x01, 0x03, 0x02, 0x12, 0x34});
appendCrcLe(good);
std::vector<std::uint8_t> stream;
stream.insert(stream.end(), bad.begin(), bad.end());
stream.insert(stream.end(), good.begin(), good.end());
PipelineContext c;
c.endpoint = QStringLiteral("/ttyUSB0");
c.protocolHint = QStringLiteral("modbus_rtu");
c.frameKind = FrameKind::StreamChunk;
c.payload.block = makeBlock(*pool, stream);
c.payload.offset = 0;
c.payload.length = stream.size();
c.payloadSize = stream.size();
std::vector<PipelineContext> out;
framer.feed(c, out);
QVERIFY(!out.empty());
QCOMPARE(out[0].payloadSize, good.size());
}
QTEST_MAIN(ModbusRtuFramerTest)
#include "modbus_rtu_framer_test.moc"

View File

@@ -0,0 +1,206 @@
#include "message_bus/pipeline/protocol/modbusrtu/ModbusRtuCodec.h"
#include <QtTest/QtTest>
#include <variant>
#include "message_bus/pipeline/PipelineContext.h"
using softbus::message_bus::pipeline::BusDirection;
using softbus::message_bus::pipeline::protocol::ModbusExceptionPdu;
using softbus::message_bus::pipeline::protocol::ModbusRtuPdu;
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::decodeModbusRtu;
using softbus::message_bus::pipeline::protocol::encodeModbusRtu;
class ModbusRtuPduCodecTest : public QObject
{
Q_OBJECT
private slots:
void roundTrip_readCoilsRequest();
void roundTrip_readCoilsResponse();
void roundTrip_readDiscreteInputsRequest();
void roundTrip_readDiscreteInputsResponse();
void roundTrip_readHoldingRegistersRequest();
void roundTrip_readHoldingRegistersResponse();
void roundTrip_exception();
void unknownDirection_heuristic_request_vs_response();
};
void ModbusRtuPduCodecTest::roundTrip_readHoldingRegistersRequest()
{
const ReadHoldingRegistersRequest req{0x01, 40001, 4};
QByteArray adu;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{req}, adu, nullptr));
QCOMPARE(adu.size(), 8);
ModbusRtuPdu out;
QVERIFY(decodeModbusRtu(BusDirection::Downstream,
reinterpret_cast<const std::uint8_t*>(adu.constData()),
static_cast<std::size_t>(adu.size()),
out,
nullptr));
const auto* back = std::get_if<ReadHoldingRegistersRequest>(&out);
QVERIFY(back);
QCOMPARE(static_cast<int>(back->unitId), 1);
QCOMPARE(static_cast<int>(back->startAddress), 40001);
QCOMPARE(static_cast<int>(back->quantity), 4);
}
void ModbusRtuPduCodecTest::roundTrip_readCoilsRequest()
{
const ReadCoilsRequest req{0x01, 0x0010, 8};
QByteArray adu;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{req}, adu, nullptr));
QCOMPARE(adu.size(), 8);
ModbusRtuPdu out;
QVERIFY(decodeModbusRtu(BusDirection::Downstream,
reinterpret_cast<const std::uint8_t*>(adu.constData()),
static_cast<std::size_t>(adu.size()),
out,
nullptr));
const auto* back = std::get_if<ReadCoilsRequest>(&out);
QVERIFY(back);
QCOMPARE(static_cast<int>(back->startAddress), 0x10);
QCOMPARE(static_cast<int>(back->quantity), 8);
}
void ModbusRtuPduCodecTest::roundTrip_readCoilsResponse()
{
ReadCoilsResponse resp;
resp.unitId = 0x01;
resp.coils = QVector<std::uint8_t>{0x55, 0x01};
QByteArray adu;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{resp}, adu, nullptr));
ModbusRtuPdu out;
QVERIFY(decodeModbusRtu(BusDirection::Upstream,
reinterpret_cast<const std::uint8_t*>(adu.constData()),
static_cast<std::size_t>(adu.size()),
out,
nullptr));
const auto* back = std::get_if<ReadCoilsResponse>(&out);
QVERIFY(back);
QCOMPARE(back->coils.size(), 2);
QCOMPARE(static_cast<int>(back->coils[0]), 0x55);
}
void ModbusRtuPduCodecTest::roundTrip_readDiscreteInputsRequest()
{
const ReadDiscreteInputsRequest req{0x01, 0x0020, 16};
QByteArray adu;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{req}, adu, nullptr));
ModbusRtuPdu out;
QVERIFY(decodeModbusRtu(BusDirection::Downstream,
reinterpret_cast<const std::uint8_t*>(adu.constData()),
static_cast<std::size_t>(adu.size()),
out,
nullptr));
const auto* back = std::get_if<ReadDiscreteInputsRequest>(&out);
QVERIFY(back);
QCOMPARE(static_cast<int>(back->startAddress), 0x20);
QCOMPARE(static_cast<int>(back->quantity), 16);
}
void ModbusRtuPduCodecTest::roundTrip_readDiscreteInputsResponse()
{
ReadDiscreteInputsResponse resp;
resp.unitId = 0x01;
resp.discreteInputs = QVector<std::uint8_t>{0x0F};
QByteArray adu;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{resp}, adu, nullptr));
ModbusRtuPdu out;
QVERIFY(decodeModbusRtu(BusDirection::Upstream,
reinterpret_cast<const std::uint8_t*>(adu.constData()),
static_cast<std::size_t>(adu.size()),
out,
nullptr));
const auto* back = std::get_if<ReadDiscreteInputsResponse>(&out);
QVERIFY(back);
QCOMPARE(back->discreteInputs.size(), 1);
QCOMPARE(static_cast<int>(back->discreteInputs[0]), 0x0F);
}
void ModbusRtuPduCodecTest::roundTrip_readHoldingRegistersResponse()
{
ReadHoldingRegistersResponse resp;
resp.unitId = 0x02;
resp.registers = QVector<std::uint16_t>{0x00FF, 0x1388};
QByteArray adu;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{resp}, adu, nullptr));
ModbusRtuPdu out;
QVERIFY(decodeModbusRtu(BusDirection::Upstream,
reinterpret_cast<const std::uint8_t*>(adu.constData()),
static_cast<std::size_t>(adu.size()),
out,
nullptr));
const auto* back = std::get_if<ReadHoldingRegistersResponse>(&out);
QVERIFY(back);
QCOMPARE(static_cast<int>(back->unitId), 2);
QCOMPARE(back->registers.size(), 2);
QCOMPARE(static_cast<int>(back->registers[0]), 255);
QCOMPARE(static_cast<int>(back->registers[1]), 5000);
}
void ModbusRtuPduCodecTest::roundTrip_exception()
{
const ModbusExceptionPdu ex{0x05, static_cast<std::uint8_t>(0x83), 0x02};
QByteArray adu;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{ex}, adu, nullptr));
QCOMPARE(adu.size(), 5);
ModbusRtuPdu out;
QVERIFY(decodeModbusRtu(BusDirection::Upstream,
reinterpret_cast<const std::uint8_t*>(adu.constData()),
static_cast<std::size_t>(adu.size()),
out,
nullptr));
const auto* back = std::get_if<ModbusExceptionPdu>(&out);
QVERIFY(back);
QCOMPARE(static_cast<int>(back->unitId), 5);
QCOMPARE(static_cast<int>(back->function), 0x83);
QCOMPARE(static_cast<int>(back->exceptionCode), 2);
}
void ModbusRtuPduCodecTest::unknownDirection_heuristic_request_vs_response()
{
ReadHoldingRegistersRequest req{0x01, 0, 1};
QByteArray aduReq;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{req}, aduReq, nullptr));
ModbusRtuPdu asReq;
QVERIFY(decodeModbusRtu(BusDirection::Unknown,
reinterpret_cast<const std::uint8_t*>(aduReq.constData()),
static_cast<std::size_t>(aduReq.size()),
asReq,
nullptr));
QVERIFY(std::holds_alternative<ReadHoldingRegistersRequest>(asReq));
ReadHoldingRegistersResponse resp;
resp.unitId = 1;
resp.registers = {0x00AA};
QByteArray aduResp;
QVERIFY(encodeModbusRtu(ModbusRtuPdu{resp}, aduResp, nullptr));
ModbusRtuPdu asResp;
QVERIFY(decodeModbusRtu(BusDirection::Unknown,
reinterpret_cast<const std::uint8_t*>(aduResp.constData()),
static_cast<std::size_t>(aduResp.size()),
asResp,
nullptr));
QVERIFY(std::holds_alternative<ReadHoldingRegistersResponse>(asResp));
}
QTEST_MAIN(ModbusRtuPduCodecTest)
#include "modbus_rtu_pdu_codec_test.moc"

View File

@@ -0,0 +1,90 @@
#include "message_bus/pipeline/stages/3_filters/ProtocolParseFilter.h"
#include <QtTest/QtTest>
#include <QDir>
#include <QFile>
#include <variant>
#include "message_bus/pipeline/protocol/modbusrtu/ModbusRtuPdu.h"
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
using softbus::message_bus::pipeline::BusDirection;
using softbus::message_bus::pipeline::PipelineContext;
using softbus::message_bus::pipeline::protocol::ModbusRtuPdu;
using softbus::message_bus::pipeline::protocol::ProtocolFamily;
using softbus::message_bus::pipeline::protocol::ReadHoldingRegistersResponse;
using softbus::message_bus::pipeline::stages::filters::ProtocolParseFilter;
class ProtocolParseFilterTest : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void parses_multiple_dom_messages();
private:
QString m_cfgDir;
};
void ProtocolParseFilterTest::initTestCase()
{
m_cfgDir = QDir::tempPath() + QStringLiteral("/softbus_test_cfg");
QDir().mkpath(m_cfgDir);
QFile mf(m_cfgDir + QStringLiteral("/metadata_dictionary.json"));
QVERIFY(mf.open(QIODevice::WriteOnly | QIODevice::Truncate));
mf.write(R"({
"metadatas":[
{"metadataId":"MD_TEMP_001","name":"Temp","description":"t","domain":"PROCESS_PARAM","semanticType":"FLOAT64","unit":"C","minValue":-40,"maxValue":180,"deadband":0.1,"isWriteable":false},
{"metadataId":"MD_PRESS_001","name":"Press","description":"p","domain":"PROCESS_PARAM","semanticType":"FLOAT64","unit":"MPa","minValue":0,"maxValue":25,"deadband":0.01,"isWriteable":false}
]
})");
mf.close();
QFile pf(m_cfgDir + QStringLiteral("/device_profiles.json"));
QVERIFY(pf.open(QIODevice::WriteOnly | QIODevice::Truncate));
pf.write(R"({
"profiles":[
{"deviceId":"devA","protocol":"modbus_rtu","mappings":[
{"pointId":"temperature","metadataId":"MD_TEMP_001","byteOffset":0,"byteLength":4,"rawType":"INT32","endianness":"BIG_ENDIAN","scaleFactor":1.0,"offsetFactor":0.0,"expression":"x*0.1"},
{"pointId":"pressure","metadataId":"MD_PRESS_001","byteOffset":4,"byteLength":4,"rawType":"INT32","endianness":"BIG_ENDIAN","scaleFactor":1.0,"offsetFactor":0.0,"expression":"x*0.001"}
]}
]
})");
pf.close();
qputenv("SOFTBUS_CONFIG_DIR", m_cfgDir.toUtf8());
}
void ProtocolParseFilterTest::parses_multiple_dom_messages()
{
ProtocolParseFilter filter;
PipelineContext ctx;
ctx.direction = BusDirection::Upstream;
ctx.protocolHint = QStringLiteral("modbus_rtu");
ctx.stableKey = QStringLiteral("devA");
ctx.deviceId = 1;
ctx.timestamp = QDateTime::currentDateTimeUtc();
ReadHoldingRegistersResponse resp;
resp.unitId = 1;
resp.registers = QVector<std::uint16_t>{0, 255, 0, 5000};
ctx.protocol.family = ProtocolFamily::ModbusRtu;
ctx.protocol.pdu = ModbusRtuPdu{std::move(resp)};
QVERIFY(filter.apply(ctx));
QCOMPARE(ctx.domMessages.size(), 2);
QCOMPARE(ctx.domMessages[0].messageId, QStringLiteral("devA.temperature"));
QCOMPARE(ctx.domMessages[0].quality, softbus::core::models::DataQuality::GOOD);
QCOMPARE(ctx.domMessages[0].domain(), softbus::core::models::DataDomain::PROCESS_PARAM);
QCOMPARE(ctx.domMessages[0].getValueAs<double>(), 25.5);
QCOMPARE(ctx.domMessages[1].messageId, QStringLiteral("devA.pressure"));
QCOMPARE(ctx.domMessages[1].getValueAs<double>(), 5.0);
}
QTEST_MAIN(ProtocolParseFilterTest)
#include "protocol_parse_filter_test.moc"