modbus rtu 1.1 plugins

This commit is contained in:
flower_linux
2026-04-10 10:52:10 +08:00
parent 51175ffd18
commit b44f5e275e
66 changed files with 1088 additions and 1124 deletions

View File

@@ -60,6 +60,8 @@ qt_add_executable(softbus_daemon
src/api/ipc_dbus/IpcDBus.h src/api/ipc_dbus/IpcDBus.h
src/api/ipc_dbus/IpcDBus.cpp src/api/ipc_dbus/IpcDBus.cpp
src/api/ipc_dbus/CommandDispatcher.h
src/api/ipc_dbus/CommandDispatcher.cpp
# device types # device types
src/devices/DeviceTypes.h src/devices/DeviceTypes.h
@@ -118,16 +120,20 @@ qt_add_executable(softbus_daemon
src/core/memory/LockFreeQueue.h src/core/memory/LockFreeQueue.h
src/core/plugin_system/IProtocolPlugin.h src/core/plugin_system/IProtocolPlugin.h
src/core/plugin_system/IProtocolFramerPlugin.h src/core/plugin_system/IProtocolFramerPlugin.h
src/core/plugin_system/IProtocolMapperPlugin.h
src/core/plugin_system/PluginManager.h src/core/plugin_system/PluginManager.h
src/core/plugin_system/PluginManager.cpp src/core/plugin_system/PluginManager.cpp
src/core/plugin_system/PluginHost.h src/core/plugin_system/PluginHost.h
src/core/plugin_system/PluginHost.cpp src/core/plugin_system/PluginHost.cpp
plugins/protocols/modbusrtu/parser/ModbusRtuProtocolPlugin.h plugins/protocols/modbus_rtu/parser/ModbusRtuProtocolPlugin.h
plugins/protocols/modbusrtu/parser/ModbusRtuProtocolPlugin.cpp plugins/protocols/modbus_rtu/parser/ModbusRtuProtocolPlugin.cpp
plugins/protocols/modbus_rtu/mapper/ModbusRtuProtocolMapperPlugin.h
plugins/protocols/modbus_rtu/mapper/ModbusRtuProtocolMapperPlugin.cpp
src/core/models/Types.h src/core/models/Types.h
src/core/models/MetadataDef.h src/core/models/MetadataDef.h
src/core/models/DataMapping.h src/core/models/DataMapping.h
src/core/models/DOMMessage.h src/core/models/DOMMessage.h
src/core/models/EnrichedMessage.h
src/core/metadata/MetadataRegistry.h src/core/metadata/MetadataRegistry.h
src/core/metadata/MetadataRegistry.cpp src/core/metadata/MetadataRegistry.cpp
src/core/metadata/ProfileRegistry.h src/core/metadata/ProfileRegistry.h
@@ -136,16 +142,18 @@ qt_add_executable(softbus_daemon
# message bus pipeline # message bus pipeline
src/message_bus/pipeline/PayloadRef.h src/message_bus/pipeline/PayloadRef.h
src/message_bus/pipeline/PipelineContext.h src/message_bus/pipeline/PipelineContext.h
src/message_bus/pipeline/DynamicRuleRegistry.h
src/message_bus/pipeline/DynamicRuleRegistry.cpp
src/message_bus/pipeline/PipelineEngine.h src/message_bus/pipeline/PipelineEngine.h
src/message_bus/pipeline/PipelineEngine.cpp src/message_bus/pipeline/PipelineEngine.cpp
src/message_bus/pipeline/protocol/ProtocolEnvelope.h src/message_bus/pipeline/protocol/ProtocolEnvelope.h
plugins/protocols/modbusrtu/types/ModbusRtuPdu.h plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h
plugins/protocols/modbusrtu/codec/ModbusRtuCodec.h plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h
plugins/protocols/modbusrtu/codec/ModbusRtuCodec.cpp plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.cpp
plugins/protocols/modbusrtu/framer/ModbusRtuFraming.h plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h
plugins/protocols/modbusrtu/framer/ModbusRtuFraming.cpp plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.cpp
plugins/protocols/modbusrtu/framer/ModbusRtuFramerPlugin.h plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.h
plugins/protocols/modbusrtu/framer/ModbusRtuFramerPlugin.cpp plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.cpp
src/message_bus/pipeline/stages/1_framers/IFramer.h src/message_bus/pipeline/stages/1_framers/IFramer.h
src/message_bus/pipeline/stages/1_framers/PassthroughFramer.h src/message_bus/pipeline/stages/1_framers/PassthroughFramer.h

Binary file not shown.

Binary file not shown.

View File

@@ -1,46 +0,0 @@
{
"plugin_dir": "bin/plugins",
"pipeline": {
"parallelPipeline": false,
"parseWorkerCount": 2,
"orderedEgress": true,
"framedQueueMax": 4096,
"maxReorderDepth": 256,
"modbusRtuMaxFrameBytes": 256,
"modbusRtuInterFrameTimeoutMs": 0
},
"plugins": {
"framers": [
{
"path": "bin/plugins/libmodbus_rtu.so",
"protocol": "modbus_rtu",
"abi": 1
}
],
"hotReload": {
"enabled": true,
"pollIntervalMs": 2000
}
},
"device_tree": [
{
"id": "serial_1",
"name": "Serial Device 1",
"type": "serial",
"endpoint": "/dev/ttyS1",
"driver": "hardware/drivers/serial/SerialQtDriver",
"capabilitiesRef": "src/hardware/drivers/serial/parameters.json",
"params": {
"protocol": "modbus_rtu",
"baudRate": 115200,
"dataBits": 8,
"parity": "none",
"stopBits": 1,
"flowControl": "none"
},
"children": [],
"enabled": true
}
],
"routes": []
}

View File

@@ -1,36 +0,0 @@
{
"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

@@ -1,28 +0,0 @@
{
"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

@@ -1,5 +0,0 @@
{
"modbus_rtu": {
"register_map": []
}
}

View File

@@ -1,10 +1,11 @@
# Runtime-loadable Modbus RTU parser + framer plugin # Runtime-loadable Modbus RTU parser + framer plugin
add_library(modbus_rtu SHARED add_library(modbus_rtu SHARED
plugin_stub.cpp plugin_stub.cpp
../modbusrtu/parser/ModbusRtuProtocolPlugin.cpp parser/ModbusRtuProtocolPlugin.cpp
../modbusrtu/codec/ModbusRtuCodec.cpp mapper/ModbusRtuProtocolMapperPlugin.cpp
../modbusrtu/framer/ModbusRtuFramerPlugin.cpp codec/ModbusRtuCodec.cpp
../modbusrtu/framer/ModbusRtuFraming.cpp framer/ModbusRtuFramerPlugin.cpp
framer/ModbusRtuFraming.cpp
) )
target_include_directories(modbus_rtu PRIVATE target_include_directories(modbus_rtu PRIVATE
${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}

View File

@@ -1,4 +1,4 @@
# Modbus RTU 协议模块(`modbusrtu` # Modbus RTU 协议模块(`modbus_rtu`
本目录实现 **Modbus RTU** 的成帧、编解码与解析插件,供守护进程静态编译与 `plugins/protocols/modbus_rtu` 动态库(`.so`)共用同一套源码。 本目录实现 **Modbus RTU** 的成帧、编解码与解析插件,供守护进程静态编译与 `plugins/protocols/modbus_rtu` 动态库(`.so`)共用同一套源码。
@@ -26,7 +26,7 @@ parser (依赖 codec、types不反向依赖 framer 插件类)
## 与动态库的关系 ## 与动态库的关系
- 动态库入口:`plugins/protocols/modbus_rtu/plugin_stub.cpp`,导出 `softbus_create_protocol_plugin` / `softbus_create_framer_plugin` 等符号。 - 动态库入口:`plugins/protocols/modbus_rtu/plugin_stub.cpp`,导出 `softbus_create_protocol_plugin` / `softbus_create_framer_plugin` 等符号。
- 主工程 `CMakeLists.txt` 同样列出本目录下源文件,用于无 `.so` 时的内置链接;路径与 include 均以 `plugins/protocols/modbusrtu/...` 为准。 - 主工程 `CMakeLists.txt` 同样列出本目录下源文件,用于无 `.so` 时的内置链接;路径与 include 均以 `plugins/protocols/modbus_rtu/...` 为准。
## 配置提示 ## 配置提示

View File

@@ -1,9 +1,9 @@
#include "plugins/protocols/modbusrtu/codec/ModbusRtuCodec.h" #include "plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h"
#include <variant> #include <variant>
#include "message_bus/pipeline/PipelineContext.h" #include "message_bus/pipeline/PipelineContext.h"
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFraming.h" #include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
namespace softbus::message_bus::pipeline::protocol namespace softbus::message_bus::pipeline::protocol
{ {

View File

@@ -6,7 +6,7 @@
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
#include "plugins/protocols/modbusrtu/types/ModbusRtuPdu.h" #include "plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h"
namespace softbus::message_bus::pipeline namespace softbus::message_bus::pipeline
{ {

View File

@@ -1,9 +1,9 @@
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFramerPlugin.h" #include "plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.h"
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFraming.h" #include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
#include "utils/logs/logging.h" #include "utils/logs/logging.h"
namespace softbus::message_bus::pipeline::protocol::modbusrtu namespace softbus::message_bus::pipeline::protocol::modbusrtu
@@ -72,7 +72,6 @@ public:
auto blk = m_pool->allocate(*pred); auto blk = m_pool->allocate(*pred);
if (!blk || !blk->data || blk->capacity < *pred) { if (!blk || !blk->data || blk->capacity < *pred) {
in.framerError = QStringLiteral("pool_exhausted");
return; return;
} }
std::memcpy(blk->data, m_buf.data(), *pred); std::memcpy(blk->data, m_buf.data(), *pred);

View File

@@ -1,4 +1,4 @@
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFraming.h" #include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
namespace softbus::message_bus::pipeline::protocol::modbusrtu namespace softbus::message_bus::pipeline::protocol::modbusrtu
{ {

View File

@@ -0,0 +1,93 @@
#include "plugins/protocols/modbus_rtu/mapper/ModbusRtuProtocolMapperPlugin.h"
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
namespace softbus::message_bus::pipeline::protocol::modbusrtu
{
namespace
{
class ModbusRtuMapperSession final : public softbus::core::plugin_system::IProtocolMapperSession
{
public:
std::vector<softbus::core::models::DOMMessage> map(
const softbus::message_bus::pipeline::PipelineContext& rawContext,
const softbus::message_bus::pipeline::protocol::ProtocolEnvelope& envelope) override
{
std::vector<softbus::core::models::DOMMessage> out;
if (envelope.family != softbus::message_bus::pipeline::protocol::ProtocolFamily::ModbusRtu) {
return out;
}
const auto* pdu = std::get_if<softbus::message_bus::pipeline::protocol::ModbusRtuPdu>(&envelope.pdu);
if (!pdu) {
return out;
}
if (const auto* resp =
std::get_if<softbus::message_bus::pipeline::protocol::ReadHoldingRegistersResponse>(pdu)) {
out.reserve(static_cast<std::size_t>(resp->registers.size()));
for (int i = 0; i < resp->registers.size(); ++i) {
softbus::core::models::DOMMessage msg;
msg.messageId = QStringLiteral("%1.reg.%2")
.arg(rawContext.stableKey.isEmpty() ? QString::number(rawContext.deviceId)
: rawContext.stableKey)
.arg(i);
msg.timestamp = rawContext.timestamp.toMSecsSinceEpoch();
msg.value = static_cast<std::uint32_t>(resp->registers[static_cast<qsizetype>(i)]);
msg.quality = softbus::core::models::DataQuality::GOOD;
msg.attributes.insert(QStringLiteral("sourceEndpoint"), rawContext.endpoint);
out.push_back(std::move(msg));
}
return out;
}
if (const auto* req =
std::get_if<softbus::message_bus::pipeline::protocol::ReadHoldingRegistersRequest>(pdu)) {
softbus::core::models::DOMMessage msg;
msg.messageId = QStringLiteral("%1.req.read_holding")
.arg(rawContext.stableKey.isEmpty() ? QString::number(rawContext.deviceId)
: rawContext.stableKey);
msg.timestamp = rawContext.timestamp.toMSecsSinceEpoch();
msg.value = static_cast<std::uint32_t>(req->quantity);
msg.quality = softbus::core::models::DataQuality::GOOD;
msg.attributes.insert(QStringLiteral("sourceEndpoint"), rawContext.endpoint);
msg.attributes.insert(QStringLiteral("frameType"), QStringLiteral("request"));
msg.attributes.insert(QStringLiteral("startAddress"), static_cast<int>(req->startAddress));
msg.attributes.insert(QStringLiteral("quantity"), static_cast<int>(req->quantity));
out.push_back(std::move(msg));
}
return out;
}
void reset() override {}
};
class ModbusRtuProtocolMapperPlugin final : public softbus::core::plugin_system::IProtocolMapperPlugin
{
public:
QString pluginId() const override { return QStringLiteral("modbus_rtu_mapper"); }
bool supports(const QString& protocolHint) const override
{
return isModbusRtuHint(protocolHint);
}
std::unique_ptr<softbus::core::plugin_system::IProtocolMapperSession> createMapperSession() override
{
return std::make_unique<ModbusRtuMapperSession>();
}
};
} // namespace
std::shared_ptr<softbus::core::plugin_system::IProtocolMapperPlugin> makeModbusRtuProtocolMapperPlugin()
{
return std::make_shared<ModbusRtuProtocolMapperPlugin>();
}
softbus::core::plugin_system::IProtocolMapperPlugin* createModbusRtuProtocolMapperPluginRaw()
{
return new ModbusRtuProtocolMapperPlugin();
}
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu

View File

@@ -0,0 +1,14 @@
#pragma once
#include <memory>
#include "core/plugin_system/IProtocolMapperPlugin.h"
namespace softbus::message_bus::pipeline::protocol::modbusrtu
{
std::shared_ptr<softbus::core::plugin_system::IProtocolMapperPlugin> makeModbusRtuProtocolMapperPlugin();
softbus::core::plugin_system::IProtocolMapperPlugin* createModbusRtuProtocolMapperPluginRaw();
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu

View File

@@ -0,0 +1,107 @@
#include "plugins/protocols/modbus_rtu/parser/ModbusRtuProtocolPlugin.h"
#include "core/plugin_system/IProtocolPlugin.h"
#include "message_bus/pipeline/PipelineContext.h"
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
#include "plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h"
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
#include <memory>
#include <utility>
#include <variant>
namespace softbus::core::plugin_system
{
namespace
{
using softbus::message_bus::pipeline::protocol::ModbusExceptionPdu;
using softbus::message_bus::pipeline::protocol::ModbusRtuPdu;
using softbus::message_bus::pipeline::protocol::ModbusRtuUnsupportedPdu;
using softbus::message_bus::pipeline::protocol::ProtocolFamily;
using softbus::message_bus::pipeline::protocol::ReadCoilsRequest;
using softbus::message_bus::pipeline::protocol::ReadCoilsResponse;
using softbus::message_bus::pipeline::protocol::ReadDiscreteInputsRequest;
using softbus::message_bus::pipeline::protocol::ReadDiscreteInputsResponse;
using softbus::message_bus::pipeline::protocol::ReadHoldingRegistersRequest;
using softbus::message_bus::pipeline::protocol::ReadHoldingRegistersResponse;
using softbus::message_bus::pipeline::protocol::ReadInputRegistersRequest;
using softbus::message_bus::pipeline::protocol::ReadInputRegistersResponse;
using softbus::message_bus::pipeline::protocol::WriteSingleCoilRequest;
using softbus::message_bus::pipeline::protocol::WriteSingleCoilResponse;
using softbus::message_bus::pipeline::protocol::WriteSingleRegisterRequest;
using softbus::message_bus::pipeline::protocol::WriteSingleRegisterResponse;
using softbus::message_bus::pipeline::protocol::WriteMultipleCoilsRequest;
using softbus::message_bus::pipeline::protocol::WriteMultipleCoilsResponse;
using softbus::message_bus::pipeline::protocol::WriteMultipleRegistersRequest;
using softbus::message_bus::pipeline::protocol::WriteMultipleRegistersResponse;
using softbus::message_bus::pipeline::protocol::decodeModbusRtu;
template <class... Ts>
struct overloaded : Ts...
{
using Ts::operator()...;
};
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
class ModbusRtuSession final : public IProtocolSession
{
public:
bool parse(const softbus::message_bus::pipeline::PipelineContext& ctx,
softbus::message_bus::pipeline::protocol::ProtocolEnvelope& out) override
{
using softbus::message_bus::pipeline::FrameKind;
if (ctx.frameKind != FrameKind::CompleteFrame || !ctx.payload.valid()) {
return false;
}
const std::size_t n = ctx.payloadSize ? ctx.payloadSize : ctx.payload.length;
const std::uint8_t* p = ctx.payload.bytes();
if (!p || n < 4) {
return false;
}
ModbusRtuPdu pdu;
if (!decodeModbusRtu(ctx.direction, p, n, pdu, nullptr)) {
out.family = ProtocolFamily::None;
out.pdu = {};
return false;
}
out.family = ProtocolFamily::ModbusRtu;
out.pdu = pdu;
return true;
}
void reset() override {}
};
} // namespace
class ModbusRtuProtocolPlugin final : public IProtocolPlugin
{
public:
QString pluginId() const override { return QStringLiteral("modbus_rtu"); }
bool supports(const QString& protocolHint) const override
{
return softbus::message_bus::pipeline::protocol::modbusrtu::isModbusRtuHint(protocolHint);
}
std::unique_ptr<IProtocolSession> createSession() override
{
return std::make_unique<ModbusRtuSession>();
}
};
std::shared_ptr<IProtocolPlugin> makeModbusRtuProtocolPlugin()
{
return std::make_shared<ModbusRtuProtocolPlugin>();
}
IProtocolPlugin* createModbusRtuProtocolPluginRaw()
{
return new ModbusRtuProtocolPlugin();
}
} // namespace softbus::core::plugin_system

View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
// Modbus RTU 协议解析器插件
#include <memory> #include <memory>
#include "core/plugin_system/IProtocolPlugin.h" #include "core/plugin_system/IProtocolPlugin.h"

View File

@@ -1,5 +1,6 @@
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFramerPlugin.h" #include "plugins/protocols/modbus_rtu/framer/ModbusRtuFramerPlugin.h"
#include "plugins/protocols/modbusrtu/parser/ModbusRtuProtocolPlugin.h" #include "plugins/protocols/modbus_rtu/mapper/ModbusRtuProtocolMapperPlugin.h"
#include "plugins/protocols/modbus_rtu/parser/ModbusRtuProtocolPlugin.h"
extern "C" int softbus_plugin_abi_version() extern "C" int softbus_plugin_abi_version()
{ {
@@ -15,3 +16,8 @@ extern "C" softbus::core::plugin_system::IProtocolFramerPlugin* softbus_create_f
{ {
return softbus::message_bus::pipeline::protocol::modbusrtu::createModbusRtuFramerPluginRaw(); return softbus::message_bus::pipeline::protocol::modbusrtu::createModbusRtuFramerPluginRaw();
} }
extern "C" softbus::core::plugin_system::IProtocolMapperPlugin* softbus_create_mapper_plugin()
{
return softbus::message_bus::pipeline::protocol::modbusrtu::createModbusRtuProtocolMapperPluginRaw();
}

View File

@@ -1,249 +0,0 @@
#include "plugins/protocols/modbusrtu/parser/ModbusRtuProtocolPlugin.h"
#include "core/plugin_system/IProtocolPlugin.h"
#include "message_bus/pipeline/PipelineContext.h"
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
#include "plugins/protocols/modbusrtu/codec/ModbusRtuCodec.h"
#include "plugins/protocols/modbusrtu/framer/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::protocol::modbusrtu::isModbusRtuHint(protocolHint);
}
std::unique_ptr<IProtocolSession> createSession() override
{
return std::make_unique<ModbusRtuSession>();
}
};
std::shared_ptr<IProtocolPlugin> makeModbusRtuProtocolPlugin()
{
return std::make_shared<ModbusRtuProtocolPlugin>();
}
IProtocolPlugin* createModbusRtuProtocolPluginRaw()
{
return new ModbusRtuProtocolPlugin();
}
} // namespace softbus::core::plugin_system

View File

@@ -0,0 +1,83 @@
# softbus_daemon API 接口表
本文档汇总 `src/api/ipc_dbus` 当前对外接口,分为:
- D-Bus 直接方法
- `executeCommand` JSON 命令接口
## 1) D-Bus 服务基础信息
| 项目 | 值 |
|---|---|
| Service | `com.softbus.Daemon` |
| ObjectPath | `/com/softbus/Daemon` |
| Interface | `com.softbus.Daemon` |
| 适配类 | `IpcDBus` |
## 2) D-Bus 方法表IpcDBus
| 方法 | 入参 | 返回 | 说明 |
|---|---|---|---|
| `isRunning()` | 无 | `bool` | 核心服务是否初始化完成 |
| `ping()` | 无 | `QString` | 健康探针,固定返回 `pong` |
| `getVersion()` | 无 | `QString` | 进程版本号 |
| `getStatus()` | 无 | `QString(JSON)` | 返回服务状态pid/role/running/timestamp 等) |
| `getPid()` | 无 | `qint64` | 返回守护进程 PID |
| `shutdown()` | 无 | `void` | 关闭 CoreService 并退出进程 |
| `reloadConfig()` | 无 | `bool` | 预留,当前为占位实现 |
| `executeCommand(requestJson)` | `QString(JSON)` | `QString(JSON)` | 统一 JSON 命令入口 |
## 3) executeCommand 协议
### 3.1 请求格式
```json
{
"id": "req-001",
"action": "add_filter",
"params": {}
}
```
### 3.2 响应格式
```json
{
"id": "req-001",
"code": 0,
"message": "ok",
"data": {}
}
```
## 4) action 路由表CommandDispatcher
| action | params 关键字段 | 成功 code | 失败 code | 说明 |
|---|---|---:|---:|---|
| `add_filter` | `id`, `endpoint?`, `deviceId?`, `stableKey?`, `condition?` | `0` | `4001` | 添加过滤规则 |
| `remove_filter` | `id` | `0` | `4041` | 删除过滤规则 |
| `add_transform_rule` | `id`, `endpoint?`, `deviceId?`, `stableKey?`, `scale?`, `offset?`, `unit?` | `0` | `4002` | 添加转换规则 |
| `remove_transform_rule` | `id` | `0` | `4042` | 删除转换规则 |
| `list_rules` | 无 | `0` | - | 返回规则计数状态 |
| `get_status` | 无 | `0` | - | 返回规则计数状态 |
| 其他未知 action | - | - | `4040` | 未知动作 |
## 5) 通用错误码约定(当前实现)
| code | message | 说明 |
|---:|---|---|
| `0` | `ok` | 成功 |
| `4000` | `invalid_json` | 请求 JSON 无法解析或非对象 |
| `4001` | `invalid_filter_rule` | 过滤规则参数非法 |
| `4002` | `invalid_transform_rule` | 转换规则参数非法 |
| `4003` | `invalid_action` | action 为空或分发器不可用 |
| `4040` | `unknown_action` | action 未注册 |
| `4041` | `filter_not_found` | 删除过滤规则时未找到 |
| `4042` | `transform_rule_not_found` | 删除转换规则时未找到 |
## 6) 维护建议
- 新增 action 时,同时更新:
- `CommandDispatcher::dispatch(...)`
- 本文档第 4 节 action 表
- 若扩展返回结构,保持 `code/message/data` 三元组不变,避免客户端兼容性问题。

View File

@@ -0,0 +1,66 @@
#include "api/ipc_dbus/CommandDispatcher.h"
#include "message_bus/pipeline/DynamicRuleRegistry.h"
namespace softbus::api::ipc
{
namespace
{
softbus::message_bus::pipeline::RuleScope parseScope(const QJsonObject& params)
{
softbus::message_bus::pipeline::RuleScope scope;
scope.endpoint = params.value(QStringLiteral("endpoint")).toString();
scope.deviceId = params.value(QStringLiteral("deviceId")).toInt(-1);
scope.stableKey = params.value(QStringLiteral("stableKey")).toString();
return scope;
}
} // namespace
DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObject& params) const
{
auto& reg = softbus::message_bus::pipeline::DynamicRuleRegistry::instance();
if (action == QStringLiteral("add_filter")) {
softbus::message_bus::pipeline::FilterRule rule;
rule.id = params.value(QStringLiteral("id")).toString();
rule.scope = parseScope(params);
rule.condition = params.value(QStringLiteral("condition")).toObject();
if (!reg.addFilterRule(rule)) {
return {4001, QStringLiteral("invalid_filter_rule"), {}};
}
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), rule.id}}};
}
if (action == QStringLiteral("remove_filter")) {
const QString id = params.value(QStringLiteral("id")).toString();
const bool ok = reg.removeFilterRule(id);
return {ok ? 0 : 4041, ok ? QStringLiteral("ok") : QStringLiteral("filter_not_found"),
QJsonObject{{QStringLiteral("id"), id}}};
}
if (action == QStringLiteral("add_transform_rule")) {
softbus::message_bus::pipeline::TransformRule rule;
rule.id = params.value(QStringLiteral("id")).toString();
rule.scope = parseScope(params);
rule.scale = params.value(QStringLiteral("scale")).toDouble(1.0);
rule.offset = params.value(QStringLiteral("offset")).toDouble(0.0);
rule.unit = params.value(QStringLiteral("unit")).toString();
if (!reg.addTransformRule(rule)) {
return {4002, QStringLiteral("invalid_transform_rule"), {}};
}
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), rule.id}}};
}
if (action == QStringLiteral("remove_transform_rule")) {
const QString id = params.value(QStringLiteral("id")).toString();
const bool ok = reg.removeTransformRule(id);
return {ok ? 0 : 4042, ok ? QStringLiteral("ok") : QStringLiteral("transform_rule_not_found"),
QJsonObject{{QStringLiteral("id"), id}}};
}
if (action == QStringLiteral("list_rules") || action == QStringLiteral("get_status")) {
return {0, QStringLiteral("ok"), reg.status()};
}
return {4040, QStringLiteral("unknown_action"), {}};
}
} // namespace softbus::api::ipc

View File

@@ -0,0 +1,23 @@
#pragma once
#include <QString>
#include <QJsonObject>
namespace softbus::api::ipc
{
struct DispatchResult
{
int code{0};
QString message;
QJsonObject data;
};
class CommandDispatcher
{
public:
DispatchResult dispatch(const QString& action, const QJsonObject& params) const;
};
} // namespace softbus::api::ipc

View File

@@ -4,13 +4,16 @@
#include <QDateTime> #include <QDateTime>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonParseError>
#include "api/ipc_dbus/CommandDispatcher.h"
#include "src/core/CoreService.h" #include "src/core/CoreService.h"
IpcDBus::IpcDBus(CoreService* coreService, const QString& role, QObject* parent) IpcDBus::IpcDBus(CoreService* coreService, const QString& role, QObject* parent)
: QObject(parent), : QObject(parent),
m_coreService(coreService), m_coreService(coreService),
m_role(role) m_role(role),
m_dispatcher(std::make_unique<softbus::api::ipc::CommandDispatcher>())
{ {
} }
@@ -68,6 +71,33 @@ bool IpcDBus::reloadConfig()
return false; return false;
} }
QString IpcDBus::executeCommand(const QString& requestJson)
{
auto makeResponse = [](const QString& id, int code, const QString& message, const QJsonObject& data) {
QJsonObject o;
o.insert(QStringLiteral("id"), id);
o.insert(QStringLiteral("code"), code);
o.insert(QStringLiteral("message"), message);
o.insert(QStringLiteral("data"), data);
return QString::fromUtf8(QJsonDocument(o).toJson(QJsonDocument::Compact));
};
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(requestJson.toUtf8(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
return makeResponse(QString(), 4000, QStringLiteral("invalid_json"), {});
}
const QJsonObject req = doc.object();
const QString id = req.value(QStringLiteral("id")).toString();
const QString action = req.value(QStringLiteral("action")).toString();
const QJsonObject params = req.value(QStringLiteral("params")).toObject();
if (action.isEmpty() || !m_dispatcher) {
return makeResponse(id, 4003, QStringLiteral("invalid_action"), {});
}
const auto result = m_dispatcher->dispatch(action, params);
return makeResponse(id, result.code, result.message, result.data);
}
void IpcDBus::updateLoadInfo() void IpcDBus::updateLoadInfo()
{ {
// 预留:后续可在此周期性采样 CPU/内存/队列长度等指标,并通过 getStatus() 输出 // 预留:后续可在此周期性采样 CPU/内存/队列长度等指标,并通过 getStatus() 输出

View File

@@ -8,9 +8,11 @@
#include <QObject> #include <QObject>
#include <memory>
#include <QString> #include <QString>
// 前向声明 // 前向声明
class CoreService; class CoreService;
namespace softbus::api::ipc { class CommandDispatcher; }
/** /**
* @brief D-Bus 服务适配器 - 暴露核心服务接口 * @brief D-Bus 服务适配器 - 暴露核心服务接口
*/ */
@@ -54,6 +56,7 @@ public slots:
bool reloadConfig(); bool reloadConfig();
QString executeCommand(const QString& requestJson);
@@ -64,6 +67,7 @@ private slots:
private: private:
CoreService* m_coreService{nullptr}; // non-owning CoreService* m_coreService{nullptr}; // non-owning
QString m_role; QString m_role;
std::unique_ptr<softbus::api::ipc::CommandDispatcher> m_dispatcher;
}; };
#endif // IPC_DBUS_H #endif // IPC_DBUS_H

View File

@@ -1,5 +1,6 @@
#include "CoreService.h" #include "CoreService.h"
#include <QCoreApplication>
#include <QFile> #include <QFile>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
@@ -43,16 +44,18 @@ bool CoreService::initialize()
// 1. 解析配置路径(放在用户 home 下的相对固定目录:~/softbus/config // 1. 解析配置路径(放在用户 home 下的相对固定目录:~/softbus/config
const QString configPath = const QString configPath =
QDir::homePath() + QStringLiteral("/softbus/config/daemon_config.json"); QDir::homePath() + QStringLiteral("/softbus/config/daemon_config.json");
const QString deviceTreePath =
QDir::homePath() + QStringLiteral("/softbus/config/device_tree.json");
auto& bus = softbus::device_bus::DeviceBus::instance(); auto& bus = softbus::device_bus::DeviceBus::instance();
bus.setConfigPath(configPath); bus.setConfigPath(configPath);
bus.setDeviceTreePath(deviceTreePath);
QJsonObject root; QJsonObject root;
const QFileInfo fi(configPath); const QFileInfo fi(configPath);
if (!fi.exists()) { if (!fi.exists()) {
LOG_WARNING() << "CoreService: config not found, skip load:" << configPath; LOG_WARNING() << "CoreService: config not found, skip load:" << configPath;
root.insert(QStringLiteral("device_tree"), QJsonArray{}); root = QJsonObject{};
bus.markDeviceTreeDirty(); // 首次启动:退出时至少会落一个最小配置文件
} else { } else {
QFile f(configPath); QFile f(configPath);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
@@ -72,6 +75,69 @@ bool CoreService::initialize()
root = doc.object(); root = doc.object();
} }
const QFileInfo treeFi(deviceTreePath);
if (treeFi.exists()) {
QFile treeFile(deviceTreePath);
if (!treeFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_ERROR() << "CoreService: failed to open" << deviceTreePath;
return false;
}
const QByteArray treeData = treeFile.readAll();
treeFile.close();
QJsonParseError treePe;
const QJsonDocument treeDoc = QJsonDocument::fromJson(treeData, &treePe);
if (treePe.error != QJsonParseError::NoError || !treeDoc.isArray()) {
LOG_ERROR() << "CoreService: invalid JSON array in" << deviceTreePath << ":" << treePe.errorString();
return false;
}
root.insert(QStringLiteral("device_tree"), treeDoc.array());
} else {
if (!root.contains(QStringLiteral("device_tree")) || !root.value(QStringLiteral("device_tree")).isArray()) {
root.insert(QStringLiteral("device_tree"), QJsonArray{});
}
bus.markDeviceTreeDirty(); // 首次独立设备树文件缺失:退出时落一份初始文件
}
auto resolvePluginPaths = [&](const QString& key) {
QJsonObject plugins = root.value(QStringLiteral("plugins")).toObject();
QJsonArray arr = plugins.value(key).toArray();
if (arr.isEmpty()) {
return;
}
const QString configDir = fi.dir().absolutePath();
const QString appDir = QCoreApplication::applicationDirPath();
for (int i = 0; i < arr.size(); ++i) {
QJsonObject obj = arr.at(i).toObject();
const QString rawPath = obj.value(QStringLiteral("path")).toString();
if (rawPath.isEmpty() || QFileInfo(rawPath).isAbsolute()) {
continue;
}
// 先尝试绝对路径,再尝试相对路径 TODO 后续可以优化为只尝试相对路径
const QStringList candidates = {
QDir(configDir).filePath(rawPath),
QDir(configDir).filePath(QStringLiteral("../") + rawPath),
QDir(appDir).filePath(rawPath),
QDir(appDir).filePath(QStringLiteral("../") + rawPath),
};
QString resolved = rawPath;
for (const auto& c : candidates) {
if (QFileInfo::exists(c)) {
resolved = QFileInfo(c).absoluteFilePath();
break;
}
}
obj.insert(QStringLiteral("path"), resolved);
arr[i] = obj;
}
plugins.insert(key, arr);
root.insert(QStringLiteral("plugins"), plugins);
};
resolvePluginPaths(QStringLiteral("protocols"));
resolvePluginPaths(QStringLiteral("framers"));
// 2. 初始化 device_bus交给 DeviceBus facade 处理具体设备) // 2. 初始化 device_bus交给 DeviceBus facade 处理具体设备)
if (!bus.initialize(root)) { if (!bus.initialize(root)) {

View File

@@ -1,9 +1,12 @@
#pragma once #pragma once
// 定义DOM消息 // 定义DOM消息
#include <cstdint> #include <cstdint>
#include <vector>
#include <variant> #include <variant>
#include <QString> #include <QString>
#include <QHash>
#include <QJsonValue>
#include "core/models/MetadataDef.h" #include "core/models/MetadataDef.h"
#include "core/models/Types.h" #include "core/models/Types.h"
@@ -18,11 +21,13 @@ class DOMMessage
public: public:
DOMMessage() = default; DOMMessage() = default;
QString messageId; QString messageId; // 消息ID
const MetadataDef* metaRef{nullptr}; const MetadataDef* metaRef{nullptr}; // 元数据引用
std::int64_t timestamp{0}; std::int64_t timestamp{0}; // 时间戳
DOMValue value{0.0}; DOMValue value{0.0}; // 值
DataQuality quality{DataQuality::BAD}; DataQuality quality{DataQuality::BAD}; // 质量
QHash<QString, QJsonValue> attributes; // 属性
std::vector<QString> traceLog; // 跟踪日志
DataDomain domain() const { return metaRef ? metaRef->domain : DataDomain::UNKNOWN; } DataDomain domain() const { return metaRef ? metaRef->domain : DataDomain::UNKNOWN; }
@@ -34,6 +39,11 @@ public:
} }
return T{}; return T{};
} }
void appendTrace(const QString& log)
{
traceLog.push_back(log);
}
}; };
} // namespace softbus::core::models } // namespace softbus::core::models

View File

@@ -0,0 +1,25 @@
#pragma once
#include <QString>
#include <QHash>
#include <QJsonValue>
#include "core/models/DOMMessage.h"
namespace softbus::core::models
{
struct EnrichedMessage
{
QString sourceMessageId;
QString logicalName;
QString unit;
DOMValue engineeringValue{0.0};
DataQuality quality{DataQuality::BAD};
bool alarm{false};
QHash<QString, QJsonValue> attributes;
std::vector<QString> traceLog;
};
} // namespace softbus::core::models

View File

@@ -9,16 +9,16 @@ namespace softbus::core::models
struct MetadataDef struct MetadataDef
{ {
QString metadataId; QString metadataId; // 元数据ID 唯一标识
QString name; QString name; // 名称
QString description; QString description; // 描述
DataDomain domain{DataDomain::UNKNOWN}; DataDomain domain{DataDomain::UNKNOWN}; // 数据域
DataType semanticType{DataType::FLOAT64}; DataType semanticType{DataType::FLOAT64}; // 语义类型
QString unit; QString unit; // 单位
double minValue{0.0}; double minValue{0.0}; // 最小值
double maxValue{0.0}; double maxValue{0.0}; // 最大值
double deadband{0.0}; double deadband{0.0}; // 死区
bool isWriteable{false}; bool isWriteable{false}; // 是否可写
}; };
} // namespace softbus::core::models } // namespace softbus::core::models

View File

@@ -0,0 +1,36 @@
#pragma once
#include <memory>
#include <vector>
#include <QString>
#include "core/models/DOMMessage.h"
#include "message_bus/pipeline/PipelineContext.h"
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
namespace softbus::core::plugin_system
{
class IProtocolMapperSession
{
public:
virtual ~IProtocolMapperSession() = default;
virtual std::vector<softbus::core::models::DOMMessage> map(
const softbus::message_bus::pipeline::PipelineContext& rawContext,
const softbus::message_bus::pipeline::protocol::ProtocolEnvelope& envelope) = 0;
virtual void reset() = 0;
};
class IProtocolMapperPlugin
{
public:
virtual ~IProtocolMapperPlugin() = default;
virtual QString pluginId() const = 0;
virtual bool supports(const QString& protocolHint) const = 0;
virtual std::unique_ptr<IProtocolMapperSession> createMapperSession() = 0;
};
} // namespace softbus::core::plugin_system

View File

@@ -4,6 +4,7 @@
#include <QString> #include <QString>
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
#include "message_bus/pipeline/PipelineContext.h" #include "message_bus/pipeline/PipelineContext.h"
namespace softbus::core::plugin_system namespace softbus::core::plugin_system
@@ -13,7 +14,8 @@ class IProtocolSession
{ {
public: public:
virtual ~IProtocolSession() = default; virtual ~IProtocolSession() = default;
virtual bool feed(softbus::message_bus::pipeline::PipelineContext& ctx) = 0; virtual bool parse(const softbus::message_bus::pipeline::PipelineContext& ctx,
softbus::message_bus::pipeline::protocol::ProtocolEnvelope& out) = 0;
virtual void reset() = 0; virtual void reset() = 0;
}; };

View File

@@ -3,6 +3,7 @@
#include <dlfcn.h> #include <dlfcn.h>
#include "core/plugin_system/IProtocolFramerPlugin.h" #include "core/plugin_system/IProtocolFramerPlugin.h"
#include "core/plugin_system/IProtocolMapperPlugin.h"
#include "core/plugin_system/IProtocolPlugin.h" #include "core/plugin_system/IProtocolPlugin.h"
namespace softbus::core::plugin_system namespace softbus::core::plugin_system
@@ -14,6 +15,7 @@ namespace
using GetAbiVersionFn = int (*)(); using GetAbiVersionFn = int (*)();
using CreateProtocolPluginFn = IProtocolPlugin* (*)(); using CreateProtocolPluginFn = IProtocolPlugin* (*)();
using CreateFramerPluginFn = IProtocolFramerPlugin* (*)(); using CreateFramerPluginFn = IProtocolFramerPlugin* (*)();
using CreateMapperPluginFn = IProtocolMapperPlugin* (*)();
} // namespace } // namespace
@@ -67,10 +69,20 @@ bool PluginHost::loadPlugin(const QString& path, QString* err)
m_manager->registerProtocolFramerPlugin(p); m_manager->registerProtocolFramerPlugin(p);
} }
} }
auto* createMapper = reinterpret_cast<CreateMapperPluginFn>(dlsym(handle, "softbus_create_mapper_plugin"));
QString mapperPluginId;
if (createMapper) {
std::shared_ptr<IProtocolMapperPlugin> p(createMapper());
if (p) {
mapperPluginId = p->pluginId();
m_manager->registerProtocolMapperPlugin(p);
}
}
LoadedPlugin lp; LoadedPlugin lp;
lp.path = path; lp.path = path;
lp.protocolPluginId = protocolPluginId; lp.protocolPluginId = protocolPluginId;
lp.framerPluginId = framerPluginId; lp.framerPluginId = framerPluginId;
lp.mapperPluginId = mapperPluginId;
lp.abiVersion = abi; lp.abiVersion = abi;
lp.handle = handle; lp.handle = handle;
m_loaded.insert(path, lp); m_loaded.insert(path, lp);
@@ -105,6 +117,9 @@ bool PluginHost::unloadPlugin(const QString& path, QString* err)
if (!it.value().framerPluginId.isEmpty()) { if (!it.value().framerPluginId.isEmpty()) {
m_manager->unregisterProtocolFramerPlugin(it.value().framerPluginId); m_manager->unregisterProtocolFramerPlugin(it.value().framerPluginId);
} }
if (!it.value().mapperPluginId.isEmpty()) {
m_manager->unregisterProtocolMapperPlugin(it.value().mapperPluginId);
}
} }
if (it.value().handle) { if (it.value().handle) {
dlclose(it.value().handle); dlclose(it.value().handle);

View File

@@ -18,6 +18,7 @@ public:
QString path; QString path;
QString protocolPluginId; QString protocolPluginId;
QString framerPluginId; QString framerPluginId;
QString mapperPluginId;
int abiVersion{1}; int abiVersion{1};
void* handle{nullptr}; void* handle{nullptr};
}; };

View File

@@ -49,4 +49,42 @@ void PluginManager::unregisterProtocolFramerPlugin(const QString& pluginId)
m_protocolFramerPlugins.remove(pluginId); m_protocolFramerPlugins.remove(pluginId);
} }
void PluginManager::registerProtocolMapperPlugin(std::shared_ptr<IProtocolMapperPlugin> plugin)
{
if (!plugin) {
return;
}
m_protocolMapperPlugins.insert(plugin->pluginId(), std::move(plugin));
}
std::shared_ptr<IProtocolMapperPlugin> PluginManager::selectProtocolMapperPlugin(const QString& protocolHint) const
{
for (auto it = m_protocolMapperPlugins.constBegin(); it != m_protocolMapperPlugins.constEnd(); ++it) {
if (it.value() && it.value()->supports(protocolHint)) {
return it.value();
}
}
return {};
}
void PluginManager::unregisterProtocolMapperPlugin(const QString& pluginId)
{
m_protocolMapperPlugins.remove(pluginId);
}
QStringList PluginManager::registeredProtocolPluginIds() const
{
return m_protocolPlugins.keys();
}
QStringList PluginManager::registeredProtocolFramerPluginIds() const
{
return m_protocolFramerPlugins.keys();
}
QStringList PluginManager::registeredProtocolMapperPluginIds() const
{
return m_protocolMapperPlugins.keys();
}
} // namespace softbus::core::plugin_system } // namespace softbus::core::plugin_system

View File

@@ -4,8 +4,10 @@
#include <QHash> #include <QHash>
#include <QString> #include <QString>
#include <QStringList>
#include "core/plugin_system/IProtocolFramerPlugin.h" #include "core/plugin_system/IProtocolFramerPlugin.h"
#include "core/plugin_system/IProtocolMapperPlugin.h"
#include "core/plugin_system/IProtocolPlugin.h" #include "core/plugin_system/IProtocolPlugin.h"
namespace softbus::core::plugin_system namespace softbus::core::plugin_system
@@ -22,10 +24,17 @@ public:
void registerProtocolFramerPlugin(std::shared_ptr<IProtocolFramerPlugin> plugin); void registerProtocolFramerPlugin(std::shared_ptr<IProtocolFramerPlugin> plugin);
std::shared_ptr<IProtocolFramerPlugin> selectProtocolFramerPlugin(const QString& protocolHint) const; std::shared_ptr<IProtocolFramerPlugin> selectProtocolFramerPlugin(const QString& protocolHint) const;
void unregisterProtocolFramerPlugin(const QString& pluginId); void unregisterProtocolFramerPlugin(const QString& pluginId);
void registerProtocolMapperPlugin(std::shared_ptr<IProtocolMapperPlugin> plugin);
std::shared_ptr<IProtocolMapperPlugin> selectProtocolMapperPlugin(const QString& protocolHint) const;
void unregisterProtocolMapperPlugin(const QString& pluginId);
QStringList registeredProtocolPluginIds() const;
QStringList registeredProtocolFramerPluginIds() const;
QStringList registeredProtocolMapperPluginIds() const;
private: private:
QHash<QString, std::shared_ptr<IProtocolPlugin>> m_protocolPlugins; QHash<QString, std::shared_ptr<IProtocolPlugin>> m_protocolPlugins;
QHash<QString, std::shared_ptr<IProtocolFramerPlugin>> m_protocolFramerPlugins; QHash<QString, std::shared_ptr<IProtocolFramerPlugin>> m_protocolFramerPlugins;
QHash<QString, std::shared_ptr<IProtocolMapperPlugin>> m_protocolMapperPlugins;
}; };
} // namespace softbus::core::plugin_system } // namespace softbus::core::plugin_system

View File

@@ -20,6 +20,7 @@ public:
std::unique_ptr<registry::DeviceRegistry> registry; std::unique_ptr<registry::DeviceRegistry> registry;
std::shared_ptr<DeviceTreeModel> treeModel; std::shared_ptr<DeviceTreeModel> treeModel;
QString configPath; QString configPath;
QString deviceTreePath;
bool deviceTreeDirty{false}; // 设备树是否脏了 bool deviceTreeDirty{false}; // 设备树是否脏了
}; };
@@ -97,33 +98,33 @@ void DeviceBus::shutdown() {
return; return;
} }
if (d->configPath.isEmpty() || !d->treeModel) { if (d->deviceTreePath.isEmpty() || !d->treeModel) {
return; return;
} }
const QFileInfo fi(d->configPath); const QFileInfo fi(d->deviceTreePath);
const QString dirPath = fi.dir().absolutePath(); const QString dirPath = fi.dir().absolutePath();
LOG_INFO() << "DeviceBus: saving config to" << dirPath; LOG_INFO() << "DeviceBus: saving device_tree to" << d->deviceTreePath;
if (!QDir().mkpath(dirPath)) { if (!QDir().mkpath(dirPath)) {
LOG_ERROR() << "DeviceBus: failed to create config dir" << dirPath; LOG_ERROR() << "DeviceBus: failed to create config dir" << dirPath;
return; return;
} }
const QJsonObject root = d->treeModel->toDaemonConfigRoot(); const QJsonArray tree = d->treeModel->toDeviceTreeJson();
const QByteArray bytes = QJsonDocument(root).toJson(QJsonDocument::Indented); const QByteArray bytes = QJsonDocument(tree).toJson(QJsonDocument::Indented);
QSaveFile out(d->configPath); QSaveFile out(d->deviceTreePath);
if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) { if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
LOG_ERROR() << "DeviceBus: failed to open config for write:" << d->configPath; LOG_ERROR() << "DeviceBus: failed to open device tree for write:" << d->deviceTreePath;
return; return;
} }
if (out.write(bytes) != bytes.size()) { if (out.write(bytes) != bytes.size()) {
LOG_ERROR() << "DeviceBus: failed to write config:" << d->configPath; LOG_ERROR() << "DeviceBus: failed to write device tree:" << d->deviceTreePath;
out.cancelWriting(); out.cancelWriting();
return; return;
} }
if (!out.commit()) { if (!out.commit()) {
LOG_ERROR() << "DeviceBus: failed to commit config:" << d->configPath; LOG_ERROR() << "DeviceBus: failed to commit device tree:" << d->deviceTreePath;
return; return;
} }
@@ -140,6 +141,16 @@ QString DeviceBus::configPath() const {
return d->configPath; return d->configPath;
} }
void DeviceBus::setDeviceTreePath(const QString& path) {
if (!d) return;
d->deviceTreePath = path;
}
QString DeviceBus::deviceTreePath() const {
if (!d) return {};
return d->deviceTreePath;
}
std::shared_ptr<DeviceTreeModel> DeviceBus::deviceTreeModel() const { std::shared_ptr<DeviceTreeModel> DeviceBus::deviceTreeModel() const {
if (!d) return {}; if (!d) return {};
return d->treeModel; return d->treeModel;

View File

@@ -17,6 +17,8 @@ public:
void setConfigPath(const QString& path); void setConfigPath(const QString& path);
QString configPath() const; QString configPath() const;
void setDeviceTreePath(const QString& path);
QString deviceTreePath() const;
// 共享设备树模型(单一真源) // 共享设备树模型(单一真源)
std::shared_ptr<DeviceTreeModel> deviceTreeModel() const; std::shared_ptr<DeviceTreeModel> deviceTreeModel() const;

View File

@@ -11,7 +11,6 @@
#include "message_bus/ingress/DriverIngressAdapter.h" #include "message_bus/ingress/DriverIngressAdapter.h"
#include "message_bus/pipeline/PipelineEngine.h" #include "message_bus/pipeline/PipelineEngine.h"
#include "core/plugin_system/PluginManager.h" #include "core/plugin_system/PluginManager.h"
#include "plugins/protocols/modbusrtu/parser/ModbusRtuProtocolPlugin.h"
#include "device_bus/manager/SerialDeviceManager.h" #include "device_bus/manager/SerialDeviceManager.h"
#include "devices/DeviceTypes.h" #include "devices/DeviceTypes.h"
#include "utils/logs/logging.h" #include "utils/logs/logging.h"
@@ -61,19 +60,38 @@ bool DeviceBusManager::initialize(const QJsonObject& rootConfig)
if (!m_pluginHost) { if (!m_pluginHost) {
m_pluginHost = std::make_unique<softbus::core::plugin_system::PluginHost>(m_pluginManager); m_pluginHost = std::make_unique<softbus::core::plugin_system::PluginHost>(m_pluginManager);
} }
m_pluginManager->registerProtocolPlugin(softbus::core::plugin_system::makeModbusRtuProtocolPlugin());
const auto pluginsObj = rootConfig.value(QStringLiteral("plugins")).toObject(); const auto pluginsObj = rootConfig.value(QStringLiteral("plugins")).toObject();
const auto framerArr = pluginsObj.value(QStringLiteral("framers")).toArray(); QSet<QString> loadedPluginPaths;
for (const auto& v : framerArr) { auto loadPlugins = [&](const QJsonArray& arr, const char* sourceTag) {
const auto obj = v.toObject(); for (const auto& v : arr) {
const QString path = obj.value(QStringLiteral("path")).toString(); const auto obj = v.toObject();
if (path.isEmpty()) { const QString path = obj.value(QStringLiteral("path")).toString();
continue; if (path.isEmpty() || loadedPluginPaths.contains(path)) {
} continue;
QString err; }
if (!m_pluginHost->loadPlugin(path, &err)) { QString err;
LOG_WARNING() << "DeviceBusManager: framer plugin load failed path=" << path << "err=" << err; if (!m_pluginHost->loadPlugin(path, &err)) {
LOG_WARNING() << "DeviceBusManager: plugin load failed source=" << sourceTag
<< "path=" << path << "err=" << err;
continue;
}
loadedPluginPaths.insert(path);
} }
};
// New generic entry for protocol plugins; each .so may export parser/framer/mapper factories.
loadPlugins(pluginsObj.value(QStringLiteral("protocols")).toArray(), "protocols");
// Backward compatibility: legacy key still supported.
loadPlugins(pluginsObj.value(QStringLiteral("framers")).toArray(), "framers");
if (m_pluginManager) {
const auto parserIds = m_pluginManager->registeredProtocolPluginIds();
const auto framerIds = m_pluginManager->registeredProtocolFramerPluginIds();
const auto mapperIds = m_pluginManager->registeredProtocolMapperPluginIds();
LOG_INFO() << "DeviceBusManager: registered parser plugins(" << parserIds.size()
<< "):" << parserIds.join(QStringLiteral(", "));
LOG_INFO() << "DeviceBusManager: registered framer plugins(" << framerIds.size()
<< "):" << framerIds.join(QStringLiteral(", "));
LOG_INFO() << "DeviceBusManager: registered mapper plugins(" << mapperIds.size()
<< "):" << mapperIds.join(QStringLiteral(", "));
} }
if (m_pipelineEngine) { if (m_pipelineEngine) {
m_pipelineEngine->setMemoryPool(m_memoryPool); m_pipelineEngine->setMemoryPool(m_memoryPool);

View File

@@ -98,8 +98,8 @@ bool initializeSerialDeviceWithDeps(const QString& endpoint, const QJsonObject&
merged.insert(QStringLiteral("stopBits"), stopBitsVal); merged.insert(QStringLiteral("stopBits"), stopBitsVal);
merged.insert(QStringLiteral("flowControl"), flowStr); merged.insert(QStringLiteral("flowControl"), flowStr);
LOG_INFO() << "DeviceBusManager: init serial endpoint=" << endpoint // LOG_INFO() << "DeviceBusManager: init serial endpoint=" << endpoint
<< "params=" << QJsonDocument(merged).toJson(QJsonDocument::Compact); // << "params=" << QJsonDocument(merged).toJson(QJsonDocument::Compact);
persistSerialParams(deps.treeModel, endpoint, merged, deps.dirtyCb); persistSerialParams(deps.treeModel, endpoint, merged, deps.dirtyCb);

View File

@@ -115,18 +115,18 @@ void DeviceMonitorService::onEvent(const DeviceEvent& ev)
} }
// 第一版:仅日志 // 第一版:仅日志
LOG_INFO() << "DeviceMonitor event" // LOG_INFO() << "DeviceMonitor event"
<< "type=" << toString(ev.type) // << "type=" << toString(ev.type)
<< "action=" << toString(ev.action) // << "action=" << toString(ev.action)
<< "subsystem=" << ev.subsystem // << "subsystem=" << ev.subsystem
<< "devnode=" << ev.devnode // << "devnode=" << ev.devnode
<< "ifname=" << ev.ifname // << "ifname=" << ev.ifname
<< "ifindex=" << ev.ifindex // << "ifindex=" << ev.ifindex
<< "linkUp=" << ev.linkUp // << "linkUp=" << ev.linkUp
<< "vid=" << ev.vid // << "vid=" << ev.vid
<< "pid=" << ev.pid // << "pid=" << ev.pid
<< "serial=" << ev.serial // << "serial=" << ev.serial
<< "path=" << ev.devicePath; // << "path=" << ev.devicePath;
} }
} // namespace softbus::device_bus::monitor } // namespace softbus::device_bus::monitor

View File

@@ -197,11 +197,11 @@ bool UdevDeviceMonitor::shouldKeepEvent(const DeviceEvent& ev) const
return false; return false;
} }
if (isConsoleLikeTty(ev.devnode)) { if (isConsoleLikeTty(ev.devnode)) {
LOG_INFO() << "UdevDeviceMonitor: console like tty" << ev.devnode; // LOG_INFO() << "UdevDeviceMonitor: console like tty" << ev.devnode;
return false; return false;
} }
if (!isWhitelistedSerialDevnode(ev.devnode)) { if (!isWhitelistedSerialDevnode(ev.devnode)) {
LOG_INFO() << "UdevDeviceMonitor: not whitelisted serial devnode" << ev.devnode; // LOG_INFO() << "UdevDeviceMonitor: not whitelisted serial devnode" << ev.devnode;
return false; return false;
} }
} }

View File

@@ -0,0 +1,95 @@
#include "message_bus/pipeline/DynamicRuleRegistry.h"
#include <mutex>
#include <shared_mutex>
namespace softbus::message_bus::pipeline
{
DynamicRuleRegistry& DynamicRuleRegistry::instance()
{
static DynamicRuleRegistry registry;
return registry;
}
bool DynamicRuleRegistry::addFilterRule(const FilterRule& rule)
{
if (rule.id.isEmpty()) {
return false;
}
std::unique_lock<std::shared_mutex> lk(m_mtx);
m_filterRules.insert(rule.id, rule);
return true;
}
bool DynamicRuleRegistry::removeFilterRule(const QString& id)
{
std::unique_lock<std::shared_mutex> lk(m_mtx);
return m_filterRules.remove(id) > 0;
}
bool DynamicRuleRegistry::addTransformRule(const TransformRule& rule)
{
if (rule.id.isEmpty()) {
return false;
}
std::unique_lock<std::shared_mutex> lk(m_mtx);
m_transformRules.insert(rule.id, rule);
return true;
}
bool DynamicRuleRegistry::removeTransformRule(const QString& id)
{
std::unique_lock<std::shared_mutex> lk(m_mtx);
return m_transformRules.remove(id) > 0;
}
std::vector<FilterRule> DynamicRuleRegistry::snapshotFilterRules(
const QString& endpoint, int deviceId, const QString& stableKey) const
{
std::shared_lock<std::shared_mutex> lk(m_mtx);
std::vector<FilterRule> out;
out.reserve(static_cast<std::size_t>(m_filterRules.size()));
for (auto it = m_filterRules.constBegin(); it != m_filterRules.constEnd(); ++it) {
if (matchScope(it.value().scope, endpoint, deviceId, stableKey)) {
out.push_back(it.value());
}
}
return out;
}
std::vector<TransformRule> DynamicRuleRegistry::snapshotTransformRules(
const QString& endpoint, int deviceId, const QString& stableKey) const
{
std::shared_lock<std::shared_mutex> lk(m_mtx);
std::vector<TransformRule> out;
out.reserve(static_cast<std::size_t>(m_transformRules.size()));
for (auto it = m_transformRules.constBegin(); it != m_transformRules.constEnd(); ++it) {
if (matchScope(it.value().scope, endpoint, deviceId, stableKey)) {
out.push_back(it.value());
}
}
return out;
}
QJsonObject DynamicRuleRegistry::status() const
{
std::shared_lock<std::shared_mutex> lk(m_mtx);
QJsonObject o;
o.insert(QStringLiteral("filterRuleCount"), m_filterRules.size());
o.insert(QStringLiteral("transformRuleCount"), m_transformRules.size());
return o;
}
bool DynamicRuleRegistry::matchScope(
const RuleScope& scope, const QString& endpoint, int deviceId, const QString& stableKey) const
{
// hybrid scope: global + endpoint/device/stableKey selective match
const bool endpointOk = scope.endpoint.isEmpty() || scope.endpoint == endpoint;
const bool deviceOk = scope.deviceId < 0 || scope.deviceId == deviceId;
const bool stableKeyOk = scope.stableKey.isEmpty() || scope.stableKey == stableKey;
return endpointOk && deviceOk && stableKeyOk;
}
} // namespace softbus::message_bus::pipeline

View File

@@ -0,0 +1,64 @@
#pragma once
#include <shared_mutex>
#include <vector>
#include <QHash>
#include <QJsonObject>
#include <QString>
namespace softbus::message_bus::pipeline
{
struct RuleScope
{
QString endpoint;
int deviceId{-1};
QString stableKey;
};
struct FilterRule
{
QString id;
RuleScope scope;
QJsonObject condition;
};
struct TransformRule
{
QString id;
RuleScope scope;
double scale{1.0};
double offset{0.0};
QString unit;
};
class DynamicRuleRegistry
{
public:
static DynamicRuleRegistry& instance();
bool addFilterRule(const FilterRule& rule);
bool removeFilterRule(const QString& id);
bool addTransformRule(const TransformRule& rule);
bool removeTransformRule(const QString& id);
std::vector<FilterRule> snapshotFilterRules(
const QString& endpoint, int deviceId, const QString& stableKey) const;
std::vector<TransformRule> snapshotTransformRules(
const QString& endpoint, int deviceId, const QString& stableKey) const;
QJsonObject status() const;
private:
DynamicRuleRegistry() = default;
bool matchScope(const RuleScope& scope, const QString& endpoint, int deviceId, const QString& stableKey) const;
private:
mutable std::shared_mutex m_mtx;
QHash<QString, FilterRule> m_filterRules;
QHash<QString, TransformRule> m_transformRules;
};
} // namespace softbus::message_bus::pipeline

View File

@@ -5,14 +5,10 @@
#include <cstdint> #include <cstdint>
#include <QDateTime> #include <QDateTime>
#include <QJsonObject>
#include <QMetaType> #include <QMetaType>
#include <QString> #include <QString>
#include <QVector>
#include "core/models/DOMMessage.h"
#include "message_bus/pipeline/PayloadRef.h" #include "message_bus/pipeline/PayloadRef.h"
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
namespace softbus::message_bus::pipeline namespace softbus::message_bus::pipeline
{ {
@@ -46,13 +42,6 @@ struct PipelineContext
BusDirection direction{BusDirection::Unknown}; BusDirection direction{BusDirection::Unknown};
std::uint64_t frameSeq{0}; std::uint64_t frameSeq{0};
/// 强类型协议解析结果(推荐下游使用);多协议扩展时只增不改 variant。
softbus::message_bus::pipeline::protocol::ProtocolEnvelope protocol;
/// 兼容旧逻辑与日志;新代码请优先使用 `protocol`,勿依赖此处 JSON 字段名。
QJsonObject parsed;
QVector<softbus::core::models::DOMMessage> domMessages;
QString framerError; // 成帧/Pipeline 旁路诊断(可选)
}; };
} // namespace softbus::message_bus::pipeline } // namespace softbus::message_bus::pipeline

View File

@@ -5,9 +5,6 @@
#include <sstream> #include <sstream>
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFraming.h"
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFramerPlugin.h"
#include "message_bus/pipeline/stages/3_filters/ProtocolParseFilter.h" #include "message_bus/pipeline/stages/3_filters/ProtocolParseFilter.h"
#include "utils/logs/logging.h" #include "utils/logs/logging.h"
@@ -91,9 +88,6 @@ void PipelineEngine::loadRuntimeConfigFromJson(const QJsonObject& obj)
std::max(64, obj.value(QStringLiteral("framedQueueMax")).toInt(4096))); std::max(64, obj.value(QStringLiteral("framedQueueMax")).toInt(4096)));
m_rtConfig.maxReorderDepth = static_cast<std::size_t>( m_rtConfig.maxReorderDepth = static_cast<std::size_t>(
std::max(16, obj.value(QStringLiteral("maxReorderDepth")).toInt(256))); std::max(16, obj.value(QStringLiteral("maxReorderDepth")).toInt(256)));
m_rtConfig.modbusMaxAdu = static_cast<std::size_t>(
std::max(8, obj.value(QStringLiteral("modbusRtuMaxFrameBytes")).toInt(256)));
m_rtConfig.modbusInterFrameTimeoutMs = obj.value(QStringLiteral("modbusRtuInterFrameTimeoutMs")).toInt(0);
} }
void PipelineEngine::start() void PipelineEngine::start()
@@ -110,13 +104,6 @@ void PipelineEngine::start()
m_passthroughFramer = m_passthroughFramer =
std::make_shared<softbus::message_bus::pipeline::stages::framers::PassthroughFramer>(m_pool); std::make_shared<softbus::message_bus::pipeline::stages::framers::PassthroughFramer>(m_pool);
if (m_pluginManager) {
softbus::message_bus::pipeline::protocol::modbusrtu::ModbusRtuFramerPluginConfig cfg;
cfg.maxAdu = m_rtConfig.modbusMaxAdu;
cfg.interFrameTimeoutMs = m_rtConfig.modbusInterFrameTimeoutMs;
m_pluginManager->registerProtocolFramerPlugin(
softbus::message_bus::pipeline::protocol::modbusrtu::makeModbusRtuFramerPlugin(m_pool, cfg));
}
// 如果启用并行管道 则创建帧队列和解析线程 如果启用有序出口 则创建合并队列和合并线程 // 如果启用并行管道 则创建帧队列和解析线程 如果启用有序出口 则创建合并队列和合并线程
// 并行管道是用来处理多个端点的数据流的 // 并行管道是用来处理多个端点的数据流的
@@ -183,6 +170,14 @@ void PipelineEngine::stop()
std::lock_guard<std::mutex> lk(m_framerSessionMutex); std::lock_guard<std::mutex> lk(m_framerSessionMutex);
m_framerSessions.clear(); m_framerSessions.clear();
} }
{
std::lock_guard<std::mutex> lk(m_parserSessionMutex);
m_parserSessions.clear();
}
{
std::lock_guard<std::mutex> lk(m_mapperSessionMutex);
m_mapperSessions.clear();
}
} }
// 功能 :清空 ingress 队列 // 功能 :清空 ingress 队列
// 参数 :无 // 参数 :无
@@ -294,10 +289,7 @@ void PipelineEngine::dispatchFramer(PipelineContext& chunkIn, std::vector<Pipeli
std::lock_guard<std::mutex> lk(m_framerSessionMutex); std::lock_guard<std::mutex> lk(m_framerSessionMutex);
auto it = m_framerSessions.find(key); auto it = m_framerSessions.find(key);
if (it == m_framerSessions.end() || !it.value()) { if (it == m_framerSessions.end() || !it.value()) {
QJsonObject params; auto s = plugin->createFramerSession(chunkIn.endpoint, QJsonObject{});
params.insert(QStringLiteral("modbusRtuMaxFrameBytes"), static_cast<int>(m_rtConfig.modbusMaxAdu));
params.insert(QStringLiteral("modbusRtuInterFrameTimeoutMs"), m_rtConfig.modbusInterFrameTimeoutMs);
auto s = plugin->createFramerSession(chunkIn.endpoint, params);
m_framerSessions.insert( m_framerSessions.insert(
key, s ? std::shared_ptr<softbus::core::plugin_system::IProtocolFramerSession>(std::move(s)) : nullptr); key, s ? std::shared_ptr<softbus::core::plugin_system::IProtocolFramerSession>(std::move(s)) : nullptr);
it = m_framerSessions.find(key); it = m_framerSessions.find(key);
@@ -454,44 +446,19 @@ bool PipelineEngine::runStages234(PipelineContext& ctx)
if (ctx.frameKind != FrameKind::CompleteFrame) { if (ctx.frameKind != FrameKind::CompleteFrame) {
return false; return false;
} }
std::ostringstream oss; auto messages = stage2Parser(ctx);
oss << std::hex << std::setfill('0'); LOG_INFO() << "PipelineEngine: stage2Parser messages size=" << messages.size();
for (std::size_t i = 0; i < ctx.payload.block->size; ++i) { if (messages.empty()) {
oss << std::setw(2) << static_cast<int>(ctx.payload.block->data[i]); return false;
if (i + 1 < ctx.payload.block->size) { }
oss << ' '; for (auto& msg : messages) {
if (!stage3Filter(msg)) {
continue;
} }
} if (!stage4Validator(msg)) {
const QString hexPreview = QString::fromStdString(oss.str()); continue;
LOG_INFO() << "the data is"<< hexPreview; }
LOG_INFO() << "the protocol hint is"<< ctx.protocolHint; (void)applyTransformAndEnrich(ctx, msg);
const QString directionText = (ctx.direction == BusDirection::Upstream)
? QStringLiteral("Upstream")
: (ctx.direction == BusDirection::Downstream)
? QStringLiteral("Downstream")
: QStringLiteral("Unknown");
LOG_INFO() << "the direction is" << directionText;
if (!stage2Parser(ctx)) {
return false;
}
QJsonObject parsed = ctx.parsed;
LOG_INFO() << "the parsed is"<< parsed;
softbus::message_bus::pipeline::protocol::ProtocolEnvelope protocol = ctx.protocol;
if (const auto* modbusPdu = std::get_if<softbus::message_bus::pipeline::protocol::ModbusRtuPdu>(&protocol.pdu)) {
LOG_INFO() << "the protocol text is" << softbus::message_bus::pipeline::protocol::toText(*modbusPdu);
} else {
LOG_INFO() << "the protocol text is" << QStringLiteral("ProtocolEnvelopePdu::monostate");
}
// @TODO 以后需要测试是否丢包和解析延迟
// m_count++;
// LOG_INFO() << "the count of command is" << m_count;
if (!stage3Filter(ctx)) {
return false;
}
if (!stage4Validator(ctx)) {
return false;
} }
return true; return true;
} }
@@ -500,47 +467,97 @@ bool PipelineEngine::runStages234(PipelineContext& ctx)
// 参数 ctx 数据 // 参数 ctx 数据
// 返回值 :是否成功 // 返回值 :是否成功
// 逻辑 :如果有效载荷无效 则返回false 如果都成功 则返回true // 逻辑 :如果有效载荷无效 则返回false 如果都成功 则返回true
bool PipelineEngine::stage4Validator(PipelineContext& ctx) bool PipelineEngine::stage4Validator(const softbus::core::models::DOMMessage& message) const
{ {
if (!ctx.payload.valid()) { return !message.messageId.isEmpty();
return false;
}
if (ctx.endpoint.isEmpty()) {
return false;
}
if (!ctx.timestamp.isValid()) {
ctx.timestamp = QDateTime::currentDateTimeUtc();
}
if (ctx.frameKind != FrameKind::CompleteFrame) {
return false;
}
return true;
} }
// 功能 运行阶段2解析器 // 功能 运行阶段2解析器
// 参数 ctx 数据 // 参数 ctx 数据
// 返回值 :是否成功 // 返回值 :是否成功
// 逻辑 :如果插件管理器为空 则返回true 如果插件管理器不为空 则选择合适的插件 创建会话 如果会话为空 则返回false 如果会话不为空 则返回true // 逻辑 :如果插件管理器为空 则返回true 如果插件管理器不为空 则选择合适的插件 创建会话 如果会话为空 则返回false 如果会话不为空 则返回true
bool PipelineEngine::stage2Parser(PipelineContext& ctx) std::vector<softbus::core::models::DOMMessage> PipelineEngine::stage2Parser(const PipelineContext& ctx)
{ {
if (!m_pluginManager) { if (!m_pluginManager) {
return true; return {};
} }
auto plugin = m_pluginManager->selectProtocolPlugin(ctx.protocolHint); auto parserPlugin = m_pluginManager->selectProtocolPlugin(ctx.protocolHint);
if (!plugin) { if (!parserPlugin) {
return true; return {};
} }
auto session = plugin->createSession(); const QString parserKey = ctx.endpoint + QStringLiteral("|") + parserPlugin->pluginId();
if (!session) { std::shared_ptr<softbus::core::plugin_system::IProtocolSession> parserSession;
return false; {
std::lock_guard<std::mutex> lk(m_parserSessionMutex);
auto it = m_parserSessions.find(parserKey);
if (it == m_parserSessions.end() || !it.value()) {
auto s = parserPlugin->createSession();
m_parserSessions.insert(
parserKey,
s ? std::shared_ptr<softbus::core::plugin_system::IProtocolSession>(std::move(s)) : nullptr);
it = m_parserSessions.find(parserKey);
}
if (it == m_parserSessions.end() || !it.value()) {
return {};
}
parserSession = it.value();
} }
return session->feed(ctx); softbus::message_bus::pipeline::protocol::ProtocolEnvelope envelope; // 协议封装
if (!parserSession->parse(ctx, envelope)) {
return {};
}
auto mapperPlugin = m_pluginManager->selectProtocolMapperPlugin(ctx.protocolHint);
if (!mapperPlugin) {
return {};
}
const QString key = ctx.endpoint + QStringLiteral("|") + mapperPlugin->pluginId();
std::shared_ptr<softbus::core::plugin_system::IProtocolMapperSession> session;
{
std::lock_guard<std::mutex> lk(m_mapperSessionMutex);
auto it = m_mapperSessions.find(key);
if (it == m_mapperSessions.end() || !it.value()) {
auto s = mapperPlugin->createMapperSession();
m_mapperSessions.insert(
key,
s ? std::shared_ptr<softbus::core::plugin_system::IProtocolMapperSession>(std::move(s)) : nullptr);
it = m_mapperSessions.find(key);
}
if (it == m_mapperSessions.end() || !it.value()) {
return {};
}
session = it.value();
}
return session->map(ctx, envelope);
} }
bool PipelineEngine::stage3Filter(PipelineContext& ctx) bool PipelineEngine::stage3Filter(softbus::core::models::DOMMessage& message)
{ {
static softbus::message_bus::pipeline::stages::filters::ProtocolParseFilter s_filter; static softbus::message_bus::pipeline::stages::filters::ProtocolParseFilter s_filter;
return s_filter.apply(ctx); return s_filter.process(message);
}
softbus::core::models::EnrichedMessage PipelineEngine::applyTransformAndEnrich(
const PipelineContext& rawCtx, softbus::core::models::DOMMessage& message) const
{
auto rules = softbus::message_bus::pipeline::DynamicRuleRegistry::instance().snapshotTransformRules(
rawCtx.endpoint, rawCtx.deviceId, rawCtx.stableKey);
for (const auto& rule : rules) {
const double base = std::visit([](const auto& x) { return static_cast<double>(x); }, message.value);
const double next = base * rule.scale + rule.offset;
if (next != base) {
message.appendTrace(QStringLiteral("transform:%1 %2->%3")
.arg(rule.id)
.arg(base, 0, 'g', 12)
.arg(next, 0, 'g', 12));
}
message.value = next;
if (!rule.unit.isEmpty()) {
message.attributes.insert(QStringLiteral("unit"), rule.unit);
}
}
static softbus::message_bus::pipeline::stages::filters::ProtocolParseFilter s_filter;
return s_filter.enrich(message);
} }
} // namespace softbus::message_bus::pipeline } // namespace softbus::message_bus::pipeline

View File

@@ -12,9 +12,13 @@
#include "core/plugin_system/PluginManager.h" #include "core/plugin_system/PluginManager.h"
#include "core/plugin_system/IProtocolFramerPlugin.h" #include "core/plugin_system/IProtocolFramerPlugin.h"
#include "core/plugin_system/IProtocolMapperPlugin.h"
#include "core/models/DOMMessage.h"
#include "core/models/EnrichedMessage.h"
#include "core/memory/LockFreeQueue.h" #include "core/memory/LockFreeQueue.h"
#include "core/memory/MemoryPool.h" #include "core/memory/MemoryPool.h"
#include "core/threading/MutexQueue.h" #include "core/threading/MutexQueue.h"
#include "message_bus/pipeline/DynamicRuleRegistry.h"
#include "message_bus/pipeline/PipelineContext.h" #include "message_bus/pipeline/PipelineContext.h"
#include "message_bus/pipeline/stages/1_framers/PassthroughFramer.h" #include "message_bus/pipeline/stages/1_framers/PassthroughFramer.h"
@@ -29,9 +33,6 @@ struct PipelineRuntimeConfig
std::size_t framedQueueMax{4096}; std::size_t framedQueueMax{4096};
std::size_t maxReorderDepth{256}; std::size_t maxReorderDepth{256};
std::size_t modbusMaxAdu{256};
int modbusInterFrameTimeoutMs{0};
}; };
struct EndpointFramingState; struct EndpointFramingState;
@@ -92,9 +93,11 @@ private:
bool runStages234(PipelineContext& ctx); bool runStages234(PipelineContext& ctx);
void ingestMergeItem(MergeItem item); void ingestMergeItem(MergeItem item);
bool stage2Parser(PipelineContext& ctx); std::vector<softbus::core::models::DOMMessage> stage2Parser(const PipelineContext& ctx);
bool stage3Filter(PipelineContext& ctx); bool stage3Filter(softbus::core::models::DOMMessage& message);
bool stage4Validator(PipelineContext& ctx); bool stage4Validator(const softbus::core::models::DOMMessage& message) const;
softbus::core::models::EnrichedMessage applyTransformAndEnrich(
const PipelineContext& rawCtx, softbus::core::models::DOMMessage& message) const;
private: private:
softbus::core::memory::LockFreeQueue<PipelineContext> m_ingressQ; softbus::core::memory::LockFreeQueue<PipelineContext> m_ingressQ;
@@ -110,6 +113,10 @@ private:
std::shared_ptr<softbus::message_bus::pipeline::stages::framers::PassthroughFramer> m_passthroughFramer; std::shared_ptr<softbus::message_bus::pipeline::stages::framers::PassthroughFramer> m_passthroughFramer;
std::mutex m_framerSessionMutex; std::mutex m_framerSessionMutex;
QHash<QString, std::shared_ptr<softbus::core::plugin_system::IProtocolFramerSession>> m_framerSessions; QHash<QString, std::shared_ptr<softbus::core::plugin_system::IProtocolFramerSession>> m_framerSessions;
std::mutex m_parserSessionMutex;
QHash<QString, std::shared_ptr<softbus::core::plugin_system::IProtocolSession>> m_parserSessions;
std::mutex m_mapperSessionMutex;
QHash<QString, std::shared_ptr<softbus::core::plugin_system::IProtocolMapperSession>> m_mapperSessions;
std::mutex m_seqMutex; std::mutex m_seqMutex;
QHash<QString, std::uint64_t> m_nextFrameSeqByEndpoint; QHash<QString, std::uint64_t> m_nextFrameSeqByEndpoint;

View File

@@ -1,8 +1,9 @@
#pragma once #pragma once
#include <cstdint>
#include <variant> #include <variant>
#include "plugins/protocols/modbusrtu/types/ModbusRtuPdu.h" #include "plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h"
namespace softbus::message_bus::pipeline::protocol namespace softbus::message_bus::pipeline::protocol
{ {
@@ -11,10 +12,16 @@ enum class ProtocolFamily : std::uint8_t
{ {
None = 0, None = 0,
ModbusRtu, ModbusRtu,
CanOpen,
}; };
/// 流水线内协议解析结果的统一外壳;后续可在此 variant 中增加 CAN 等协议 /// CANopen 协议占位类型(后续接入真实 CANopen 对象后替换/扩展)
using ProtocolEnvelopePdu = std::variant<std::monostate, ModbusRtuPdu>; struct CanOpenPduPlaceholder
{
};
/// 流水线内协议解析结果的统一外壳;通过 variant 承载多协议强类型结果。
using ProtocolEnvelopePdu = std::variant<std::monostate, ModbusRtuPdu, CanOpenPduPlaceholder>;
struct ProtocolEnvelope struct ProtocolEnvelope
{ {

View File

@@ -22,7 +22,6 @@ void PassthroughFramer::feed(softbus::message_bus::pipeline::PipelineContext&
} }
auto blk = m_pool->allocate(n); auto blk = m_pool->allocate(n);
if (!blk || !blk->data || blk->capacity < n) { if (!blk || !blk->data || blk->capacity < n) {
chunkIn.framerError = QStringLiteral("pool_exhausted");
return; return;
} }
const std::uint8_t* src = chunkIn.payload.bytes(); const std::uint8_t* src = chunkIn.payload.bytes();

View File

@@ -1,5 +1,8 @@
#pragma once #pragma once
// 定义解析器阶段接口 // 定义解析器阶段接口
#include <vector>
#include "core/models/DOMMessage.h"
#include "message_bus/pipeline/PipelineContext.h" #include "message_bus/pipeline/PipelineContext.h"
namespace softbus::message_bus::pipeline::stages::parsers namespace softbus::message_bus::pipeline::stages::parsers
@@ -9,7 +12,8 @@ class IParserStage
{ {
public: public:
virtual ~IParserStage() = default; virtual ~IParserStage() = default;
virtual bool parse(softbus::message_bus::pipeline::PipelineContext& ctx) = 0; virtual std::vector<softbus::core::models::DOMMessage> parse(
const softbus::message_bus::pipeline::PipelineContext& rawContext) = 0;
}; };
} // namespace softbus::message_bus::pipeline::stages::parsers } // namespace softbus::message_bus::pipeline::stages::parsers

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "message_bus/pipeline/PipelineContext.h" #include "core/models/DOMMessage.h"
namespace softbus::message_bus::pipeline::stages::filters namespace softbus::message_bus::pipeline::stages::filters
{ {
@@ -9,7 +9,7 @@ class IFilterStage
{ {
public: public:
virtual ~IFilterStage() = default; virtual ~IFilterStage() = default;
virtual bool apply(softbus::message_bus::pipeline::PipelineContext& ctx) = 0; virtual bool process(softbus::core::models::DOMMessage& message) = 0;
}; };
} // namespace softbus::message_bus::pipeline::stages::filters } // namespace softbus::message_bus::pipeline::stages::filters

View File

@@ -1,565 +1,67 @@
#include "message_bus/pipeline/stages/3_filters/ProtocolParseFilter.h" #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 <QDir>
#include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QJsonArray>
#include <QVector>
#include <variant>
#include "plugins/protocols/modbusrtu/types/ModbusRtuPdu.h"
#include "message_bus/pipeline/protocol/ProtocolEnvelope.h"
#include "utils/logs/logging.h"
namespace softbus::message_bus::pipeline::stages::filters 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; ProtocolParseFilter::ProtocolParseFilter() = default;
bool ProtocolParseFilter::apply(softbus::message_bus::pipeline::PipelineContext& ctx) bool ProtocolParseFilter::process(softbus::core::models::DOMMessage& message)
{ {
if (ctx.direction == softbus::message_bus::pipeline::BusDirection::Downstream) { if (!ensureLoaded()) {
return true; return true;
} }
if (!ensureLoaded()) { // Message-stage filter: only normalize quality by metadata range.
return true; // 保持兼容:配置缺失不阻断流水线 if (!message.metaRef) {
return true;
} }
if (normalizedProtocol(ctx.protocolHint).contains(QStringLiteral("modbus"))) { const auto* meta = m_metadataRegistry.findById(message.metaRef->metadataId);
return parseModbus(ctx); if (!meta) {
return true;
}
const double v = std::visit(
[](const auto& x) { return static_cast<double>(x); },
message.value);
if (meta->maxValue > meta->minValue && (v < meta->minValue || v > meta->maxValue)) {
message.quality = softbus::core::models::DataQuality::UNCERTAIN;
message.appendTrace(QStringLiteral("filter:range_check -> uncertain"));
} }
return true; return true;
} }
bool ProtocolParseFilter::ensureLoaded() softbus::core::models::EnrichedMessage
ProtocolParseFilter::enrich(const softbus::core::models::DOMMessage& message)
{
softbus::core::models::EnrichedMessage out;
out.sourceMessageId = message.messageId;
out.logicalName = message.metaRef ? message.metaRef->name : message.messageId;
out.unit = message.metaRef ? message.metaRef->unit : QString();
out.engineeringValue = message.value;
out.quality = message.quality;
out.attributes = message.attributes;
out.traceLog = message.traceLog;
return out;
}
bool ProtocolParseFilter::ensureLoaded() const
{ {
std::call_once(m_loadOnce, [this]() { std::call_once(m_loadOnce, [this]() {
QString configBase = qEnvironmentVariable("SOFTBUS_CONFIG_DIR"); QString configBase = qEnvironmentVariable("SOFTBUS_CONFIG_DIR");
if (!configBase.isEmpty() && !configBase.endsWith(QChar('/'))) { if (!configBase.isEmpty() && !configBase.endsWith(QChar('/'))) {
configBase.append(QChar('/')); configBase.append(QChar('/'));
} }
const QString homeCfg = configBase.isEmpty() const QString homeCfg = configBase.isEmpty()
? (QDir::homePath() + QStringLiteral("/softbus/config/")) ? (QDir::homePath() + QStringLiteral("/softbus/config/"))
: configBase; : configBase;
const QString metaHome = homeCfg + QStringLiteral("metadata_dictionary.json"); 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 metaLocal = QStringLiteral("config/metadata_dictionary.json");
const QString profLocal = QStringLiteral("config/device_profiles.json");
const QString metaPath = QFileInfo::exists(metaHome) ? metaHome : metaLocal; const QString metaPath = QFileInfo::exists(metaHome) ? metaHome : metaLocal;
const QString profPath = QFileInfo::exists(profHome) ? profHome : profLocal; m_loaded = m_metadataRegistry.loadFromFile(metaPath);
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; 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 } // namespace softbus::message_bus::pipeline::stages::filters

View File

@@ -2,8 +2,8 @@
#include <mutex> #include <mutex>
#include "core/models/EnrichedMessage.h"
#include "core/metadata/MetadataRegistry.h" #include "core/metadata/MetadataRegistry.h"
#include "core/metadata/ProfileRegistry.h"
#include "message_bus/pipeline/stages/3_filters/IFilterStage.h" #include "message_bus/pipeline/stages/3_filters/IFilterStage.h"
namespace softbus::message_bus::pipeline::stages::filters namespace softbus::message_bus::pipeline::stages::filters
@@ -14,17 +14,16 @@ class ProtocolParseFilter final : public IFilterStage
public: public:
ProtocolParseFilter(); ProtocolParseFilter();
bool apply(softbus::message_bus::pipeline::PipelineContext& ctx) override; bool process(softbus::core::models::DOMMessage& message) override;
softbus::core::models::EnrichedMessage enrich(const softbus::core::models::DOMMessage& message);
private: private:
bool ensureLoaded(); bool ensureLoaded() const;
bool parseModbus(softbus::message_bus::pipeline::PipelineContext& ctx);
private: private:
softbus::core::metadata::MetadataRegistry m_metadataRegistry; mutable softbus::core::metadata::MetadataRegistry m_metadataRegistry;
softbus::core::metadata::ProfileRegistry m_profileRegistry; mutable std::once_flag m_loadOnce;
std::once_flag m_loadOnce; mutable bool m_loaded{false};
bool m_loaded{false};
}; };
} // namespace softbus::message_bus::pipeline::stages::filters } // namespace softbus::message_bus::pipeline::stages::filters

View File

@@ -1,5 +1,5 @@
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFramer.h" #include "message_bus/pipeline/stages/1_framers/ModbusRtuFramer.h"
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFraming.h" #include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
#include <QtTest/QtTest> #include <QtTest/QtTest>

View File

@@ -1,4 +1,4 @@
#include "plugins/protocols/modbusrtu/framer/ModbusRtuFraming.h" #include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
#include <QtTest/QtTest> #include <QtTest/QtTest>

View File

@@ -1,4 +1,4 @@
#include "plugins/protocols/modbusrtu/codec/ModbusRtuCodec.h" #include "plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h"
#include <QtTest/QtTest> #include <QtTest/QtTest>

View File

@@ -6,14 +6,7 @@
#include <QFile> #include <QFile>
#include <variant> #include <variant>
#include "plugins/protocols/modbusrtu/types/ModbusRtuPdu.h" #include "core/models/DOMMessage.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; using softbus::message_bus::pipeline::stages::filters::ProtocolParseFilter;
class ProtocolParseFilterTest : public QObject class ProtocolParseFilterTest : public QObject
@@ -61,29 +54,14 @@ void ProtocolParseFilterTest::initTestCase()
void ProtocolParseFilterTest::parses_multiple_dom_messages() void ProtocolParseFilterTest::parses_multiple_dom_messages()
{ {
ProtocolParseFilter filter; ProtocolParseFilter filter;
PipelineContext ctx; softbus::core::models::DOMMessage msg;
ctx.direction = BusDirection::Upstream; msg.messageId = QStringLiteral("devA.temperature");
ctx.protocolHint = QStringLiteral("modbus_rtu"); msg.value = 25.5;
ctx.stableKey = QStringLiteral("devA"); msg.quality = softbus::core::models::DataQuality::GOOD;
ctx.deviceId = 1; QVERIFY(filter.process(msg));
ctx.timestamp = QDateTime::currentDateTimeUtc(); auto enriched = filter.enrich(msg);
QCOMPARE(enriched.sourceMessageId, msg.messageId);
ReadHoldingRegistersResponse resp; QCOMPARE(std::get<double>(enriched.engineeringValue), 25.5);
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) QTEST_MAIN(ProtocolParseFilterTest)