dommessage

This commit is contained in:
flower_linux
2026-04-28 21:56:56 +08:00
parent 7459cc0f36
commit 7280fca08d
24 changed files with 848 additions and 115 deletions

View File

@@ -162,6 +162,8 @@ qt_add_executable(softbus_daemon
src/message_bus/pipeline/PipelineEngine.cpp
src/message_bus/pipeline/PipelineEngineApi.h
src/message_bus/pipeline/PipelineEngineApi.cpp
src/message_bus/pipeline/derivation/DerivationEngine.h
src/message_bus/pipeline/derivation/DerivationEngine.cpp
src/message_bus/pipeline/protocol/ProtocolEnvelope.h
plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h
plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h

Binary file not shown.

View File

@@ -166,16 +166,17 @@ SerialQtDriver(read callback)
### 8.2 Mapper 行为Modbus RTU
`ModbusRtuProtocolMapperPlugin` 当前行为:
`ModbusRtuProtocolMapperPlugin` + `PipelineEngine``IMappingLookup` 组合行为:
1.`ReadHoldingRegistersRequest` 记录 `unitId -> startAddress`,用于后续响应地址回填。
2.`ReadHoldingRegistersResponse` 遍历寄存器并构造 `compositeKey`
- `compositeKey = endpointHash + protocol + slaveId + registerAddress`(位拼接)。
3. `MappingRegistry` 命中:
- 生成 `DOMMessage``messageId = domPath`,并应用 `scale/offset/unit`
4. 若未命中:
- 仅在 `DiscoveryPool.sniffingEnabled=true` 时写入发现池样本;
- 主链路继续 `continue`,不生成业务消息。
3. **解析优先级(统一语义)**`MappingRegistry`(运行时绑定)优先;未命中则查 `ProfileRegistry``device_profiles.json`,按 `runtimeDeviceId``modbus_rtu` 协议匹配,支持 `*` 通配设备)。
4. 若上述之一命中:
- 生成轻量 `DOMMessage``messageId`/`pointId` 来自绑定或 profile`metadataId` 来自 profile若有`value` 为工程值;静态元数据不随每条样本重复序列化,由 `metadataId` 在控制面/Registry 解析。
5. 若仍未命中:
- 若存在 `discoverySink`,始终上报 `DiscoveryRecord`(发现池内部仍受嗅探开关与限频约束);
- **仅当嗅探开启**时,主链路额外产出 `modbus.unmapped:*``DOMMessage` 供规则调试;嗅探关闭时不产出未映射 DOM减少总线冗载。
### 8.3 DiscoveryPool 约束

View File

@@ -50,7 +50,9 @@ public:
const auto addr = static_cast<std::uint16_t>(baseAddress + static_cast<std::uint16_t>(i));
const auto compKey = buildCompositeKey(
rawContext.endpointHash, rawContext.protocol, resp->unitId, addr);
const auto mapRule = m_context.mappingLookup ? m_context.mappingLookup->find(compKey) : std::nullopt;
const auto mapRule = m_context.mappingLookup
? m_context.mappingLookup->find(rawContext, compKey)
: std::nullopt;
const double rawValue = static_cast<double>(resp->registers[static_cast<qsizetype>(i)]);
if (!mapRule.has_value()) {
if (m_context.discoverySink) {
@@ -64,8 +66,10 @@ public:
rec.lastSeenMs = rawContext.timestampMs;
m_context.discoverySink->reportUnmapped(rec);
}
// 无 metadata 映射时仍产出 DOM便于 DynamicRuleRegistry 的 transform 等下游逻辑生效
// compositeKey 仍按与映射表相同的算法计算,便于后续在 UI 里绑定映射)
const bool sniff = m_context.sniffingEnabled ? m_context.sniffingEnabled() : false;
if (!sniff) {
continue;
}
softbus::core::models::DOMMessage pass;
pass.messageId = QStringLiteral("modbus.unmapped:%1").arg(compKey);
pass.timestamp = rawContext.timestampMs;
@@ -81,6 +85,8 @@ public:
}
softbus::core::models::DOMMessage msg;
msg.messageId = mapRule->domPath;
msg.pointId = mapRule->pointId.isEmpty() ? mapRule->domPath : mapRule->pointId;
msg.metadataId = mapRule->metadataId;
msg.timestamp = rawContext.timestampMs;
msg.value = (rawValue * mapRule->scale) + mapRule->offset;
msg.quality = softbus::core::models::DataQuality::GOOD;
@@ -88,6 +94,7 @@ public:
msg.attributes.insert(QStringLiteral("compositeKey"), QString::number(compKey));
msg.attributes.insert(QStringLiteral("slaveId"), static_cast<int>(resp->unitId));
msg.attributes.insert(QStringLiteral("registerAddress"), static_cast<int>(addr));
msg.attributes.insert(QStringLiteral("mappingSource"), mapRule->sourceTag);
if (!mapRule->unit.isEmpty()) {
msg.attributes.insert(QStringLiteral("unit"), mapRule->unit);
}
@@ -142,4 +149,3 @@ softbus::core::plugin_system::IProtocolMapperPlugin* createModbusRtuProtocolMapp
}
} // namespace softbus::message_bus::pipeline::protocol::modbusrtu

View File

@@ -58,9 +58,9 @@
| `remove_filter` | `id` | `0` | `4041` | 删除过滤规则 |
| `add_transform_rule` | `id`, `endpointHash?`, `deviceId?`, `stableKey?`, `scale?`, `offset?`, `unit?` | `0` | `4002` | 添加转换规则 |
| `remove_transform_rule` | `id` | `0` | `4042` | 删除转换规则 |
| `add_mapping_rule` | `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?` | `0` | `4004` | 新增/更新映射规则 |
| `remove_mapping_rule` | `compositeKey` | `0` | `4043` | 删除映射规则 |
| `list_mapping_rules` | 无 | `0` | - | 返回映射规则列表 |
| `add_mapping_rule` | `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?`, `metadataId?`, `pointId?`, `catalogVersion?`, `updatedBy?` | `0` | `4004` / `5002` | 新增/更新映射规则;成功后持久化到 `runtime_mappings.json``5002` 表示持久化失败 |
| `remove_mapping_rule` | `compositeKey` | `0` | `4043` / `5002` | 删除映射规则并持久化 |
| `list_mapping_rules` | 无 | `0` | - | 返回 `items``mappingCatalogVersion` |
| `discovery_start` | `ttlMs?` | `0` | - | 开启嗅探模式 |
| `discovery_stop` | 无 | `0` | - | 关闭嗅探模式 |
| `discovery_clear` | 无 | `0` | - | 清空发现池 |
@@ -123,10 +123,10 @@
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/metadata` | 无 | 返回 metadata 字典全量快照 |
| `GET` | `/api/v1/metadata/{metadataId}` | path: `metadataId` | 返回指定 metadata 详情 |
| `GET` | `/api/v1/profiles` | 无 | 返回全部设备 profile 快照 |
| `GET` | `/api/v1/profiles/{deviceId}` | path: `deviceId` | 返回指定 `deviceId` 关联的全部 profile |
| `GET` | `/api/v1/metadata` | 无 | 返回 `data`(字典数组)与可选 `catalogVersion`(根级字段) |
| `GET` | `/api/v1/metadata/{metadataId}` | path: `metadataId` | 返回指定 metadata 详情(含可选 `catalogVersion` |
| `GET` | `/api/v1/profiles` | 无 | 返回 `data` 与可选 `catalogVersion` |
| `GET` | `/api/v1/profiles/{deviceId}` | path: `deviceId` | 返回指定 `deviceId` 关联的全部 profile(含可选 `catalogVersion` |
### 5.7 Discovery 路由
@@ -142,9 +142,9 @@
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `POST` | `/api/v1/mapping/bind` | body: `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?` | 绑定或更新映射规则 |
| `POST` | `/api/v1/mapping/unbind` | body: `compositeKey` | 删除映射规则 |
| `GET` | `/api/v1/mapping/list` | 无 | 列出当前映射规则 |
| `POST` | `/api/v1/mapping/bind` | body: `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?`, `metadataId?`, `pointId?`, `catalogVersion?`, `updatedBy?` | 绑定或更新映射规则并写入 `runtime_mappings.json` |
| `POST` | `/api/v1/mapping/unbind` | body: `compositeKey` | 删除映射规则并持久化 |
| `GET` | `/api/v1/mapping/list` | 无 | 返回 `items``mappingCatalogVersion` |
## 6) 通用错误码约定(当前实现)
@@ -160,6 +160,7 @@
| `4041` | `filter_not_found` | 删除过滤规则时未找到 |
| `4042` | `transform_rule_not_found` | 删除转换规则时未找到 |
| `4043` | `mapping_rule_not_found` | 删除映射规则时未找到 |
| `5002` | `persist_mappings_failed` | 映射规则内存更新成功但写入 `runtime_mappings.json` 失败 |
## 7) 维护建议

View File

@@ -3,7 +3,9 @@
#include <algorithm>
#include <array>
#include <mutex>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QHttpServer>
#include <QHttpHeaders>
@@ -15,6 +17,7 @@
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QSaveFile>
#include <QUrlQuery>
#include <utility>
@@ -176,6 +179,17 @@ QHttpServerResponse okDataResponse(const QJsonValue& data,
return jsonResponse(QJsonObject{{QStringLiteral("ok"), true}, {QStringLiteral("data"), data}}, code);
}
QHttpServerResponse okDataResponseWithCatalog(const QJsonValue& data,
const QString& catalogVersion,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
{
QJsonObject o{{QStringLiteral("ok"), true}, {QStringLiteral("data"), data}};
if (!catalogVersion.isEmpty()) {
o.insert(QStringLiteral("catalogVersion"), catalogVersion);
}
return jsonResponse(o, code);
}
QHttpServerResponse errorResponse(const QString& error,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::BadRequest)
{
@@ -212,6 +226,10 @@ QJsonArray devicesToJson(const std::vector<softbus::devices::DeviceInfo>& device
QString resolveConfigBaseDir()
{
const QString daemonConfigPath = softbus::device_bus::DeviceBus::instance().configPath();
if (!daemonConfigPath.isEmpty()) {
return QFileInfo(daemonConfigPath).dir().absolutePath() + QChar('/');
}
QString configBase = qEnvironmentVariable("SOFTBUS_CONFIG_DIR");
if (!configBase.isEmpty()) {
return configBase.endsWith(QChar('/')) ? configBase : configBase + QChar('/');
@@ -219,10 +237,71 @@ QString resolveConfigBaseDir()
return QDir::homePath() + QStringLiteral("/softbus/config/");
}
QString resolveConfigFile(const QString& fileName, const QString& fallbackRelative)
QString resolveConfigFile(const QString& fileName)
{
const QString homePath = resolveConfigBaseDir() + fileName;
return QFileInfo::exists(homePath) ? homePath : fallbackRelative;
return resolveConfigBaseDir() + fileName;
}
bool ensureConfigFileInitialized(const QString& targetPath,
const QString& fallbackPath,
const QByteArray& defaultContent)
{
if (QFile::exists(targetPath)) {
return true;
}
const QFileInfo fi(targetPath);
if (!QDir().mkpath(fi.dir().absolutePath())) {
return false;
}
QByteArray initBytes = defaultContent;
QFile fallback(fallbackPath);
if (!fallbackPath.isEmpty() && fallback.exists() && fallback.open(QIODevice::ReadOnly)) {
initBytes = fallback.readAll();
}
QSaveFile initFile(targetPath);
if (!initFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
if (initFile.write(initBytes) != initBytes.size()) {
initFile.cancelWriting();
return false;
}
return initFile.commit();
}
void ensureRestApiConfigFilesInitialized()
{
static std::once_flag once;
std::call_once(once, []() {
const QByteArray defaultRules = QJsonDocument(QJsonObject{
{QStringLiteral("filter"), QJsonArray{}},
{QStringLiteral("transform"), QJsonArray{}},
}).toJson(QJsonDocument::Indented);
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("runtime_rules.json")),
QStringLiteral(),
defaultRules);
const QByteArray defaultMappings = QStringLiteral(
"{\n \"catalogVersion\": 1,\n \"items\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("runtime_mappings.json")),
QStringLiteral(),
defaultMappings);
const QByteArray defaultProfiles = QStringLiteral(
"{\n \"catalogVersion\": \"\",\n \"profiles\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("device_profiles.json")),
QStringLiteral("config/device_profiles.json"),
defaultProfiles);
const QByteArray defaultMetadata = QStringLiteral(
"{\n \"catalogVersion\": \"\",\n \"metadatas\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("metadata_dictionary.json")),
QStringLiteral("config/metadata_dictionary.json"),
defaultMetadata);
});
}
bool ensureMetadataLoaded(softbus::core::metadata::MetadataRegistry& registry, QString* error)
@@ -232,10 +311,9 @@ bool ensureMetadataLoaded(softbus::core::metadata::MetadataRegistry& registry, Q
static bool loaded = false;
std::lock_guard<std::mutex> lk(mtx);
if (!attempted) {
ensureRestApiConfigFilesInitialized();
attempted = true;
loaded = registry.loadFromFile(
resolveConfigFile(QStringLiteral("metadata_dictionary.json"),
QStringLiteral("config/metadata_dictionary.json")));
loaded = registry.loadFromFile(resolveConfigFile(QStringLiteral("metadata_dictionary.json")));
}
if (!loaded && error) {
*error = registry.lastError();
@@ -249,17 +327,17 @@ softbus::core::metadata::MetadataRegistry& metadataRegistry()
return registry;
}
bool ensureProfileLoaded(softbus::core::metadata::ProfileRegistry& registry, QString* error)
bool ensureProfileLoaded(QString* error)
{
static std::mutex mtx;
static bool attempted = false;
static bool loaded = false;
std::lock_guard<std::mutex> lk(mtx);
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
if (!attempted) {
ensureRestApiConfigFilesInitialized();
attempted = true;
loaded = registry.loadFromFile(
resolveConfigFile(QStringLiteral("device_profiles.json"),
QStringLiteral("config/device_profiles.json")));
loaded = registry.loadFromFile(resolveConfigFile(QStringLiteral("device_profiles.json")));
}
if (!loaded && error) {
*error = registry.lastError();
@@ -267,12 +345,6 @@ bool ensureProfileLoaded(softbus::core::metadata::ProfileRegistry& registry, QSt
return loaded;
}
softbus::core::metadata::ProfileRegistry& profileRegistry()
{
static softbus::core::metadata::ProfileRegistry registry;
return registry;
}
bool isProtectedNodeKey(const QString& key)
{
for (const auto* value : kProtectedNodeKeys) {
@@ -400,6 +472,11 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
rule.scale = payload.value(QStringLiteral("scale")).toDouble(1.0);
rule.offset = payload.value(QStringLiteral("offset")).toDouble(0.0);
rule.unit = payload.value(QStringLiteral("unit")).toString();
rule.metadataId = payload.value(QStringLiteral("metadataId")).toString();
rule.pointId = payload.value(QStringLiteral("pointId")).toString();
rule.catalogVersion = payload.value(QStringLiteral("catalogVersion")).toString();
rule.updatedBy = payload.value(QStringLiteral("updatedBy")).toString(QStringLiteral("http"));
rule.updatedAtMs = QDateTime::currentMSecsSinceEpoch();
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
if (!mappingReg.upsert(rule)) {
@@ -407,10 +484,15 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
QJsonObject{{QStringLiteral("error"), QStringLiteral("invalid_mapping_rule")}},
QHttpServerResponse::StatusCode::BadRequest));
}
return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true},
{QStringLiteral("compositeKey"),
QString::number(rule.compositeKey)}},
QHttpServerResponse::StatusCode::Ok));
ensureRestApiConfigFilesInitialized();
const QString mapPath = resolveConfigFile(QStringLiteral("runtime_mappings.json"));
(void)mappingReg.savePersistedFile(mapPath);
return withCorsHeaders(QHttpServerResponse(
QJsonObject{{QStringLiteral("ok"), true},
{QStringLiteral("compositeKey"), QString::number(rule.compositeKey)},
{QStringLiteral("mappingCatalogVersion"),
QString::number(mappingReg.mappingCatalogVersion())}},
QHttpServerResponse::StatusCode::Ok));
});
server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Post, routeContext,
@@ -420,6 +502,11 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
payload.value(QStringLiteral("compositeKey")).toString().toULongLong();
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
const bool ok = mappingReg.remove(compositeKey);
if (ok) {
ensureRestApiConfigFilesInitialized();
const QString mapPath = resolveConfigFile(QStringLiteral("runtime_mappings.json"));
(void)mappingReg.savePersistedFile(mapPath);
}
return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), ok},
{QStringLiteral("compositeKey"),
QString::number(compositeKey)}},
@@ -429,9 +516,10 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
return withCorsHeaders(
QHttpServerResponse(QJsonObject{{QStringLiteral("items"), mappingReg.list()}},
QHttpServerResponse::StatusCode::Ok));
QJsonObject o;
o.insert(QStringLiteral("items"), mappingReg.list());
o.insert(QStringLiteral("mappingCatalogVersion"), mappingReg.mappingCatalogVersion());
return withCorsHeaders(QHttpServerResponse(o, QHttpServerResponse::StatusCode::Ok));
});
}
@@ -631,7 +719,7 @@ void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
return errorResponse(error.isEmpty() ? QStringLiteral("metadata_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.list());
return okDataResponseWithCatalog(registry.list(), registry.catalogVersion());
});
server->route(QStringLiteral("/api/v1/metadata/<arg>"), QHttpServerRequest::Method::Get, routeContext,
@@ -646,7 +734,7 @@ void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
for (const auto& item : items) {
const auto obj = item.toObject();
if (obj.value(QStringLiteral("metadataId")).toString() == metadataId) {
return okDataResponse(obj);
return okDataResponseWithCatalog(obj, registry.catalogVersion());
}
}
return errorResponse(QStringLiteral("metadata_not_found"),
@@ -657,24 +745,24 @@ void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
void registerProfileRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/profiles"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& registry = profileRegistry();
QString error;
if (!ensureProfileLoaded(registry, &error)) {
if (!ensureProfileLoaded(&error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.list());
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
return okDataResponseWithCatalog(registry.list(), registry.catalogVersion());
});
server->route(QStringLiteral("/api/v1/profiles/<arg>"), QHttpServerRequest::Method::Get, routeContext,
[](const QString& deviceId) {
auto& registry = profileRegistry();
QString error;
if (!ensureProfileLoaded(registry, &error)) {
if (!ensureProfileLoaded(&error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.listByDeviceId(deviceId));
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
return okDataResponseWithCatalog(registry.listByDeviceId(deviceId), registry.catalogVersion());
});
}

View File

@@ -1,5 +1,6 @@
#include "api/ipc_dbus/CommandDispatcher.h"
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
@@ -29,6 +30,35 @@ QString rulesConfigPath()
return QDir::homePath() + QStringLiteral("/softbus/config/runtime_rules.json");
}
QString runtimeMappingsConfigPath()
{
const QFileInfo fi(rulesConfigPath());
return fi.dir().filePath(QStringLiteral("runtime_mappings.json"));
}
bool persistMappingsToDedicatedConfig(const softbus::message_bus::pipeline::MappingRegistry& reg)
{
const QString path = runtimeMappingsConfigPath();
const QFileInfo fi(path);
if (!QDir().mkpath(fi.dir().absolutePath())) {
LOG_ERROR() << "CommandDispatcher: failed to create mappings directory:" << fi.dir().absolutePath();
return false;
}
return reg.savePersistedFile(path);
}
void appendMappingAuditLine(const QString& line)
{
const QFileInfo fi(runtimeMappingsConfigPath());
const QString auditPath = fi.dir().filePath(QStringLiteral("mapping_audit.log"));
QFile f(auditPath);
if (!f.open(QIODevice::Append | QIODevice::Text)) {
return;
}
f.write(line.toUtf8());
f.write("\n");
}
softbus::message_bus::pipeline::RuleScope parseScope(const QJsonObject& params)
{
softbus::message_bus::pipeline::RuleScope scope;
@@ -188,14 +218,38 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
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();
rule.metadataId = params.value(QStringLiteral("metadataId")).toString();
rule.pointId = params.value(QStringLiteral("pointId")).toString();
rule.catalogVersion = params.value(QStringLiteral("catalogVersion")).toString();
rule.updatedBy = params.value(QStringLiteral("updatedBy")).toString();
rule.updatedAtMs = QDateTime::currentMSecsSinceEpoch();
if (!mappingReg.upsert(rule)) {
return {4004, QStringLiteral("invalid_mapping_rule"), {}};
}
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("compositeKey"), keyText}}};
if (!persistMappingsToDedicatedConfig(mappingReg)) {
return {5002, QStringLiteral("persist_mappings_failed"), {}};
}
appendMappingAuditLine(QStringLiteral("upsert compositeKey=%1 domPath=%2 at %3")
.arg(keyText)
.arg(rule.domPath)
.arg(rule.updatedAtMs));
return {0,
QStringLiteral("ok"),
QJsonObject{{QStringLiteral("compositeKey"), keyText},
{QStringLiteral("mappingCatalogVersion"),
QString::number(mappingReg.mappingCatalogVersion())}}};
}
if (action == QStringLiteral("remove_mapping_rule")) {
const QString keyText = params.value(QStringLiteral("compositeKey")).toString();
const bool ok = mappingReg.remove(keyText.toULongLong());
if (ok) {
if (!persistMappingsToDedicatedConfig(mappingReg)) {
return {5002, QStringLiteral("persist_mappings_failed"), {}};
}
appendMappingAuditLine(QStringLiteral("remove compositeKey=%1 at %2")
.arg(keyText)
.arg(QDateTime::currentMSecsSinceEpoch()));
}
return {ok ? 0 : 4043, ok ? QStringLiteral("ok") : QStringLiteral("mapping_rule_not_found"),
QJsonObject{{QStringLiteral("compositeKey"), keyText}}};
}

View File

@@ -26,11 +26,8 @@ DataDomain parseDomain(const QString& s)
if (x == QStringLiteral("MONITOR_DATA")) {
return DataDomain::MONITOR_DATA;
}
if (x == QStringLiteral("HEALTH_DIAG")) {
return DataDomain::HEALTH_DIAG;
}
if (x == QStringLiteral("CONFIG_PARAM")) {
return DataDomain::CONFIG_PARAM;
if (x == QStringLiteral("MANAGE_DATA")) {
return DataDomain::MANAGE_DATA;
}
return DataDomain::UNKNOWN;
}
@@ -74,10 +71,8 @@ QString domainToString(DataDomain domain)
return QStringLiteral("CONTROL_CMD");
case DataDomain::MONITOR_DATA:
return QStringLiteral("MONITOR_DATA");
case DataDomain::HEALTH_DIAG:
return QStringLiteral("HEALTH_DIAG");
case DataDomain::CONFIG_PARAM:
return QStringLiteral("CONFIG_PARAM");
case DataDomain::MANAGE_DATA:
return QStringLiteral("MANAGE_DATA");
case DataDomain::UNKNOWN:
default:
return QStringLiteral("UNKNOWN");
@@ -110,7 +105,7 @@ QString dataTypeToString(DataType type)
QJsonObject toJsonObject(const softbus::core::models::MetadataDef& def)
{
return QJsonObject{
QJsonObject o{
{QStringLiteral("metadataId"), def.metadataId},
{QStringLiteral("name"), def.name},
{QStringLiteral("description"), def.description},
@@ -122,6 +117,13 @@ QJsonObject toJsonObject(const softbus::core::models::MetadataDef& def)
{QStringLiteral("deadband"), def.deadband},
{QStringLiteral("isWriteable"), def.isWriteable},
};
if (!def.recordStatus.isEmpty()) {
o.insert(QStringLiteral("recordStatus"), def.recordStatus);
}
if (!def.entryCatalogVersion.isEmpty()) {
o.insert(QStringLiteral("entryCatalogVersion"), def.entryCatalogVersion);
}
return o;
}
} // namespace
@@ -130,6 +132,7 @@ bool MetadataRegistry::loadFromFile(const QString& filePath)
{
m_lastError.clear();
m_defsById.clear();
m_catalogVersion.clear();
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
@@ -145,15 +148,19 @@ bool MetadataRegistry::loadFromFile(const QString& filePath)
}
const auto root = doc.object();
m_catalogVersion = root.value(QStringLiteral("catalogVersion")).toString();
const auto arr = root.value(QStringLiteral("metadatas")).toArray();
for (const auto& item : arr) {
if (!item.isObject()) {
continue;
}
const auto one = parseOne(item.toObject());
auto one = parseOne(item.toObject());
if (!one.has_value() || one->metadataId.isEmpty()) {
continue;
}
if (one->entryCatalogVersion.isEmpty() && !m_catalogVersion.isEmpty()) {
one->entryCatalogVersion = m_catalogVersion;
}
m_defsById.insert(one->metadataId, *one);
}
@@ -195,6 +202,8 @@ std::optional<softbus::core::models::MetadataDef> MetadataRegistry::parseOne(con
def.maxValue = o.value(QStringLiteral("maxValue")).toDouble(0.0);
def.deadband = o.value(QStringLiteral("deadband")).toDouble(0.0);
def.isWriteable = o.value(QStringLiteral("isWriteable")).toBool(false);
def.recordStatus = o.value(QStringLiteral("recordStatus")).toString(QStringLiteral("ACTIVE"));
def.entryCatalogVersion = o.value(QStringLiteral("entryCatalogVersion")).toString();
return def;
}

View File

@@ -21,6 +21,7 @@ public:
QJsonArray list() const;
bool empty() const { return m_defsById.isEmpty(); }
const QString& lastError() const { return m_lastError; }
const QString& catalogVersion() const { return m_catalogVersion; }
private:
static std::optional<softbus::core::models::MetadataDef> parseOne(const QJsonObject& o);
@@ -28,6 +29,7 @@ private:
private:
QHash<QString, softbus::core::models::MetadataDef> m_defsById;
QString m_lastError;
QString m_catalogVersion;
};
} // namespace softbus::core::metadata

View File

@@ -8,6 +8,12 @@
namespace softbus::core::metadata
{
ProfileRegistry& ProfileRegistry::instance()
{
static ProfileRegistry reg;
return reg;
}
namespace
{
@@ -91,6 +97,7 @@ bool ProfileRegistry::loadFromFile(const QString& filePath)
{
m_lastError.clear();
m_profilesByKey.clear();
m_catalogVersion.clear();
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
@@ -106,6 +113,7 @@ bool ProfileRegistry::loadFromFile(const QString& filePath)
}
const auto root = doc.object();
m_catalogVersion = root.value(QStringLiteral("catalogVersion")).toString();
const auto arr = root.value(QStringLiteral("profiles")).toArray();
for (const auto& item : arr) {
if (!item.isObject()) {
@@ -246,4 +254,57 @@ softbus::core::models::Endianness ProfileRegistry::parseEndianness(const QString
return Endianness::BigEndian;
}
bool ProfileRegistry::mappingMatchesModbusRegister(const softbus::core::models::DataPointMapping& m,
const std::uint16_t registerAddress)
{
constexpr std::uint32_t kUnset = 0xFFFFFFFFu;
if (m.registerAddress != kUnset) {
return m.registerAddress == static_cast<std::uint32_t>(registerAddress);
}
if (m.byteLength == 2) {
return static_cast<std::uint32_t>(m.byteOffset) == static_cast<std::uint32_t>(registerAddress) * 2U;
}
return false;
}
std::optional<ProfileModbusRegisterHit> ProfileRegistry::findModbusRegisterMapping(
const int runtimeDeviceId, const QString& protocolNorm, const std::uint16_t registerAddress) const
{
if (protocolNorm.isEmpty()) {
return std::nullopt;
}
const QString pid = runtimeDeviceId >= 0 ? QString::number(runtimeDeviceId) : QString();
const auto tryProfile = [&](const softbus::core::models::DeviceDataProfile* p) -> std::optional<ProfileModbusRegisterHit> {
if (!p) {
return std::nullopt;
}
for (const auto& m : p->mappings) {
if (!mappingMatchesModbusRegister(m, registerAddress)) {
continue;
}
ProfileModbusRegisterHit hit;
hit.domPath = m.pointId.isEmpty() ? m.metadataId : m.pointId;
hit.scale = m.scaleFactor;
hit.offset = m.offsetFactor;
hit.metadataId = m.metadataId;
hit.pointId = m.pointId;
return hit;
}
return std::nullopt;
};
if (!pid.isEmpty()) {
if (const auto* exact = find(pid, protocolNorm)) {
if (auto h = tryProfile(exact)) {
return h;
}
}
}
if (const auto* wild = find(QStringLiteral("*"), protocolNorm)) {
return tryProfile(wild);
}
return std::nullopt;
}
} // namespace softbus::core::metadata

View File

@@ -12,9 +12,21 @@
namespace softbus::core::metadata
{
struct ProfileModbusRegisterHit
{
QString domPath;
double scale{1.0};
double offset{0.0};
QString unit;
QString metadataId;
QString pointId;
};
class ProfileRegistry
{
public:
static ProfileRegistry& instance();
bool loadFromFile(const QString& filePath);
const softbus::core::models::DeviceDataProfile* find(const QString& deviceId,
const QString& protocol) const;
@@ -23,6 +35,12 @@ public:
const QString& lastError() const { return m_lastError; }
bool empty() const { return m_profilesByKey.isEmpty(); }
const QString& catalogVersion() const { return m_catalogVersion; }
/// Modbus 保持寄存器地址与 profile 中 DataPointMapping 对齐registerAddress 或 byteOffset+byteLength
std::optional<ProfileModbusRegisterHit> findModbusRegisterMapping(int runtimeDeviceId,
const QString& protocolNorm,
std::uint16_t registerAddress) const;
private:
static QString makeKey(const QString& deviceId, const QString& protocol);
@@ -30,9 +48,16 @@ private:
static softbus::core::models::DataType parseDataType(const QString& s);
static softbus::core::models::Endianness parseEndianness(const QString& s);
private:
ProfileRegistry() = default;
static bool mappingMatchesModbusRegister(const softbus::core::models::DataPointMapping& m,
std::uint16_t registerAddress);
private:
QHash<QString, softbus::core::models::DeviceDataProfile> m_profilesByKey;
QString m_lastError;
QString m_catalogVersion;
};
} // namespace softbus::core::metadata

View File

@@ -21,15 +21,39 @@ class DOMMessage
public:
DOMMessage() = default;
QString messageId; // 消息ID
const MetadataDef* metaRef{nullptr}; // 元数据引用
QString messageId; // 消息ID(建议与 pointId 对齐)
QString pointId; // 标准点位 ID热路径标识可与 messageId 相同)
QString metadataId; // 元数据目录 ID热路径静态定义不入总线载荷重复序列化
const MetadataDef* metaRef{nullptr}; // 进程内解析缓存指针(可选,非跨边界序列化字段)
std::int64_t timestamp{0}; // 时间戳
DOMValue value{0.0}; // 值
DataQuality quality{DataQuality::BAD}; // 质量
QHash<QString, QJsonValue> attributes; // 属性
std::vector<QString> traceLog; // 跟踪日志
DataDomain domain() const { return metaRef ? metaRef->domain : DataDomain::UNKNOWN; }
DataDomain domain() const
{
if (metaRef) {
return metaRef->domain;
}
const auto it = attributes.constFind(QStringLiteral("domain"));
if (it != attributes.constEnd() && it->isString()) {
const QString x = it->toString().trimmed().toUpper();
if (x == QLatin1String("PROCESS_PARAM")) {
return DataDomain::PROCESS_PARAM;
}
if (x == QLatin1String("CONTROL_CMD")) {
return DataDomain::CONTROL_CMD;
}
if (x == QLatin1String("MONITOR_DATA")) {
return DataDomain::MONITOR_DATA;
}
if (x == QLatin1String("MANAGE_DATA")) {
return DataDomain::MANAGE_DATA;
}
}
return DataDomain::UNKNOWN;
}
template <typename T>
T getValueAs() const

View File

@@ -14,6 +14,9 @@ struct EnrichedMessage
QString sourceMessageId;
QString logicalName;
QString unit;
QString metadataId;
QString pointId;
QString catalogVersion;
DOMValue engineeringValue{0.0};
DataQuality quality{DataQuality::BAD};
bool alarm{false};

View File

@@ -19,6 +19,8 @@ struct MetadataDef
double maxValue{0.0}; // 最大值
double deadband{0.0}; // 死区
bool isWriteable{false}; // 是否可写
QString recordStatus{QStringLiteral("ACTIVE")}; // 治理ACTIVE / DEPRECATED 作用:控制数据是否有效
QString entryCatalogVersion; // 写入该条时的目录版本(可选)
};
} // namespace softbus::core::models

View File

@@ -2,6 +2,8 @@
// 定义数据域类型、数据质量类型、数据类型
#include <cstdint>
#include <QString>
namespace softbus::core::models
{
@@ -9,9 +11,8 @@ enum class DataDomain : std::uint8_t
{
PROCESS_PARAM = 0, // 过程参数
CONTROL_CMD = 1, // 控制指令
MONITOR_DATA = 2,
HEALTH_DIAG = 3,
CONFIG_PARAM = 4,
MONITOR_DATA = 2, // 监控数据
MANAGE_DATA = 3, // 管理数据
UNKNOWN = 255
};
@@ -35,4 +36,21 @@ enum class DataType : std::uint8_t
BYTE_ARRAY
};
inline QString dataDomainToString(DataDomain domain)
{
switch (domain) {
case DataDomain::PROCESS_PARAM:
return QStringLiteral("PROCESS_PARAM");
case DataDomain::CONTROL_CMD:
return QStringLiteral("CONTROL_CMD");
case DataDomain::MONITOR_DATA:
return QStringLiteral("MONITOR_DATA");
case DataDomain::MANAGE_DATA:
return QStringLiteral("MANAGE_DATA");
case DataDomain::UNKNOWN:
default:
return QStringLiteral("UNKNOWN");
}
}
} // namespace softbus::core::models

View File

@@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <functional>
#include <optional>
#include <QJsonObject>
@@ -17,6 +18,9 @@ struct MappingLookupResult
double scale{1.0};
double offset{0.0};
QString unit;
QString metadataId;
QString pointId;
QString sourceTag; // "mapping" | "profile"
};
struct DiscoveryRecord
@@ -34,7 +38,8 @@ class IMappingLookup
{
public:
virtual ~IMappingLookup() = default;
virtual std::optional<MappingLookupResult> find(std::uint64_t compositeKey) const = 0;
virtual std::optional<MappingLookupResult> find(const softbus::core::models::RawBusMessage& raw,
std::uint64_t compositeKey) const = 0;
};
class IDiscoverySink
@@ -48,6 +53,7 @@ struct MapperSessionContext
{
IMappingLookup* mappingLookup{nullptr};
IDiscoverySink* discoverySink{nullptr};
std::function<bool()> sniffingEnabled;
QJsonObject params;
};

View File

@@ -16,6 +16,8 @@
#include "message_bus/egress/EgressRouter.h"
#include "message_bus/ingress/DriverIngressAdapter.h"
#include "message_bus/pipeline/PipelineEngine.h"
#include "core/metadata/ProfileRegistry.h"
#include "message_bus/pipeline/mapping/MappingRegistry.h"
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
#include "core/plugin_system/PluginManager.h"
#include "device_bus/manager/SerialDeviceManager.h"
@@ -44,39 +46,118 @@ QString rulesConfigPath()
return QDir::homePath() + QStringLiteral("/softbus/config/runtime_rules.json");
}
QString runtimeMappingsConfigPath()
{
const QFileInfo fi(rulesConfigPath());
return fi.dir().filePath(QStringLiteral("runtime_mappings.json"));
}
bool ensureConfigFileInitialized(const QString& targetPath,
const QString& fallbackPath,
const QByteArray& defaultContent,
const char* fileLabel)
{
if (QFile::exists(targetPath)) {
return true;
}
const QFileInfo fi(targetPath);
if (!QDir().mkpath(fi.dir().absolutePath())) {
LOG_WARNING() << "DeviceBusManager: failed to create config directory:" << fi.dir().absolutePath();
return false;
}
QByteArray initBytes = defaultContent;
QFile fallback(fallbackPath);
if (!fallbackPath.isEmpty() && fallback.exists() && fallback.open(QIODevice::ReadOnly)) {
initBytes = fallback.readAll();
}
QSaveFile initFile(targetPath);
if (!initFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
LOG_WARNING() << "DeviceBusManager: failed to create" << fileLabel << "file:" << targetPath;
return false;
}
if (initFile.write(initBytes) != initBytes.size()) {
LOG_WARNING() << "DeviceBusManager: failed to initialize" << fileLabel << "file:" << targetPath;
initFile.cancelWriting();
return false;
}
if (!initFile.commit()) {
LOG_WARNING() << "DeviceBusManager: failed to commit" << fileLabel << "file:" << targetPath;
return false;
}
LOG_INFO() << "DeviceBusManager: initialized" << fileLabel << "file:" << targetPath;
return true;
}
void ensureCoreConfigFilesInitialized()
{
const QFileInfo rulesFi(rulesConfigPath());
const QString baseDir = rulesFi.dir().absolutePath();
const QByteArray defaultRules = QJsonDocument(QJsonObject{
{QStringLiteral("filter"), QJsonArray{}},
{QStringLiteral("transform"), QJsonArray{}},
}).toJson(QJsonDocument::Indented);
(void)ensureConfigFileInitialized(rulesConfigPath(),
QStringLiteral(),
defaultRules,
"runtime_rules");
const QByteArray defaultMappings = QStringLiteral(
"{\n \"catalogVersion\": 1,\n \"items\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(runtimeMappingsConfigPath(),
QStringLiteral(),
defaultMappings,
"runtime_mappings");
const QString profilesPath = QDir(baseDir).filePath(QStringLiteral("device_profiles.json"));
const QByteArray defaultProfiles = QStringLiteral(
"{\n \"catalogVersion\": \"\",\n \"profiles\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(profilesPath,
QStringLiteral("config/device_profiles.json"),
defaultProfiles,
"device_profiles");
const QString metadataPath = QDir(baseDir).filePath(QStringLiteral("metadata_dictionary.json"));
const QByteArray defaultMetadata = QStringLiteral(
"{\n \"catalogVersion\": \"\",\n \"metadatas\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(metadataPath,
QStringLiteral("config/metadata_dictionary.json"),
defaultMetadata,
"metadata_dictionary");
}
void loadRuntimeMappingsFromDedicatedConfig()
{
const QString path = runtimeMappingsConfigPath();
if (!softbus::message_bus::pipeline::MappingRegistry::instance().loadPersistedFile(path)) {
LOG_WARNING() << "DeviceBusManager: failed to load runtime_mappings from" << path;
} else {
LOG_INFO() << "DeviceBusManager: loaded runtime_mappings from" << path;
}
}
void loadProfileCatalogSingleton()
{
const QString dir = QFileInfo(rulesConfigPath()).dir().absolutePath();
const QString profilePath = QDir(dir).filePath(QStringLiteral("device_profiles.json"));
if (!softbus::core::metadata::ProfileRegistry::instance().loadFromFile(profilePath)) {
LOG_WARNING() << "DeviceBusManager: profile catalog load failed path=" << profilePath
<< "err=" << softbus::core::metadata::ProfileRegistry::instance().lastError();
} else {
LOG_INFO() << "DeviceBusManager: loaded device_profiles from" << profilePath;
}
}
void loadDynamicRulesFromDedicatedConfig()
{
auto& reg = softbus::message_bus::pipeline::DynamicRuleRegistry::instance();
const QString path = rulesConfigPath();
QFile file(path);
if (!file.exists()) {
const QFileInfo fi(path);
if (!QDir().mkpath(fi.dir().absolutePath())) {
LOG_WARNING() << "DeviceBusManager: failed to create rules config directory:" << fi.dir().absolutePath();
return;
}
QSaveFile initFile(path);
if (!initFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
LOG_WARNING() << "DeviceBusManager: failed to create rules config file:" << path;
return;
}
const QJsonObject emptyRules{
{QStringLiteral("filter"), QJsonArray{}},
{QStringLiteral("transform"), QJsonArray{}},
};
const QByteArray initBytes = QJsonDocument(emptyRules).toJson(QJsonDocument::Indented);
if (initFile.write(initBytes) != initBytes.size()) {
LOG_WARNING() << "DeviceBusManager: failed to initialize rules config file:" << path;
initFile.cancelWriting();
return;
}
if (!initFile.commit()) {
LOG_WARNING() << "DeviceBusManager: failed to commit initialized rules config file:" << path;
return;
}
LOG_INFO() << "DeviceBusManager: created empty rules config file:" << path;
return;
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_WARNING() << "DeviceBusManager: failed to open rules config:" << path;
return;
@@ -207,7 +288,10 @@ bool DeviceBusManager::initialize(const QJsonObject& rootConfig)
m_pipelineEngine->setMemoryPool(m_memoryPool);
m_pipelineEngine->loadRuntimeConfigFromJson(rootConfig.value(QStringLiteral("pipeline")).toObject());
m_pipelineEngine->setPluginManager(m_pluginManager);
ensureCoreConfigFilesInitialized();
loadDynamicRulesFromDedicatedConfig();
loadRuntimeMappingsFromDedicatedConfig();
loadProfileCatalogSingleton();
m_pipelineEngine->start();
}
if (!m_ingressAdapter) {

View File

@@ -7,7 +7,11 @@
#include <functional>
#include "core/models/MessageRoutingUtils.h"
#include "device_bus/DeviceBus.h"
#include "device_bus/tree/DeviceTreeModel.h"
#include "core/metadata/ProfileRegistry.h"
#include "message_bus/pipeline/PipelineEngineApi.h"
#include "message_bus/pipeline/derivation/DerivationEngine.h"
#include "message_bus/pipeline/discovery/DiscoveryPool.h"
#include "message_bus/pipeline/mapping/MappingRegistry.h"
#include "message_bus/pipeline/stages/3_filters/ProtocolParseFilter.h"
@@ -37,24 +41,113 @@ struct EndpointFramingState
namespace
{
QString protocolNormForProfile(softbus::core::models::ProtocolType p)
{
using softbus::core::models::ProtocolType;
switch (p) {
case ProtocolType::MODBUS_RTU:
return QStringLiteral("modbus_rtu");
case ProtocolType::MODBUS_ASCII:
return QStringLiteral("modbus_ascii");
case ProtocolType::MODBUS_TCP:
return QStringLiteral("modbus_tcp");
default:
return {};
}
}
class MappingLookupAdapter final : public softbus::core::plugin_system::IMappingLookup
{
public:
std::optional<softbus::core::plugin_system::MappingLookupResult> find(std::uint64_t compositeKey) const override
std::optional<softbus::core::plugin_system::MappingLookupResult> find(
const softbus::core::models::RawBusMessage& raw,
std::uint64_t compositeKey) const override
{
auto rule = softbus::message_bus::pipeline::MappingRegistry::instance().find(compositeKey);
if (!rule.has_value()) {
if (rule.has_value()) {
softbus::core::plugin_system::MappingLookupResult out;
out.domPath = rule->domPath;
out.scale = rule->scale;
out.offset = rule->offset;
out.unit = rule->unit;
out.metadataId = rule->metadataId;
out.pointId = rule->pointId.isEmpty() ? rule->domPath : rule->pointId;
out.sourceTag = QStringLiteral("mapping");
return out;
}
const QString proto = protocolNormForProfile(raw.protocol);
if (proto.isEmpty() || raw.protocol != softbus::core::models::ProtocolType::MODBUS_RTU) {
return std::nullopt;
}
const std::uint16_t regAddr = static_cast<std::uint16_t>(compositeKey & 0xFFFFu);
const auto hit = softbus::core::metadata::ProfileRegistry::instance().findModbusRegisterMapping(
raw.deviceId, proto, regAddr);
if (!hit.has_value()) {
return std::nullopt;
}
softbus::core::plugin_system::MappingLookupResult out;
out.domPath = rule->domPath;
out.scale = rule->scale;
out.offset = rule->offset;
out.unit = rule->unit;
out.domPath = hit->domPath;
out.scale = hit->scale;
out.offset = hit->offset;
out.unit = hit->unit;
out.metadataId = hit->metadataId;
out.pointId = hit->pointId.isEmpty() ? hit->domPath : hit->pointId;
out.sourceTag = QStringLiteral("profile");
return out;
}
};
void attachPesmRuntimeContext(const softbus::core::models::RawBusMessage& raw,
std::vector<softbus::core::models::DOMMessage>& msgs)
{
if (msgs.empty()) {
return;
}
auto tree = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
std::optional<softbus::devices::DeviceInfo> devInfo;
if (raw.deviceId >= 0) {
devInfo = softbus::device_bus::DeviceBus::instance().findDeviceById(raw.deviceId);
}
const DeviceTreeNode* node = nullptr;
if (tree && devInfo.has_value()) {
const int idx = tree->findIndexByEndpoint(devInfo->endpoint);
if (idx >= 0 && idx < tree->nodes().size()) {
node = &tree->nodes().at(idx);
}
}
for (auto& msg : msgs) {
msg.attributes.insert(QStringLiteral("endpointHash"), static_cast<qint64>(raw.endpointHash));
if (raw.deviceId >= 0) {
msg.attributes.insert(QStringLiteral("runtimeDeviceId"), raw.deviceId);
}
if (devInfo.has_value()) {
msg.attributes.insert(QStringLiteral("deviceEndpoint"), devInfo->endpoint);
if (!devInfo->protocol.isEmpty()) {
msg.attributes.insert(QStringLiteral("deviceProtocol"), devInfo->protocol);
}
}
if (node) {
msg.attributes.insert(QStringLiteral("deviceNodeId"), node->id);
const QJsonValue av = node->params.value(QStringLiteral("assetId"));
if (av.isString() && !av.toString().isEmpty()) {
msg.attributes.insert(QStringLiteral("assetId"), av.toString());
}
const QJsonValue roleV = node->params.value(QStringLiteral("treeRole"));
if (roleV.isString() && !roleV.toString().isEmpty()) {
msg.attributes.insert(QStringLiteral("treeRole"), roleV.toString());
}
}
if (!msg.pointId.isEmpty()) {
msg.attributes.insert(QStringLiteral("pointId"), msg.pointId);
}
if (!msg.metadataId.isEmpty()) {
msg.attributes.insert(QStringLiteral("metadataId"), msg.metadataId);
}
}
}
class DiscoverySinkAdapter final : public softbus::core::plugin_system::IDiscoverySink
{
public:
@@ -584,6 +677,18 @@ bool PipelineEngine::runStages234(const softbus::core::models::RawBusMessage& ct
<< "quality=" << static_cast<int>(enriched.quality)
<< "alarm=" << enriched.alarm
<< "traceCount=" << static_cast<int>(enriched.traceLog.size());
std::vector<softbus::core::models::EnrichedMessage> derived;
softbus::message_bus::pipeline::derivation::DerivationEngine::instance().deriveFromEnriched(enriched, derived);
for (const auto& d : derived) {
const QString dv = std::visit(
[](const auto& x) { return QString::number(static_cast<double>(x), 'g', 12); },
d.engineeringValue);
LOG_INFO() << "PipelineEngine: derived message"
<< "sourceMessageId=" << d.sourceMessageId
<< "engineeringValue=" << dv
<< "traceCount=" << static_cast<int>(d.traceLog.size());
}
}
return true;
}
@@ -652,6 +757,9 @@ std::vector<softbus::core::models::DOMMessage> PipelineEngine::stage2Parser(cons
softbus::core::plugin_system::MapperSessionContext context;
context.mappingLookup = m_mappingLookup.get();
context.discoverySink = m_discoverySink.get();
context.sniffingEnabled = []() {
return softbus::message_bus::pipeline::DiscoveryPool::instance().isSniffingEnabled();
};
auto s = mapperPlugin->createMapperSession(context);
m_mapperSessions.insert(
mapperKey,
@@ -663,7 +771,9 @@ std::vector<softbus::core::models::DOMMessage> PipelineEngine::stage2Parser(cons
}
session = it.value();
}
return session->map(ctx, envelope);
auto messages = session->map(ctx, envelope);
attachPesmRuntimeContext(ctx, messages);
return messages;
}
bool PipelineEngine::stage3Filter(softbus::core::models::DOMMessage& message)

View File

@@ -0,0 +1,44 @@
#include "message_bus/pipeline/derivation/DerivationEngine.h"
#include <QJsonArray>
namespace softbus::message_bus::pipeline::derivation
{
DerivationEngine& DerivationEngine::instance()
{
static DerivationEngine eng;
return eng;
}
void DerivationEngine::deriveFromEnriched(const softbus::core::models::EnrichedMessage& in,
std::vector<softbus::core::models::EnrichedMessage>& outAppend) const
{
if (!m_enabled) {
return;
}
if (!in.attributes.value(QStringLiteral("sb.deriveMirror")).toBool()) {
return;
}
const QString srcPoint = in.attributes.value(QStringLiteral("pointId")).toString();
if (srcPoint.isEmpty()) {
return;
}
// MVP若存在派生后缀配置则复制一份工程值到派生点示例约定pointId + ".mirror"
const QString mirrorId = srcPoint + QStringLiteral(".mirror");
softbus::core::models::EnrichedMessage derived;
derived.sourceMessageId = mirrorId;
derived.logicalName = in.logicalName + QStringLiteral(" (derived)");
derived.unit = in.unit;
derived.engineeringValue = in.engineeringValue;
derived.quality = in.quality;
derived.attributes = in.attributes;
derived.traceLog = in.traceLog;
derived.attributes.insert(QStringLiteral("pointId"), mirrorId);
derived.attributes.insert(QStringLiteral("derivedFrom"), QJsonArray{srcPoint});
derived.attributes.insert(QStringLiteral("derivationRuleId"), QStringLiteral("builtin.mirror"));
derived.attributes.insert(QStringLiteral("derivationVersion"), QStringLiteral("1"));
outAppend.push_back(std::move(derived));
}
} // namespace softbus::message_bus::pipeline::derivation

View File

@@ -0,0 +1,30 @@
#pragma once
#include <vector>
#include "core/models/EnrichedMessage.h"
namespace softbus::message_bus::pipeline::derivation
{
/// 最小推导层:基于已富化的标准点生成可追溯派生点(占位 + 单规则 MVP
class DerivationEngine
{
public:
static DerivationEngine& instance();
/// 根据当前配置,从输入生成零条或多条派生富化消息(不改变输入)
void deriveFromEnriched(const softbus::core::models::EnrichedMessage& in,
std::vector<softbus::core::models::EnrichedMessage>& outAppend) const;
void setEnabled(bool on) { m_enabled = on; }
bool enabled() const { return m_enabled; }
private:
DerivationEngine() = default;
private:
bool m_enabled{true};
};
} // namespace softbus::message_bus::pipeline::derivation

View File

@@ -1,5 +1,13 @@
#include "message_bus/pipeline/mapping/MappingRegistry.h"
#include <QDateTime>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QSaveFile>
namespace softbus::message_bus::pipeline
{
@@ -15,14 +23,23 @@ bool MappingRegistry::upsert(const MappingRule& rule)
return false;
}
QWriteLocker lk(&m_lock);
m_rules[rule.compositeKey] = rule;
MappingRule r = rule;
if (r.updatedAtMs <= 0) {
r.updatedAtMs = QDateTime::currentMSecsSinceEpoch();
}
m_rules[rule.compositeKey] = std::move(r);
++m_catalogVersion;
return true;
}
bool MappingRegistry::remove(std::uint64_t compositeKey)
{
QWriteLocker lk(&m_lock);
return m_rules.erase(compositeKey) > 0;
const bool ok = m_rules.erase(compositeKey) > 0;
if (ok) {
++m_catalogVersion;
}
return ok;
}
std::optional<MappingRule> MappingRegistry::find(std::uint64_t compositeKey) const
@@ -58,6 +75,21 @@ QJsonArray MappingRegistry::list() const
o.insert(QStringLiteral("scale"), rule.scale);
o.insert(QStringLiteral("offset"), rule.offset);
o.insert(QStringLiteral("unit"), rule.unit);
if (!rule.metadataId.isEmpty()) {
o.insert(QStringLiteral("metadataId"), rule.metadataId);
}
if (!rule.pointId.isEmpty()) {
o.insert(QStringLiteral("pointId"), rule.pointId);
}
if (!rule.catalogVersion.isEmpty()) {
o.insert(QStringLiteral("catalogVersion"), rule.catalogVersion);
}
if (rule.updatedAtMs > 0) {
o.insert(QStringLiteral("updatedAtMs"), rule.updatedAtMs);
}
if (!rule.updatedBy.isEmpty()) {
o.insert(QStringLiteral("updatedBy"), rule.updatedBy);
}
arr.push_back(o);
}
return arr;
@@ -66,7 +98,110 @@ QJsonArray MappingRegistry::list() const
QJsonObject MappingRegistry::status() const
{
QReadLocker lk(&m_lock);
return QJsonObject{{QStringLiteral("mappingRuleCount"), static_cast<qint64>(m_rules.size())}};
QJsonObject o;
o.insert(QStringLiteral("mappingRuleCount"), static_cast<qint64>(m_rules.size()));
o.insert(QStringLiteral("mappingCatalogVersion"), m_catalogVersion);
return o;
}
bool MappingRegistry::loadPersistedFile(const QString& path)
{
QFile f(path);
if (!f.exists()) {
return true;
}
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QJsonParseError pe;
const QJsonDocument doc = QJsonDocument::fromJson(f.readAll(), &pe);
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
return false;
}
const QJsonObject root = doc.object();
const qint64 ver = root.value(QStringLiteral("catalogVersion")).toVariant().toLongLong();
const QJsonArray items = root.value(QStringLiteral("items")).toArray();
QWriteLocker lk(&m_lock);
m_rules.clear();
for (const auto& v : items) {
if (!v.isObject()) {
continue;
}
const QJsonObject o = v.toObject();
MappingRule rule;
rule.compositeKey = o.value(QStringLiteral("compositeKey")).toString().toULongLong();
rule.domPath = o.value(QStringLiteral("domPath")).toString();
rule.scale = o.value(QStringLiteral("scale")).toDouble(1.0);
rule.offset = o.value(QStringLiteral("offset")).toDouble(0.0);
rule.unit = o.value(QStringLiteral("unit")).toString();
rule.metadataId = o.value(QStringLiteral("metadataId")).toString();
rule.pointId = o.value(QStringLiteral("pointId")).toString();
rule.catalogVersion = o.value(QStringLiteral("catalogVersion")).toString();
rule.updatedAtMs = o.value(QStringLiteral("updatedAtMs")).toVariant().toLongLong();
rule.updatedBy = o.value(QStringLiteral("updatedBy")).toString();
if (rule.compositeKey != 0 && !rule.domPath.isEmpty()) {
m_rules[rule.compositeKey] = rule;
}
}
if (ver > 0) {
m_catalogVersion = ver;
}
return true;
}
bool MappingRegistry::savePersistedFile(const QString& path) const
{
QJsonArray items;
{
QReadLocker lk(&m_lock);
for (const auto& kv : m_rules) {
const auto& rule = kv.second;
QJsonObject o;
o.insert(QStringLiteral("compositeKey"), QString::number(rule.compositeKey));
o.insert(QStringLiteral("domPath"), rule.domPath);
o.insert(QStringLiteral("scale"), rule.scale);
o.insert(QStringLiteral("offset"), rule.offset);
o.insert(QStringLiteral("unit"), rule.unit);
if (!rule.metadataId.isEmpty()) {
o.insert(QStringLiteral("metadataId"), rule.metadataId);
}
if (!rule.pointId.isEmpty()) {
o.insert(QStringLiteral("pointId"), rule.pointId);
}
if (!rule.catalogVersion.isEmpty()) {
o.insert(QStringLiteral("catalogVersion"), rule.catalogVersion);
}
if (rule.updatedAtMs > 0) {
o.insert(QStringLiteral("updatedAtMs"), rule.updatedAtMs);
}
if (!rule.updatedBy.isEmpty()) {
o.insert(QStringLiteral("updatedBy"), rule.updatedBy);
}
items.push_back(o);
}
}
qint64 verSnap = 1;
{
QReadLocker lk(&m_lock);
verSnap = m_catalogVersion;
}
QJsonObject root;
root.insert(QStringLiteral("catalogVersion"), verSnap);
root.insert(QStringLiteral("items"), items);
QSaveFile out(path);
if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
const QByteArray bytes = QJsonDocument(root).toJson(QJsonDocument::Indented);
if (out.write(bytes) != bytes.size()) {
out.cancelWriting();
return false;
}
return out.commit();
}
} // namespace softbus::message_bus::pipeline

View File

@@ -19,6 +19,11 @@ struct MappingRule
double scale{1.0};
double offset{0.0};
QString unit;
QString metadataId;
QString pointId;
QString catalogVersion;
qint64 updatedAtMs{0};
QString updatedBy;
};
class MappingRegistry
@@ -34,12 +39,17 @@ public:
QJsonArray list() const;
QJsonObject status() const;
bool loadPersistedFile(const QString& path);
bool savePersistedFile(const QString& path) const;
qint64 mappingCatalogVersion() const { return m_catalogVersion; }
private:
MappingRegistry() = default;
private:
mutable QReadWriteLock m_lock;
std::unordered_map<std::uint64_t, MappingRule> m_rules;
qint64 m_catalogVersion{1};
};
} // namespace softbus::message_bus::pipeline

View File

@@ -13,14 +13,18 @@ bool ProtocolParseFilter::process(softbus::core::models::DOMMessage& message)
if (!ensureLoaded()) {
return true;
}
// Message-stage filter: only normalize quality by metadata range.
if (!message.metaRef) {
return true;
const softbus::core::models::MetadataDef* meta = message.metaRef;
if (!meta && !message.metadataId.isEmpty()) {
meta = m_metadataRegistry.findById(message.metadataId);
}
const auto* meta = m_metadataRegistry.findById(message.metaRef->metadataId);
if (!meta) {
return true;
}
if (message.metadataId.isEmpty()) {
message.metadataId = meta->metadataId;
}
message.attributes.insert(QStringLiteral("domain"),
softbus::core::models::dataDomainToString(meta->domain));
const double v = std::visit(
[](const auto& x) { return static_cast<double>(x); },
message.value);
@@ -36,12 +40,27 @@ 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.metadataId = message.metadataId;
out.pointId = message.pointId.isEmpty() ? message.messageId : message.pointId;
const softbus::core::models::MetadataDef* meta = message.metaRef;
if (!meta && !message.metadataId.isEmpty()) {
meta = m_metadataRegistry.findById(message.metadataId);
}
out.logicalName = meta ? meta->name : message.messageId;
out.unit = meta ? meta->unit : QString();
if (!meta && message.attributes.contains(QStringLiteral("unit"))) {
out.unit = message.attributes.value(QStringLiteral("unit")).toString();
}
out.engineeringValue = message.value;
out.quality = message.quality;
out.attributes = message.attributes;
out.traceLog = message.traceLog;
if (meta && !meta->entryCatalogVersion.isEmpty()) {
out.catalogVersion = meta->entryCatalogVersion;
} else if (!m_metadataRegistry.catalogVersion().isEmpty()) {
out.catalogVersion = m_metadataRegistry.catalogVersion();
}
return out;
}
@@ -64,4 +83,3 @@ bool ProtocolParseFilter::ensureLoaded() const
}
} // namespace softbus::message_bus::pipeline::stages::filters