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

4
.gitignore vendored
View File

@@ -34,7 +34,9 @@ Thumbs.db
*.rc
/.qmake.cache
/.qmake.stash
/.cursor/
/.cache/
/.vscode/
# qtcreator generated files
*.pro.user*
*.qbs.user*

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,36 @@
{
"profiles": [
{
"deviceId": "*",
"protocol": "modbus_rtu",
"mappings": [
{
"pointId": "temperature",
"metadataId": "MD_TEMP_001",
"registerAddress": 40001,
"byteOffset": 0,
"byteLength": 4,
"bitOffset": -1,
"endianness": "BIG_ENDIAN",
"rawType": "INT32",
"scaleFactor": 1.0,
"offsetFactor": 0.0,
"expression": "x * 0.1"
},
{
"pointId": "pressure",
"metadataId": "MD_PRESS_001",
"registerAddress": 40003,
"byteOffset": 4,
"byteLength": 4,
"bitOffset": -1,
"endianness": "BIG_ENDIAN",
"rawType": "INT32",
"scaleFactor": 1.0,
"offsetFactor": 0.0,
"expression": "x * 0.001"
}
]
}
]
}

View File

@@ -0,0 +1,28 @@
{
"metadatas": [
{
"metadataId": "MD_TEMP_001",
"name": "主管道温度",
"description": "由两个寄存器组合得到温度",
"domain": "PROCESS_PARAM",
"semanticType": "FLOAT64",
"unit": "C",
"minValue": -40.0,
"maxValue": 180.0,
"deadband": 0.1,
"isWriteable": false
},
{
"metadataId": "MD_PRESS_001",
"name": "主管道压力",
"description": "由两个寄存器组合得到压力",
"domain": "PROCESS_PARAM",
"semanticType": "FLOAT64",
"unit": "MPa",
"minValue": 0.0,
"maxValue": 25.0,
"deadband": 0.01,
"isWriteable": false
}
]
}

View File

@@ -0,0 +1,133 @@
#include "core/metadata/MetadataRegistry.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
namespace softbus::core::metadata
{
namespace
{
using softbus::core::models::DataDomain;
using softbus::core::models::DataType;
DataDomain parseDomain(const QString& s)
{
const QString x = s.trimmed().toUpper();
if (x == QStringLiteral("PROCESS_PARAM")) {
return DataDomain::PROCESS_PARAM;
}
if (x == QStringLiteral("CONTROL_CMD")) {
return DataDomain::CONTROL_CMD;
}
if (x == QStringLiteral("MONITOR_DATA")) {
return DataDomain::MONITOR_DATA;
}
if (x == QStringLiteral("HEALTH_DIAG")) {
return DataDomain::HEALTH_DIAG;
}
if (x == QStringLiteral("CONFIG_PARAM")) {
return DataDomain::CONFIG_PARAM;
}
return DataDomain::UNKNOWN;
}
DataType parseDataType(const QString& s)
{
const QString x = s.trimmed().toUpper();
if (x == QStringLiteral("BOOL")) {
return DataType::BOOL;
}
if (x == QStringLiteral("INT32")) {
return DataType::INT32;
}
if (x == QStringLiteral("UINT32")) {
return DataType::UINT32;
}
if (x == QStringLiteral("INT64")) {
return DataType::INT64;
}
if (x == QStringLiteral("FLOAT32")) {
return DataType::FLOAT32;
}
if (x == QStringLiteral("FLOAT64")) {
return DataType::FLOAT64;
}
if (x == QStringLiteral("STRING")) {
return DataType::STRING;
}
if (x == QStringLiteral("BYTE_ARRAY")) {
return DataType::BYTE_ARRAY;
}
return DataType::FLOAT64;
}
} // namespace
bool MetadataRegistry::loadFromFile(const QString& filePath)
{
m_lastError.clear();
m_defsById.clear();
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
m_lastError = QStringLiteral("failed to open metadata file: %1").arg(filePath);
return false;
}
QJsonParseError pe;
const auto doc = QJsonDocument::fromJson(f.readAll(), &pe);
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
m_lastError = QStringLiteral("invalid metadata json: %1").arg(pe.errorString());
return false;
}
const auto root = doc.object();
const auto arr = root.value(QStringLiteral("metadatas")).toArray();
for (const auto& item : arr) {
if (!item.isObject()) {
continue;
}
const auto one = parseOne(item.toObject());
if (!one.has_value() || one->metadataId.isEmpty()) {
continue;
}
m_defsById.insert(one->metadataId, *one);
}
if (m_defsById.isEmpty()) {
m_lastError = QStringLiteral("metadata file contains no valid entries");
return false;
}
return true;
}
const softbus::core::models::MetadataDef* MetadataRegistry::findById(const QString& metadataId) const
{
const auto it = m_defsById.constFind(metadataId);
if (it == m_defsById.constEnd()) {
return nullptr;
}
return &it.value();
}
std::optional<softbus::core::models::MetadataDef> MetadataRegistry::parseOne(const QJsonObject& o)
{
softbus::core::models::MetadataDef def;
def.metadataId = o.value(QStringLiteral("metadataId")).toString();
def.name = o.value(QStringLiteral("name")).toString();
def.description = o.value(QStringLiteral("description")).toString();
def.domain = parseDomain(o.value(QStringLiteral("domain")).toString());
def.semanticType = parseDataType(o.value(QStringLiteral("semanticType")).toString());
def.unit = o.value(QStringLiteral("unit")).toString();
def.minValue = o.value(QStringLiteral("minValue")).toDouble(0.0);
def.maxValue = o.value(QStringLiteral("maxValue")).toDouble(0.0);
def.deadband = o.value(QStringLiteral("deadband")).toDouble(0.0);
def.isWriteable = o.value(QStringLiteral("isWriteable")).toBool(false);
return def;
}
} // namespace softbus::core::metadata

View File

@@ -0,0 +1,31 @@
#pragma once
#include <optional>
#include <QHash>
#include <QJsonObject>
#include <QString>
#include "core/models/MetadataDef.h"
namespace softbus::core::metadata
{
class MetadataRegistry
{
public:
bool loadFromFile(const QString& filePath);
const softbus::core::models::MetadataDef* findById(const QString& metadataId) const;
bool empty() const { return m_defsById.isEmpty(); }
const QString& lastError() const { return m_lastError; }
private:
static std::optional<softbus::core::models::MetadataDef> parseOne(const QJsonObject& o);
private:
QHash<QString, softbus::core::models::MetadataDef> m_defsById;
QString m_lastError;
};
} // namespace softbus::core::metadata

View File

@@ -0,0 +1,160 @@
#include "core/metadata/ProfileRegistry.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
namespace softbus::core::metadata
{
namespace
{
QString normalizedProtocol(const QString& protocol)
{
return protocol.trimmed().toLower();
}
} // namespace
bool ProfileRegistry::loadFromFile(const QString& filePath)
{
m_lastError.clear();
m_profilesByKey.clear();
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
m_lastError = QStringLiteral("failed to open profile file: %1").arg(filePath);
return false;
}
QJsonParseError pe;
const auto doc = QJsonDocument::fromJson(f.readAll(), &pe);
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
m_lastError = QStringLiteral("invalid profile json: %1").arg(pe.errorString());
return false;
}
const auto root = doc.object();
const auto arr = root.value(QStringLiteral("profiles")).toArray();
for (const auto& item : arr) {
if (!item.isObject()) {
continue;
}
const auto obj = item.toObject();
softbus::core::models::DeviceDataProfile p;
p.deviceId = obj.value(QStringLiteral("deviceId")).toString();
p.protocol = normalizedProtocol(obj.value(QStringLiteral("protocol")).toString());
const auto mappings = obj.value(QStringLiteral("mappings")).toArray();
for (const auto& m : mappings) {
if (!m.isObject()) {
continue;
}
const auto one = parseMapping(m.toObject());
if (one.has_value()) {
p.mappings.append(*one);
}
}
if (p.deviceId.isEmpty() || p.protocol.isEmpty() || p.mappings.isEmpty()) {
continue;
}
m_profilesByKey.insert(makeKey(p.deviceId, p.protocol), std::move(p));
}
if (m_profilesByKey.isEmpty()) {
m_lastError = QStringLiteral("profile file contains no valid entries");
return false;
}
return true;
}
const softbus::core::models::DeviceDataProfile* ProfileRegistry::find(const QString& deviceId,
const QString& protocol) const
{
const auto protocolNorm = normalizedProtocol(protocol);
const auto exactIt = m_profilesByKey.constFind(makeKey(deviceId, protocolNorm));
if (exactIt != m_profilesByKey.constEnd()) {
return &exactIt.value();
}
const auto wildcard = m_profilesByKey.constFind(makeKey(QStringLiteral("*"), protocolNorm));
if (wildcard != m_profilesByKey.constEnd()) {
return &wildcard.value();
}
return nullptr;
}
QString ProfileRegistry::makeKey(const QString& deviceId, const QString& protocol)
{
return deviceId + QStringLiteral("::") + protocol;
}
std::optional<softbus::core::models::DataPointMapping> ProfileRegistry::parseMapping(
const QJsonObject& o)
{
softbus::core::models::DataPointMapping m;
m.pointId = o.value(QStringLiteral("pointId")).toString();
m.metadataId = o.value(QStringLiteral("metadataId")).toString();
m.registerAddress = static_cast<std::uint32_t>(
o.value(QStringLiteral("registerAddress")).toInt(static_cast<int>(m.registerAddress)));
m.byteOffset = static_cast<std::uint16_t>(o.value(QStringLiteral("byteOffset")).toInt(0));
m.byteLength = static_cast<std::uint16_t>(o.value(QStringLiteral("byteLength")).toInt(0));
m.bitOffset = static_cast<std::int8_t>(o.value(QStringLiteral("bitOffset")).toInt(-1));
m.endianness = parseEndianness(o.value(QStringLiteral("endianness")).toString());
m.rawType = parseDataType(o.value(QStringLiteral("rawType")).toString());
m.scaleFactor = o.value(QStringLiteral("scaleFactor")).toDouble(1.0);
m.offsetFactor = o.value(QStringLiteral("offsetFactor")).toDouble(0.0);
m.expression = o.value(QStringLiteral("expression")).toString();
if (m.pointId.isEmpty() || m.metadataId.isEmpty() || m.byteLength == 0) {
return std::nullopt;
}
return m;
}
softbus::core::models::DataType ProfileRegistry::parseDataType(const QString& s)
{
const QString x = s.trimmed().toUpper();
using softbus::core::models::DataType;
if (x == QStringLiteral("BOOL")) {
return DataType::BOOL;
}
if (x == QStringLiteral("INT32")) {
return DataType::INT32;
}
if (x == QStringLiteral("UINT32")) {
return DataType::UINT32;
}
if (x == QStringLiteral("INT64")) {
return DataType::INT64;
}
if (x == QStringLiteral("FLOAT32")) {
return DataType::FLOAT32;
}
if (x == QStringLiteral("FLOAT64")) {
return DataType::FLOAT64;
}
if (x == QStringLiteral("STRING")) {
return DataType::STRING;
}
if (x == QStringLiteral("BYTE_ARRAY")) {
return DataType::BYTE_ARRAY;
}
return DataType::INT32;
}
softbus::core::models::Endianness ProfileRegistry::parseEndianness(const QString& s)
{
const QString x = s.trimmed().toUpper();
using softbus::core::models::Endianness;
if (x == QStringLiteral("LITTLE_ENDIAN")) {
return Endianness::LittleEndian;
}
if (x == QStringLiteral("BIG_ENDIAN_BYTE_SWAP")) {
return Endianness::BigEndianByteSwap;
}
return Endianness::BigEndian;
}
} // namespace softbus::core::metadata

View File

@@ -0,0 +1,35 @@
#pragma once
#include <optional>
#include <QHash>
#include <QJsonObject>
#include <QString>
#include "core/models/DataMapping.h"
namespace softbus::core::metadata
{
class ProfileRegistry
{
public:
bool loadFromFile(const QString& filePath);
const softbus::core::models::DeviceDataProfile* find(const QString& deviceId,
const QString& protocol) const;
const QString& lastError() const { return m_lastError; }
bool empty() const { return m_profilesByKey.isEmpty(); }
private:
static QString makeKey(const QString& deviceId, const QString& protocol);
static std::optional<softbus::core::models::DataPointMapping> parseMapping(const QJsonObject& o);
static softbus::core::models::DataType parseDataType(const QString& s);
static softbus::core::models::Endianness parseEndianness(const QString& s);
private:
QHash<QString, softbus::core::models::DeviceDataProfile> m_profilesByKey;
QString m_lastError;
};
} // namespace softbus::core::metadata

View File

@@ -0,0 +1,39 @@
#pragma once
// 定义DOM消息
#include <cstdint>
#include <variant>
#include <QString>
#include "core/models/MetadataDef.h"
#include "core/models/Types.h"
namespace softbus::core::models
{
using DOMValue = std::variant<bool, std::int32_t, std::uint32_t, std::int64_t, float, double>;
class DOMMessage
{
public:
DOMMessage() = default;
QString messageId;
const MetadataDef* metaRef{nullptr};
std::int64_t timestamp{0};
DOMValue value{0.0};
DataQuality quality{DataQuality::BAD};
DataDomain domain() const { return metaRef ? metaRef->domain : DataDomain::UNKNOWN; }
template <typename T>
T getValueAs() const
{
if (const auto* p = std::get_if<T>(&value)) {
return *p;
}
return T{};
}
};
} // namespace softbus::core::models

View File

@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <QString>
#include <QVector>
#include "core/models/Types.h"
namespace softbus::core::models
{
enum class Endianness : std::uint8_t
{
BigEndian = 0,
LittleEndian,
BigEndianByteSwap
};
struct DataPointMapping
{
QString pointId;
QString metadataId;
std::uint32_t registerAddress{0xFFFFFFFFu}; // 可选:用于按地址匹配
std::uint16_t byteOffset{0};
std::uint16_t byteLength{0};
std::int8_t bitOffset{-1};
Endianness endianness{Endianness::BigEndian};
DataType rawType{DataType::INT32};
double scaleFactor{1.0};
double offsetFactor{0.0};
QString expression;
};
struct DeviceDataProfile
{
QString deviceId;
QString protocol;
QVector<DataPointMapping> mappings;
};
} // namespace softbus::core::models

View File

@@ -0,0 +1,24 @@
#pragma once
// 定义元数据定义
#include <QString>
#include "core/models/Types.h"
namespace softbus::core::models
{
struct MetadataDef
{
QString metadataId;
QString name;
QString description;
DataDomain domain{DataDomain::UNKNOWN};
DataType semanticType{DataType::FLOAT64};
QString unit;
double minValue{0.0};
double maxValue{0.0};
double deadband{0.0};
bool isWriteable{false};
};
} // namespace softbus::core::models

38
src/core/models/Types.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
// 定义数据域类型、数据质量类型、数据类型
#include <cstdint>
namespace softbus::core::models
{
enum class DataDomain : std::uint8_t
{
PROCESS_PARAM = 0, // 过程参数
CONTROL_CMD = 1, // 控制指令
MONITOR_DATA = 2,
HEALTH_DIAG = 3,
CONFIG_PARAM = 4,
UNKNOWN = 255
};
enum class DataQuality : std::uint8_t
{
GOOD = 0,
BAD = 1,
UNCERTAIN = 2,
STALE = 3
};
enum class DataType : std::uint8_t
{
BOOL = 0,
INT32,
UINT32,
INT64,
FLOAT32,
FLOAT64,
STRING,
BYTE_ARRAY
};
} // namespace softbus::core::models

View File

@@ -0,0 +1,79 @@
#pragma once
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <mutex>
#include <atomic>
namespace softbus::core::threading
{
/// 多生产者多消费者安全队列(互斥 + 条件变量)。
template <typename T>
class MutexQueue
{
public:
explicit MutexQueue(std::size_t maxDepth = 0)
: m_maxDepth(maxDepth)
{
}
bool tryPush(T value)
{
std::lock_guard<std::mutex> lk(m_mutex);
if (m_maxDepth > 0 && m_queue.size() >= m_maxDepth) {
return false;
}
m_queue.push_back(std::move(value));
m_cv.notify_all();
return true;
}
void push(T value)
{
std::lock_guard<std::mutex> lk(m_mutex);
m_queue.push_back(std::move(value));
m_cv.notify_all();
}
/// `running=false` 且队列空时返回 false。
bool popWait(T& out, const std::atomic<bool>& running)
{
std::unique_lock<std::mutex> lk(m_mutex);
m_cv.wait(lk, [&] { return !m_queue.empty() || !running.load(std::memory_order_acquire); });
if (m_queue.empty()) {
return false;
}
out = std::move(m_queue.front());
m_queue.pop_front();
return true;
}
template <class Rep, class Period>
bool popWaitFor(T& out, const std::atomic<bool>& running, std::chrono::duration<Rep, Period> timeout)
{
std::unique_lock<std::mutex> lk(m_mutex);
if (!m_cv.wait_for(lk, timeout, [&] {
return !m_queue.empty() || !running.load(std::memory_order_acquire);
})) {
return false;
}
if (m_queue.empty()) {
return false;
}
out = std::move(m_queue.front());
m_queue.pop_front();
return true;
}
private:
std::mutex m_mutex;
std::condition_variable m_cv;
std::deque<T> m_queue;
std::size_t m_maxDepth;
};
} // namespace softbus::core::threading

View File

@@ -0,0 +1,25 @@
#pragma once
#include <variant>
#include "message_bus/pipeline/protocol/modbusrtu/ModbusRtuPdu.h"
namespace softbus::message_bus::pipeline::protocol
{
enum class ProtocolFamily : std::uint8_t
{
None = 0,
ModbusRtu,
};
/// 流水线内协议解析结果的统一外壳;后续可在此 variant 中增加 CAN 等协议。
using ProtocolEnvelopePdu = std::variant<std::monostate, ModbusRtuPdu>;
struct ProtocolEnvelope
{
ProtocolFamily family{ProtocolFamily::None};
ProtocolEnvelopePdu pdu{};
};
} // namespace softbus::message_bus::pipeline::protocol

View File

@@ -0,0 +1,428 @@
#include "message_bus/pipeline/protocol/modbusrtu/ModbusRtuCodec.h"
#include <variant>
#include "message_bus/pipeline/PipelineContext.h"
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFraming.h"
namespace softbus::message_bus::pipeline::protocol
{
namespace
{
using softbus::message_bus::pipeline::BusDirection;
using softbus::message_bus::pipeline::stages::framers::modbusRtuCrc16;
using softbus::message_bus::pipeline::stages::framers::modbusRtuCrcOk;
void appendCrcLe(QByteArray& adu)
{
const auto crc = modbusRtuCrc16(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 (!modbusRtuCrcOk(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 || !modbusRtuCrcOk(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 || !modbusRtuCrcOk(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 (!modbusRtuCrcOk(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 || !modbusRtuCrcOk(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 || !modbusRtuCrcOk(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

View File

@@ -0,0 +1,27 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <QByteArray>
#include <QString>
#include "message_bus/pipeline/protocol/modbusrtu/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

View 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

View File

@@ -0,0 +1,244 @@
#include "message_bus/pipeline/protocol/modbusrtu/ModbusRtuProtocolPlugin.h"
#include "core/plugin_system/IProtocolPlugin.h"
#include "message_bus/pipeline/PipelineContext.h"
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
#include "message_bus/pipeline/protocol/modbusrtu/ModbusRtuCodec.h"
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFraming.h"
#include <QJsonArray>
#include <QJsonObject>
#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...>;
void syncLegacyJson(const ModbusRtuPdu& pdu, QJsonObject& o)
{
o = QJsonObject();
std::visit(
overloaded{
[&](const ModbusExceptionPdu& e) {
o.insert(QStringLiteral("unitId"), e.unitId);
o.insert(QStringLiteral("function"), e.function);
o.insert(QStringLiteral("exceptionCode"), e.exceptionCode);
o.insert(QStringLiteral("exception"), true);
},
[&](const ReadHoldingRegistersResponse& r) {
QJsonArray regs;
for (std::uint16_t v : r.registers) {
regs.append(static_cast<int>(v));
}
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 3);
o.insert(QStringLiteral("byteCount"), r.registers.size() * 2);
o.insert(QStringLiteral("registers"), regs);
},
[&](const ReadHoldingRegistersRequest& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 3);
o.insert(QStringLiteral("startAddress"), r.startAddress);
o.insert(QStringLiteral("quantity"), r.quantity);
},
[&](const ReadCoilsRequest& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 1);
o.insert(QStringLiteral("startAddress"), r.startAddress);
o.insert(QStringLiteral("quantity"), r.quantity);
},
[&](const ReadDiscreteInputsRequest& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 2);
o.insert(QStringLiteral("startAddress"), r.startAddress);
o.insert(QStringLiteral("quantity"), r.quantity);
},
[&](const ReadInputRegistersRequest& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 4);
o.insert(QStringLiteral("startAddress"), r.startAddress);
o.insert(QStringLiteral("quantity"), r.quantity);
},
[&](const ReadCoilsResponse& r) {
QJsonArray bits;
for (std::uint8_t b : r.coils) {
bits.append(static_cast<int>(b));
}
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 1);
o.insert(QStringLiteral("byteCount"), r.coils.size());
o.insert(QStringLiteral("coils"), bits);
},
[&](const ReadDiscreteInputsResponse& r) {
QJsonArray bits;
for (std::uint8_t b : r.discreteInputs) {
bits.append(static_cast<int>(b));
}
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 2);
o.insert(QStringLiteral("byteCount"), r.discreteInputs.size());
o.insert(QStringLiteral("discreteInputs"), bits);
},
[&](const ReadInputRegistersResponse& r) {
QJsonArray regs;
for (std::uint16_t v : r.inputRegisters) {
regs.append(static_cast<int>(v));
}
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 4);
o.insert(QStringLiteral("byteCount"), r.inputRegisters.size() * 2);
o.insert(QStringLiteral("inputRegisters"), regs);
},
[&](const WriteSingleCoilRequest& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 5);
o.insert(QStringLiteral("address"), r.address);
o.insert(QStringLiteral("value"), r.value);
},
[&](const WriteSingleCoilResponse& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 5);
o.insert(QStringLiteral("address"), r.address);
o.insert(QStringLiteral("value"), r.value);
},
[&](const WriteSingleRegisterRequest& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 6);
o.insert(QStringLiteral("address"), r.address);
o.insert(QStringLiteral("value"), r.value);
},
[&](const WriteSingleRegisterResponse& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 6);
o.insert(QStringLiteral("address"), r.address);
o.insert(QStringLiteral("value"), r.value);
},
[&](const WriteMultipleCoilsRequest& r) {
QJsonArray vals;
for (std::uint8_t b : r.values) {
vals.append(static_cast<int>(b));
}
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 15);
o.insert(QStringLiteral("startAddress"), r.startAddress);
o.insert(QStringLiteral("quantity"), r.quantity);
o.insert(QStringLiteral("values"), vals);
},
[&](const WriteMultipleCoilsResponse& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 15);
o.insert(QStringLiteral("startAddress"), r.startAddress);
o.insert(QStringLiteral("quantity"), r.quantity);
},
[&](const WriteMultipleRegistersRequest& r) {
QJsonArray vals;
for (std::uint16_t v : r.values) {
vals.append(static_cast<int>(v));
}
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 16);
o.insert(QStringLiteral("startAddress"), r.startAddress);
o.insert(QStringLiteral("quantity"), r.quantity);
o.insert(QStringLiteral("values"), vals);
},
[&](const WriteMultipleRegistersResponse& r) {
o.insert(QStringLiteral("unitId"), r.unitId);
o.insert(QStringLiteral("function"), 16);
o.insert(QStringLiteral("startAddress"), r.startAddress);
o.insert(QStringLiteral("quantity"), r.quantity);
},
[&](const ModbusRtuUnsupportedPdu&) {},
[&](const auto&) {}},
pdu);
}
class ModbusRtuSession final : public IProtocolSession
{
public:
bool feed(softbus::message_bus::pipeline::PipelineContext& ctx) 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)) {
ctx.protocol.family = ProtocolFamily::None;
ctx.protocol.pdu = {};
ctx.parsed = QJsonObject();
return false;
}
ctx.protocol.family = ProtocolFamily::ModbusRtu;
ctx.protocol.pdu = pdu;
syncLegacyJson(pdu, ctx.parsed);
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::stages::framers::protocolHintIsModbusRtu(protocolHint);
}
std::unique_ptr<IProtocolSession> createSession() override
{
return std::make_unique<ModbusRtuSession>();
}
};
std::shared_ptr<IProtocolPlugin> makeModbusRtuProtocolPlugin()
{
return std::make_shared<ModbusRtuProtocolPlugin>();
}
} // namespace softbus::core::plugin_system

View File

@@ -0,0 +1,12 @@
#pragma once
#include <memory>
#include "core/plugin_system/IProtocolPlugin.h"
namespace softbus::core::plugin_system
{
std::shared_ptr<IProtocolPlugin> makeModbusRtuProtocolPlugin();
} // namespace softbus::core::plugin_system

View File

@@ -0,0 +1,201 @@
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFramer.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <cstring>
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFraming.h"
#include "utils/logs/logging.h"
namespace softbus::message_bus::pipeline::stages::framers
{
ModbusRtuFramer::ModbusRtuFramer(std::shared_ptr<softbus::core::memory::MemoryPool> pool,
ModbusRtuFramerConfig cfg,
QString exclusiveEndpoint)
: m_pool(std::move(pool))
, m_cfg(cfg)
, m_exclusiveEndpoint(std::move(exclusiveEndpoint))
{
}
// 功能 :追加数据块 将数据块追加到会话中 通过时间戳记录最后接收时间 时间间隔超过最大帧间隔时间 则清空会话 如果超过最大ADU长度 则记录溢出 并记录溢出数量
void ModbusRtuFramer::appendChunk(Session& session, const std::uint8_t* p, std::size_t n)
{
if (!p || n == 0) {
return;
}
session.lastRx = std::chrono::steady_clock::now();
if (session.buf.size() < m_cfg.maxAdu) {
session.buf.resize(m_cfg.maxAdu);
}
if (session.len + n > m_cfg.maxAdu) {
++m_cnt.overflow;
session.len = 0;
LOG_WARNING() << "ModbusRtuFramer: buffer_overflow maxAdu=" << m_cfg.maxAdu;
return;
}
// `p/n` 来自 chunkIn.payload.bytes(),也就是串口读回的原始字节块(当前函数会再次拷贝进 session.buf
const std::size_t beforeLen = session.len;
std::ostringstream oss;
oss << std::hex << std::setfill('0');
const std::size_t show = std::min(n, static_cast<std::size_t>(16));
for (std::size_t i = 0; i < show; ++i) {
oss << std::setw(2) << static_cast<int>(p[i]);
if (i + 1 < show) {
oss << ' ';
}
}
if (n > show) {
oss << " ...";
}
const QString hexPreview = QString::fromStdString(oss.str());
// LOG_INFO() << "ModbusRtuFramer: appendChunk n=" << n << " session.len(before)=" << beforeLen
// << " p=" << static_cast<const void*>(p) << " bytes(16)=" << hexPreview;
std::memcpy(session.buf.data() + session.len, p, n);
session.len += n;
m_cnt.bytesIn += n;
}
// 功能 :尝试消费缓冲区 如果时间间隔超过最大帧间隔时间 则清空会话 如果超过最大ADU长度 则记录溢出 并记录溢出数量
void ModbusRtuFramer::tryConsumeBuffer(Session& session,
const QString& endpoint,
QString traceId,
int deviceId,
const QString& stableKey,
const QString& protocolHint,
QDateTime ts,
BusDirection dir,
std::vector<softbus::message_bus::pipeline::PipelineContext>& out,
QString* framerErr)
{
(void)framerErr;
if (m_cfg.interFrameTimeoutMs > 0 && session.len > 0) {
const auto now = std::chrono::steady_clock::now();
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - session.lastRx).count();
if (ms >= m_cfg.interFrameTimeoutMs) {
session.len = 0;
}
}
while (session.len > 0) {
const std::uint8_t* buf = session.buf.data();
auto pred =
predictModbusRtuAduLength(buf, session.len, m_cfg.maxAdu);
if (!pred) {
break;
}
if (session.len < *pred) {
break;
}
// std::ostringstream oss;
// oss << std::hex << std::setfill('0');
// for (std::size_t i = 0; i < session.len; ++i) {
// oss << std::setw(2) << static_cast<int>(buf[i]);
// if (i + 1 < session.len) {
// oss << ' ';
// }
// }
// const QString hexPreview = QString::fromStdString(oss.str());
// LOG_INFO() << "ModbusRtuFramer: tryConsumeBuffer buf(16)=" << hexPreview << " session.len=" << session.len << " pred=" << *pred ;
if (modbusRtuCrcOk(buf, *pred)) {
auto blk = m_pool->allocate(*pred);
if (!blk || !blk->data || blk->capacity < *pred) {
if (framerErr) {
*framerErr = QStringLiteral("pool_exhausted");
}
LOG_WARNING() << "ModbusRtuFramer: pool_exhausted endpoint=" << endpoint;
return;
}
std::memcpy(blk->data, buf, *pred);
blk->size = *pred;
softbus::message_bus::pipeline::PipelineContext ctx;
ctx.deviceId = deviceId;
ctx.stableKey = stableKey;
ctx.endpoint = endpoint;
ctx.timestamp = ts;
ctx.protocolHint = protocolHint;
ctx.traceId = traceId;
ctx.direction = dir;
ctx.frameKind = softbus::message_bus::pipeline::FrameKind::CompleteFrame;
ctx.payloadSize = *pred;
ctx.payload.block = std::move(blk);
ctx.payload.offset = 0;
ctx.payload.length = *pred;
out.push_back(std::move(ctx));
++m_cnt.framesEmitted;
const std::size_t rest = session.len - *pred;
if (rest > 0) {
std::memmove(session.buf.data(), buf + *pred, rest);
}
session.len = rest;
// LOG_INFO() << "ModbusRtuFramer success to consume buffer rest=" << rest;
continue;
}
++m_cnt.crcFail;
++m_cnt.resyncBytes;
// LOG_WARNING() << "ModbusRtuFramer: crc_mismatch resync_byte endpoint=" << endpoint
// << "traceId=" << traceId << "buf_len=" << session.len;
if (session.len <= 1) {
session.len = 0;
break;
}
std::memmove(session.buf.data(), buf + 1, session.len - 1);
--session.len;
}
}
void ModbusRtuFramer::feed(softbus::message_bus::pipeline::PipelineContext& chunkIn,
std::vector<softbus::message_bus::pipeline::PipelineContext>& completeFramesOut)
{
if (!m_pool || !chunkIn.payload.valid()) {
return;
}
if (!protocolHintIsModbusRtu(chunkIn.protocolHint)) {
return;
}
const std::uint8_t* p = chunkIn.payload.bytes();
const std::size_t n = chunkIn.payloadSize ? chunkIn.payloadSize : chunkIn.payload.length;
if (!p || n == 0) {
return;
}
Session* session = nullptr;
if (!m_exclusiveEndpoint.isEmpty()) {
session = &m_exclusiveSession;
} else {
session = &m_sessions[chunkIn.endpoint];
}
appendChunk(*session, p, n);
QString err;
tryConsumeBuffer(*session,
chunkIn.endpoint,
chunkIn.traceId,
chunkIn.deviceId,
chunkIn.stableKey,
chunkIn.protocolHint,
chunkIn.timestamp,
chunkIn.direction,
completeFramesOut,
&err);
if (!err.isEmpty()) {
chunkIn.framerError = err;
}
chunkIn.payload = {};
chunkIn.payloadSize = 0;
chunkIn.frameKind = softbus::message_bus::pipeline::FrameKind::StreamChunk;
}
} // namespace softbus::message_bus::pipeline::stages::framers

View File

@@ -0,0 +1,79 @@
#pragma once
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
#include <QHash>
#include <QString>
#include "core/memory/MemoryPool.h"
#include "message_bus/pipeline/PipelineContext.h"
#include "message_bus/pipeline/stages/1_framers/IFramer.h"
namespace softbus::message_bus::pipeline::stages::framers
{
struct ModbusRtuFramerConfig
{
std::size_t maxAdu{256};
int interFrameTimeoutMs{0};
};
struct ModbusRtuFramerCounters
{
std::atomic<std::uint64_t> bytesIn{0}; // 输入字节数
std::atomic<std::uint64_t> framesEmitted{0}; // 输出帧数
std::atomic<std::uint64_t> resyncBytes{0}; // 重同步字节数
std::atomic<std::uint64_t> crcFail{0}; // CRC校验失败数
std::atomic<std::uint64_t> overflow{0}; // 溢出数
};
/// 单 worker、多 endpoint使用内部 QHash单线程访问 Session无锁
/// 并行成帧:构造时传入 `exclusiveEndpoint`,仅重组该串口字节流(每 endpoint 一实例)。
class ModbusRtuFramer final : public IFramer
{
public:
ModbusRtuFramer(std::shared_ptr<softbus::core::memory::MemoryPool> pool,
ModbusRtuFramerConfig cfg = {},
QString exclusiveEndpoint = {});
void feed(softbus::message_bus::pipeline::PipelineContext& chunkIn,
std::vector<softbus::message_bus::pipeline::PipelineContext>& completeFramesOut) override;
const ModbusRtuFramerCounters& counters() const { return m_cnt; }
ModbusRtuFramerCounters& counters() { return m_cnt; }
void setConfig(ModbusRtuFramerConfig cfg) { m_cfg = cfg; }
private:
struct Session
{
std::vector<std::uint8_t> buf;
std::size_t len{0};
std::chrono::steady_clock::time_point lastRx{std::chrono::steady_clock::now()};
};
void appendChunk(Session& session, const std::uint8_t* p, std::size_t n);
void tryConsumeBuffer(Session& session,
const QString& endpoint,
QString traceId,
int deviceId,
const QString& stableKey,
const QString& protocolHint,
QDateTime ts,
BusDirection dir,
std::vector<softbus::message_bus::pipeline::PipelineContext>& out,
QString* framerErr);
std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
ModbusRtuFramerConfig m_cfg;
ModbusRtuFramerCounters m_cnt;
QString m_exclusiveEndpoint;
QHash<QString, Session> m_sessions;
Session m_exclusiveSession;
};
} // namespace softbus::message_bus::pipeline::stages::framers

View File

@@ -0,0 +1,125 @@
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFraming.h"
#include <QtGlobal>
namespace softbus::message_bus::pipeline::stages::framers
{
std::uint16_t modbusRtuCrc16(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) {
if (crc & 1) {
crc = static_cast<std::uint16_t>((crc >> 1) ^ 0xA001);
} else {
crc = static_cast<std::uint16_t>(crc >> 1);
}
}
}
return crc;
}
// 功能 校验CRC 如果CRC校验不正确 则返回false 如果CRC校验正确 则返回true
// 参数 adu 数据缓冲区 aduLen 数据长度
// 返回值 如果CRC校验不正确 则返回false 如果CRC校验正确 则返回true
// 逻辑:如果数据缓冲区为空 则返回false 如果数据长度小于4 则返回false 如果数据长度大于等于4 则返回true
// 如果数据长度大于等于4 则返回true
// 如果数据长度大于等于4 则返回true
bool modbusRtuCrcOk(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));
const std::uint16_t want = modbusRtuCrc16(adu, body);
return got == want;
}
static bool fcIsReadRange(std::uint8_t fc)
{
return fc == 0x01 || fc == 0x02 || fc == 0x03 || fc == 0x04;
}
// 功能 预测Modbus RTU帧长度 如果长度不足 则返回nullopt 如果长度足够 则返回长度
// 参数 buf 数据缓冲区 len 数据长度 maxAdu 最大ADU长度
// 返回值 :如果长度不足 则返回nullopt 如果长度足够 则返回长度
// 逻辑: unit 是设备地址 fc 是功能码 如果fc是异常码 则返回5 如果fc是读指令 则返回8 如果fc是写指令 则返回8 如果fc是其他指令 则返回nullopt
// 如果fc是读指令 则检查buf长度是否大于等于8 并且CRC校验是否正确 如果正确 则返回8
// 如果fc是写指令 则检查buf长度是否大于等于8 并且CRC校验是否正确 如果正确 则返回8
std::optional<std::size_t> predictModbusRtuAduLength(const std::uint8_t* buf,
std::size_t len,
std::size_t maxAdu)
{
if (len < 2 || !buf) {
return std::nullopt;
}
const std::uint8_t unit = buf[0];
(void)unit;
const std::uint8_t fc = buf[1];
if (fc & 0x80) {
return len < 5 ? std::nullopt : std::optional<std::size_t>(5);
}
if (fcIsReadRange(fc)) {
if (len >= 8 && modbusRtuCrcOk(buf, 8)) {
return std::optional<std::size_t>(8);
}
if (len < 3) {
return std::nullopt;
}
const std::size_t byteCount = buf[2];
const std::size_t total = 5 + byteCount;
if (total > maxAdu) {
return std::nullopt;
}
if (len < total) {
return std::nullopt;
}
if (modbusRtuCrcOk(buf, total) ) {
return total;
}
return std::optional<std::size_t>(8);
}
if (fc == 0x05 || fc == 0x06) {
return std::optional<std::size_t>(8);
}
if (fc == 0x16) {
return std::optional<std::size_t>(10);
}
if (fc == 0x0F || fc == 0x10) {
if (len >= 8 && modbusRtuCrcOk(buf, 8)) {
return std::optional<std::size_t>(8);
}
if (len >= 7) {
const std::uint8_t bc = buf[6];
const std::size_t req = 9 + static_cast<std::size_t>(bc);
if (bc <= 242 && req <= maxAdu && req >= 9) {
return req;
}
}
return len >= 2 ? std::optional<std::size_t>(8) : std::nullopt;
}
if (len < 6) {
return std::nullopt;
}
return std::optional<std::size_t>(8);
}
bool protocolHintIsModbusRtu(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::stages::framers

View File

@@ -0,0 +1,24 @@
#pragma once
// 功能 定义Modbus RTU帧解析器 包含CRC校验、帧预测、协议提示判断
#include <cstddef>
#include <cstdint>
#include <optional>
#include <QString>
namespace softbus::message_bus::pipeline::stages::framers
{
std::uint16_t modbusRtuCrc16(const std::uint8_t* data, std::size_t len);
/// 校验 ADU含尾端 CRC LE`aduLen` 为整帧长度unit..crc_hi
bool modbusRtuCrcOk(const std::uint8_t* adu, std::size_t aduLen);
/// 根据已缓冲前缀预测整帧 ADU 长度(含 2 字节 CRC未知或不足返回 nullopt。
std::optional<std::size_t> predictModbusRtuAduLength(const std::uint8_t* buf,
std::size_t len,
std::size_t maxAdu);
bool protocolHintIsModbusRtu(const QString& hint);
} // namespace softbus::message_bus::pipeline::stages::framers

View File

@@ -0,0 +1,54 @@
#include "message_bus/pipeline/stages/1_framers/PassthroughFramer.h"
#include <cstring>
namespace softbus::message_bus::pipeline::stages::framers
{
PassthroughFramer::PassthroughFramer(std::shared_ptr<softbus::core::memory::MemoryPool> pool)
: m_pool(std::move(pool))
{
}
void PassthroughFramer::feed(softbus::message_bus::pipeline::PipelineContext& chunkIn,
std::vector<softbus::message_bus::pipeline::PipelineContext>& completeFramesOut)
{
if (!m_pool || !chunkIn.payload.valid()) {
return;
}
const std::size_t n = chunkIn.payloadSize ? chunkIn.payloadSize : chunkIn.payload.length;
if (n == 0) {
return;
}
auto blk = m_pool->allocate(n);
if (!blk || !blk->data || blk->capacity < n) {
chunkIn.framerError = QStringLiteral("pool_exhausted");
return;
}
const std::uint8_t* src = chunkIn.payload.bytes();
if (!src) {
return;
}
std::memcpy(blk->data, src, n);
blk->size = n;
softbus::message_bus::pipeline::PipelineContext out;
out.deviceId = chunkIn.deviceId;
out.stableKey = chunkIn.stableKey;
out.endpoint = chunkIn.endpoint;
out.timestamp = chunkIn.timestamp;
out.protocolHint = chunkIn.protocolHint;
out.traceId = chunkIn.traceId;
out.direction = chunkIn.direction;
out.frameKind = softbus::message_bus::pipeline::FrameKind::CompleteFrame;
out.payloadSize = n;
out.payload.block = std::move(blk);
out.payload.offset = 0;
out.payload.length = n;
completeFramesOut.push_back(std::move(out));
chunkIn.payload = {};
chunkIn.payloadSize = 0;
}
} // namespace softbus::message_bus::pipeline::stages::framers

View File

@@ -0,0 +1,25 @@
#pragma once
#include <memory>
#include <vector>
#include "core/memory/MemoryPool.h"
#include "message_bus/pipeline/stages/1_framers/IFramer.h"
namespace softbus::message_bus::pipeline::stages::framers
{
/// 将单个 StreamChunk 拷贝为 CompleteFrame整帧生命周期由新池块持有
class PassthroughFramer final : public IFramer
{
public:
explicit PassthroughFramer(std::shared_ptr<softbus::core::memory::MemoryPool> pool);
void feed(softbus::message_bus::pipeline::PipelineContext& chunkIn,
std::vector<softbus::message_bus::pipeline::PipelineContext>& completeFramesOut) override;
private:
std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
};
} // namespace softbus::message_bus::pipeline::stages::framers

View File

@@ -0,0 +1,565 @@
#include "message_bus/pipeline/stages/3_filters/ProtocolParseFilter.h"
#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <cstring>
#include <limits>
#include <optional>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QVector>
#include <variant>
#include "message_bus/pipeline/protocol/modbusrtu/ModbusRtuPdu.h"
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
#include "utils/logs/logging.h"
namespace softbus::message_bus::pipeline::stages::filters
{
namespace
{
using softbus::core::models::DOMMessage;
using softbus::core::models::DataPointMapping;
using softbus::core::models::DataQuality;
using softbus::core::models::DataType;
using softbus::core::models::Endianness;
using softbus::message_bus::pipeline::protocol::ModbusRtuPdu;
using softbus::message_bus::pipeline::protocol::ProtocolFamily;
using softbus::message_bus::pipeline::protocol::ReadHoldingRegistersResponse;
QByteArray registersToBytes(const QVector<std::uint16_t>& regs)
{
QByteArray out;
out.reserve(regs.size() * 2);
for (std::uint16_t v : regs) {
out.append(static_cast<char>((v >> 8) & 0xFF));
out.append(static_cast<char>(v & 0xFF));
}
return out;
}
QJsonArray registersToQJsonArray(const QVector<std::uint16_t>& regs)
{
QJsonArray a;
for (std::uint16_t v : regs) {
a.append(static_cast<int>(v));
}
return a;
}
std::uint64_t readUnsignedRaw(const QByteArray& bytes, const DataPointMapping& mapping, bool* ok)
{
*ok = false;
if (mapping.byteLength == 0 || mapping.byteLength > 8) {
return 0;
}
const int offset = static_cast<int>(mapping.byteOffset);
const int len = static_cast<int>(mapping.byteLength);
if (offset < 0 || offset + len > bytes.size()) {
return 0;
}
std::array<std::uint8_t, 8> buf{};
for (int i = 0; i < len; ++i) {
buf[static_cast<std::size_t>(i)] = static_cast<std::uint8_t>(bytes[offset + i]);
}
if (mapping.endianness == Endianness::LittleEndian) {
std::reverse(buf.begin(), buf.begin() + len);
} else if (mapping.endianness == Endianness::BigEndianByteSwap && (len % 2 == 0)) {
for (int i = 0; i + 1 < len; i += 2) {
std::swap(buf[static_cast<std::size_t>(i)], buf[static_cast<std::size_t>(i + 1)]);
}
}
std::uint64_t v = 0;
for (int i = 0; i < len; ++i) {
v = (v << 8) | buf[static_cast<std::size_t>(i)];
}
*ok = true;
return v;
}
double decodeRawToDouble(std::uint64_t raw, const DataPointMapping& mapping)
{
switch (mapping.rawType) {
case DataType::BOOL:
return raw ? 1.0 : 0.0;
case DataType::INT32:
return static_cast<double>(static_cast<std::int32_t>(raw & 0xFFFFFFFFu));
case DataType::UINT32:
return static_cast<double>(static_cast<std::uint32_t>(raw & 0xFFFFFFFFu));
case DataType::INT64:
return static_cast<double>(static_cast<std::int64_t>(raw));
case DataType::FLOAT32: {
const std::uint32_t u = static_cast<std::uint32_t>(raw & 0xFFFFFFFFu);
float f = 0.0f;
std::memcpy(&f, &u, sizeof(float));
return static_cast<double>(f);
}
case DataType::FLOAT64: {
double d = 0.0;
std::memcpy(&d, &raw, sizeof(double));
return d;
}
case DataType::STRING:
case DataType::BYTE_ARRAY:
default:
return static_cast<double>(raw);
}
}
class ExprParser
{
public:
ExprParser(const QString& expr, const QJsonArray& regs, double x)
: m_expr(expr)
, m_regs(regs)
, m_x(x)
{
}
bool eval(double* out)
{
m_pos = 0;
if (!out) {
return false;
}
const auto v = parseBitOr();
skipSpaces();
if (!v.has_value() || m_pos != m_expr.size()) {
return false;
}
*out = *v;
return true;
}
private:
std::optional<double> parseBitOr()
{
auto lhs = parseBitAnd();
if (!lhs.has_value()) {
return std::nullopt;
}
while (true) {
skipSpaces();
if (!match('|')) {
break;
}
auto rhs = parseBitAnd();
if (!rhs.has_value()) {
return std::nullopt;
}
const auto lv = static_cast<std::int64_t>(*lhs);
const auto rv = static_cast<std::int64_t>(*rhs);
lhs = static_cast<double>(lv | rv);
}
return lhs;
}
std::optional<double> parseBitAnd()
{
auto lhs = parseShift();
if (!lhs.has_value()) {
return std::nullopt;
}
while (true) {
skipSpaces();
if (!match('&')) {
break;
}
auto rhs = parseShift();
if (!rhs.has_value()) {
return std::nullopt;
}
const auto lv = static_cast<std::int64_t>(*lhs);
const auto rv = static_cast<std::int64_t>(*rhs);
lhs = static_cast<double>(lv & rv);
}
return lhs;
}
std::optional<double> parseShift()
{
auto lhs = parseAddSub();
if (!lhs.has_value()) {
return std::nullopt;
}
while (true) {
skipSpaces();
if (match(QStringLiteral("<<"))) {
auto rhs = parseAddSub();
if (!rhs.has_value()) {
return std::nullopt;
}
lhs = static_cast<double>(static_cast<std::int64_t>(*lhs) << static_cast<int>(*rhs));
continue;
}
if (match(QStringLiteral(">>"))) {
auto rhs = parseAddSub();
if (!rhs.has_value()) {
return std::nullopt;
}
lhs = static_cast<double>(static_cast<std::int64_t>(*lhs) >> static_cast<int>(*rhs));
continue;
}
break;
}
return lhs;
}
std::optional<double> parseAddSub()
{
auto lhs = parseMulDiv();
if (!lhs.has_value()) {
return std::nullopt;
}
while (true) {
skipSpaces();
if (match('+')) {
auto rhs = parseMulDiv();
if (!rhs.has_value()) {
return std::nullopt;
}
lhs = *lhs + *rhs;
continue;
}
if (match('-')) {
auto rhs = parseMulDiv();
if (!rhs.has_value()) {
return std::nullopt;
}
lhs = *lhs - *rhs;
continue;
}
break;
}
return lhs;
}
std::optional<double> parseMulDiv()
{
auto lhs = parseUnary();
if (!lhs.has_value()) {
return std::nullopt;
}
while (true) {
skipSpaces();
if (match('*')) {
auto rhs = parseUnary();
if (!rhs.has_value()) {
return std::nullopt;
}
lhs = *lhs * *rhs;
continue;
}
if (match('/')) {
auto rhs = parseUnary();
if (!rhs.has_value() || *rhs == 0.0) {
return std::nullopt;
}
lhs = *lhs / *rhs;
continue;
}
break;
}
return lhs;
}
std::optional<double> parseUnary()
{
skipSpaces();
if (match('-')) {
auto v = parseUnary();
return v.has_value() ? std::optional<double>(-*v) : std::nullopt;
}
if (match('+')) {
return parseUnary();
}
return parsePrimary();
}
std::optional<double> parsePrimary()
{
skipSpaces();
if (match('(')) {
auto v = parseBitOr();
skipSpaces();
if (!v.has_value() || !match(')')) {
return std::nullopt;
}
return v;
}
if (peekIsAlpha()) {
return parseVariable();
}
return parseNumber();
}
std::optional<double> parseVariable()
{
const auto name = parseIdentifier();
if (name == QStringLiteral("x")) {
return m_x;
}
if (name.startsWith(QStringLiteral("r"))) {
bool ok = false;
const int idx = name.mid(1).toInt(&ok);
if (ok && idx >= 0 && idx < m_regs.size()) {
return static_cast<double>(m_regs[idx].toInt(0));
}
}
return std::nullopt;
}
std::optional<double> parseNumber()
{
skipSpaces();
const int start = m_pos;
bool dot = false;
while (m_pos < m_expr.size()) {
const QChar c = m_expr[m_pos];
if (c.isDigit()) {
++m_pos;
continue;
}
if (c == QChar('.')) {
if (dot) {
break;
}
dot = true;
++m_pos;
continue;
}
break;
}
if (m_pos <= start) {
return std::nullopt;
}
bool ok = false;
const double v = m_expr.mid(start, m_pos - start).toDouble(&ok);
if (!ok) {
return std::nullopt;
}
return v;
}
QString parseIdentifier()
{
skipSpaces();
const int start = m_pos;
while (m_pos < m_expr.size()) {
const QChar c = m_expr[m_pos];
if (c.isLetterOrNumber() || c == QChar('_')) {
++m_pos;
continue;
}
break;
}
return m_expr.mid(start, m_pos - start);
}
bool match(QChar c)
{
if (m_pos < m_expr.size() && m_expr[m_pos] == c) {
++m_pos;
return true;
}
return false;
}
bool match(const QString& token)
{
if (m_expr.mid(m_pos, token.size()) == token) {
m_pos += token.size();
return true;
}
return false;
}
bool peekIsAlpha() const
{
return m_pos < m_expr.size() && (m_expr[m_pos].isLetter() || m_expr[m_pos] == QChar('_'));
}
void skipSpaces()
{
while (m_pos < m_expr.size() && m_expr[m_pos].isSpace()) {
++m_pos;
}
}
private:
QString m_expr;
QJsonArray m_regs;
double m_x{0.0};
int m_pos{0};
};
softbus::core::models::DOMValue castToSemantic(double value, DataType t)
{
using softbus::core::models::DOMValue;
switch (t) {
case DataType::BOOL:
return DOMValue{value != 0.0};
case DataType::INT32:
return DOMValue{static_cast<std::int32_t>(value)};
case DataType::UINT32:
return DOMValue{static_cast<std::uint32_t>(value)};
case DataType::INT64:
return DOMValue{static_cast<std::int64_t>(value)};
case DataType::FLOAT32:
return DOMValue{static_cast<float>(value)};
case DataType::FLOAT64:
default:
return DOMValue{value};
}
}
DataQuality qualityByRange(double value, const softbus::core::models::MetadataDef& meta)
{
if (!std::isfinite(value)) {
return DataQuality::BAD;
}
if (meta.maxValue > meta.minValue && (value < meta.minValue || value > meta.maxValue)) {
return DataQuality::UNCERTAIN;
}
return DataQuality::GOOD;
}
QString normalizedProtocol(const QString& protocolHint)
{
return protocolHint.trimmed().toLower();
}
} // namespace
ProtocolParseFilter::ProtocolParseFilter() = default;
bool ProtocolParseFilter::apply(softbus::message_bus::pipeline::PipelineContext& ctx)
{
if (ctx.direction == softbus::message_bus::pipeline::BusDirection::Downstream) {
return true;
}
if (!ensureLoaded()) {
return true; // 保持兼容:配置缺失不阻断流水线
}
if (normalizedProtocol(ctx.protocolHint).contains(QStringLiteral("modbus"))) {
return parseModbus(ctx);
}
return true;
}
bool ProtocolParseFilter::ensureLoaded()
{
std::call_once(m_loadOnce, [this]() {
QString configBase = qEnvironmentVariable("SOFTBUS_CONFIG_DIR");
if (!configBase.isEmpty() && !configBase.endsWith(QChar('/'))) {
configBase.append(QChar('/'));
}
const QString homeCfg = configBase.isEmpty()
? (QDir::homePath() + QStringLiteral("/softbus/config/"))
: configBase;
const QString metaHome = homeCfg + QStringLiteral("metadata_dictionary.json");
const QString profHome = homeCfg + QStringLiteral("device_profiles.json");
const QString metaLocal = QStringLiteral("config/metadata_dictionary.json");
const QString profLocal = QStringLiteral("config/device_profiles.json");
const QString metaPath = QFileInfo::exists(metaHome) ? metaHome : metaLocal;
const QString profPath = QFileInfo::exists(profHome) ? profHome : profLocal;
const bool metaOk = m_metadataRegistry.loadFromFile(metaPath);
const bool profOk = m_profileRegistry.loadFromFile(profPath);
if (!metaOk || !profOk) {
LOG_WARNING() << "ProtocolParseFilter: config load failed metadata="
<< m_metadataRegistry.lastError() << " profile="
<< m_profileRegistry.lastError();
m_loaded = false;
return;
}
m_loaded = true;
});
return m_loaded;
}
bool ProtocolParseFilter::parseModbus(softbus::message_bus::pipeline::PipelineContext& ctx)
{
QVector<std::uint16_t> regsVec;
QJsonArray regsForExpr;
if (ctx.protocol.family == ProtocolFamily::ModbusRtu) {
if (const auto* m = std::get_if<ModbusRtuPdu>(&ctx.protocol.pdu)) {
if (const auto* resp = std::get_if<ReadHoldingRegistersResponse>(&*m)) {
regsVec = resp->registers;
regsForExpr = registersToQJsonArray(regsVec);
}
}
}
if (regsVec.isEmpty()) {
const QJsonArray regsLegacy = ctx.parsed.value(QStringLiteral("registers")).toArray();
if (regsLegacy.isEmpty()) {
return true;
}
regsForExpr = regsLegacy;
for (const auto& reg : regsLegacy) {
regsVec.append(static_cast<std::uint16_t>(reg.toInt(0) & 0xFFFF));
}
}
const auto* profile = m_profileRegistry.find(ctx.stableKey, ctx.protocolHint);
if (!profile) {
profile = m_profileRegistry.find(QString::number(ctx.deviceId), ctx.protocolHint);
}
if (!profile) {
return true;
}
const QByteArray bytes = registersToBytes(regsVec);
ctx.domMessages.clear();
ctx.domMessages.reserve(profile->mappings.size());
for (const auto& mapping : profile->mappings) {
const auto* meta = m_metadataRegistry.findById(mapping.metadataId);
if (!meta) {
continue;
}
bool ok = false;
const std::uint64_t raw = readUnsignedRaw(bytes, mapping, &ok);
if (!ok) {
continue;
}
double v = decodeRawToDouble(raw, mapping);
v = v * mapping.scaleFactor + mapping.offsetFactor;
if (!mapping.expression.trimmed().isEmpty()) {
ExprParser parser(mapping.expression, regsForExpr, v);
double exprV = 0.0;
if (parser.eval(&exprV)) {
v = exprV;
}
}
DOMMessage msg;
msg.messageId = QStringLiteral("%1.%2")
.arg(ctx.stableKey.isEmpty() ? QString::number(ctx.deviceId) : ctx.stableKey,
mapping.pointId);
msg.metaRef = meta;
msg.timestamp = ctx.timestamp.toMSecsSinceEpoch();
msg.value = castToSemantic(v, meta->semanticType);
msg.quality = qualityByRange(v, *meta);
ctx.domMessages.append(std::move(msg));
}
return true;
}
} // namespace softbus::message_bus::pipeline::stages::filters

View File

@@ -0,0 +1,30 @@
#pragma once
#include <mutex>
#include "core/metadata/MetadataRegistry.h"
#include "core/metadata/ProfileRegistry.h"
#include "message_bus/pipeline/stages/3_filters/IFilterStage.h"
namespace softbus::message_bus::pipeline::stages::filters
{
class ProtocolParseFilter final : public IFilterStage
{
public:
ProtocolParseFilter();
bool apply(softbus::message_bus::pipeline::PipelineContext& ctx) override;
private:
bool ensureLoaded();
bool parseModbus(softbus::message_bus::pipeline::PipelineContext& ctx);
private:
softbus::core::metadata::MetadataRegistry m_metadataRegistry;
softbus::core::metadata::ProfileRegistry m_profileRegistry;
std::once_flag m_loadOnce;
bool m_loaded{false};
};
} // namespace softbus::message_bus::pipeline::stages::filters

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"