context bind

This commit is contained in:
flower_linux
2026-04-22 17:25:51 +08:00
parent 11bc015444
commit 7459cc0f36
31 changed files with 1427 additions and 31 deletions

View File

@@ -160,6 +160,8 @@ qt_add_executable(softbus_daemon
src/message_bus/pipeline/mapping/MappingRegistry.cpp src/message_bus/pipeline/mapping/MappingRegistry.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/PipelineEngineApi.h
src/message_bus/pipeline/PipelineEngineApi.cpp
src/message_bus/pipeline/protocol/ProtocolEnvelope.h src/message_bus/pipeline/protocol/ProtocolEnvelope.h
plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h plugins/protocols/modbus_rtu/types/ModbusRtuPdu.h
plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h

Binary file not shown.

View File

@@ -24,7 +24,7 @@ public:
{ {
m_buf.resize(m_cfg.maxAdu); m_buf.resize(m_cfg.maxAdu);
} }
// 功能 处理收到的数据块
void feedChunk(softbus::core::models::RawBusMessage& in, void feedChunk(softbus::core::models::RawBusMessage& in,
std::vector<softbus::core::models::RawBusMessage>& out) override std::vector<softbus::core::models::RawBusMessage>& out) override
{ {
@@ -47,6 +47,7 @@ public:
m_lastActivity = m_lastRx; m_lastActivity = m_lastRx;
if (m_len + n > m_cfg.maxAdu) { if (m_len + n > m_cfg.maxAdu) {
// 功能 如果数据块超过最大长度,则丢弃
LOG_WARNING() << "ModbusRtuFramerPlugin: overflow endpointHash=0x" LOG_WARNING() << "ModbusRtuFramerPlugin: overflow endpointHash=0x"
<< QString::number(m_endpointHash, 16); << QString::number(m_endpointHash, 16);
m_len = 0; m_len = 0;

View File

@@ -64,6 +64,19 @@ public:
rec.lastSeenMs = rawContext.timestampMs; rec.lastSeenMs = rawContext.timestampMs;
m_context.discoverySink->reportUnmapped(rec); m_context.discoverySink->reportUnmapped(rec);
} }
// 无 metadata 映射时仍产出 DOM便于 DynamicRuleRegistry 的 transform 等下游逻辑生效
// compositeKey 仍按与映射表相同的算法计算,便于后续在 UI 里绑定映射)
softbus::core::models::DOMMessage pass;
pass.messageId = QStringLiteral("modbus.unmapped:%1").arg(compKey);
pass.timestamp = rawContext.timestampMs;
pass.value = rawValue;
pass.quality = softbus::core::models::DataQuality::GOOD;
pass.attributes.insert(QStringLiteral("sourceEndpoint"), QString::number(rawContext.endpointHash));
pass.attributes.insert(QStringLiteral("compositeKey"), QString::number(compKey));
pass.attributes.insert(QStringLiteral("slaveId"), static_cast<int>(resp->unitId));
pass.attributes.insert(QStringLiteral("registerAddress"), static_cast<int>(addr));
pass.attributes.insert(QStringLiteral("unmapped"), true);
out.push_back(std::move(pass));
continue; continue;
} }
softbus::core::models::DOMMessage msg; softbus::core::models::DOMMessage msg;

View File

@@ -78,7 +78,57 @@
| `GET` | `/v1/status` | 透传 `get_status` 的状态响应 | | `GET` | `/v1/status` | 透传 `get_status` 的状态响应 |
| `POST` | `/v1/command` | 统一命令入口(请求体为 executeCommand JSON | | `POST` | `/v1/command` | 统一命令入口(请求体为 executeCommand JSON |
### 5.2 Discovery 路由 ### 5.2 设备树 / 设备路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/device-tree` | 无 | 返回当前设备树快照 |
| `GET` | `/api/v1/devices` | 无 | 返回当前运行态设备列表 |
| `GET` | `/api/v1/devices/{id}` | path: `id(int)` | 返回指定运行态设备详情 |
| `PATCH` | `/api/v1/device-tree/nodes/{id}` | body: 任意可变节点字段子集 | 部分更新设备树节点;当前至少保护 `id``params.stableKey``params.online``params.lastSeenTs` |
### 5.3 规则管理路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/rules/status` | 无 | 返回动态规则计数状态 |
| `POST` | `/api/v1/rules/filter` | body: `id`, `endpointHash?`, `deviceId?`, `stableKey?`, `condition?` | 新增过滤规则 |
| `DELETE` | `/api/v1/rules/filter/{id}` | path: `id` | 删除过滤规则 |
| `POST` | `/api/v1/rules/transform` | body: `id`, `endpointHash?`, `deviceId?`, `stableKey?`, `scale?`, `offset?`, `unit?` | 新增转换规则 |
| `DELETE` | `/api/v1/rules/transform/{id}` | path: `id` | 删除转换规则 |
- 说明:
- `transform` 规则已经在 `PipelineEngine::applyTransformAndEnrich(...)` 中生效。
- `filter` 路由当前主要提供“注册表管理”能力;动态 filter 规则尚未真正接入 `PipelineEngine::stage3Filter(...)` 的执行链路。
### 5.4 Pipeline 路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/pipeline/status` | 无 | 返回 pipeline 运行状态、session 数量、队列容量、插件摘要与 counters |
| `GET` | `/api/v1/pipeline/counters` | 无 | 返回 `in/out/drop/error/mergeDrop` 计数器快照 |
| `GET` | `/api/v1/pipeline/config` | 无 | 返回当前运行时配置快照 |
- 说明:`/v1/status` 仍然是 control-plane 摘要(规则 + discovery + mapping不等同于 `/api/v1/pipeline/status`
### 5.5 插件 / 协议能力路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/plugins` | 无 | 返回 parser / framer / mapper 三类逻辑插件的统一列表 |
| `GET` | `/api/v1/plugins/protocols` | 无 | 返回按协议聚合后的能力快照parser/framer/mapper pluginId |
| `GET` | `/api/v1/plugins/mappers` | 无 | 返回 mapper 插件列表 |
### 5.6 Metadata / Profile 路由
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `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 |
### 5.7 Discovery 路由
| 方法 | 路径 | 请求参数 | 说明 | | 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---| |---|---|---|---|
@@ -88,7 +138,7 @@
| `POST` | `/api/v1/discovery/clear` | 无 | 清空发现池样本 | | `POST` | `/api/v1/discovery/clear` | 无 | 清空发现池样本 |
| `GET` | `/api/v1/discovery/samples` | query: `offset?`, `limit?`, `endpointHash?`, `protocol?` | 拉取样本(按更新时间倒序,且过滤已映射 key | | `GET` | `/api/v1/discovery/samples` | query: `offset?`, `limit?`, `endpointHash?`, `protocol?` | 拉取样本(按更新时间倒序,且过滤已映射 key |
### 5.3 Mapping 路由 ### 5.8 Mapping 路由
| 方法 | 路径 | 请求参数 | 说明 | | 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---| |---|---|---|---|

View File

@@ -1,7 +1,12 @@
#include "api/http/RestApiRoutes.h" #include "api/http/RestApiRoutes.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <mutex>
#include <QDir>
#include <QFileInfo>
#include <QHttpServer> #include <QHttpServer>
#include <QHttpHeaders>
#include <QHttpServerRequest> #include <QHttpServerRequest>
#include <QHttpServerResponse> #include <QHttpServerResponse>
#include <QHttpServerResponder> #include <QHttpServerResponder>
@@ -9,13 +14,20 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonParseError> #include <QJsonParseError>
#include <QJsonValue>
#include <QUrlQuery> #include <QUrlQuery>
#include <utility> #include <utility>
#include "api/http/RestApiCommandHttp.h" #include "api/http/RestApiCommandHttp.h"
#include "api/ipc_dbus/CommandDispatcher.h" #include "api/ipc_dbus/CommandDispatcher.h"
#include "core/metadata/MetadataRegistry.h"
#include "core/metadata/ProfileRegistry.h"
#include "core/plugin_system/PluginManager.h"
#include "device_bus/DeviceBus.h"
#include "message_bus/pipeline/discovery/DiscoveryPool.h" #include "message_bus/pipeline/discovery/DiscoveryPool.h"
#include "message_bus/pipeline/mapping/MappingRegistry.h" #include "message_bus/pipeline/mapping/MappingRegistry.h"
#include "message_bus/pipeline/PipelineEngine.h"
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
namespace softbus::api::http namespace softbus::api::http
{ {
@@ -23,6 +35,97 @@ namespace softbus::api::http
namespace namespace
{ {
QHttpServerResponse withCorsHeaders(QHttpServerResponse response)
{
QHttpHeaders headers = response.headers();
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowOrigin, QByteArrayView("*"));
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowMethods,
QByteArrayView("GET, POST, PATCH, DELETE, OPTIONS"));
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowHeaders,
QByteArrayView("Content-Type, Authorization, X-Requested-With"));
headers.append(QHttpHeaders::WellKnownHeader::AccessControlMaxAge, QByteArrayView("86400"));
response.setHeaders(std::move(headers));
return response;
}
QHttpServerResponse corsPreflightResponse()
{
return withCorsHeaders(QHttpServerResponse(QHttpServerResponse::StatusCode::NoContent));
}
void registerCorsPreflightRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/health"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/v1/status"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/v1/command"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/start"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/stop"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/status"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/clear"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/discovery/samples"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/mapping/bind"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/device-tree"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/devices"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/devices/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/device-tree/nodes/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/status"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/filter"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/filter/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/transform"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/rules/transform/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/pipeline/status"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/pipeline/counters"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/pipeline/counters/clear"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/pipeline/config"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/plugins"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/plugins/protocols"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/plugins/mappers"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/metadata"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/metadata/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/profiles"), QHttpServerRequest::Method::Options, routeContext,
[]() { return corsPreflightResponse(); });
server->route(QStringLiteral("/api/v1/profiles/<arg>"), QHttpServerRequest::Method::Options, routeContext,
[](const QString&) { return corsPreflightResponse(); });
}
constexpr auto kProtectedNodeKeys = std::array{
"id",
"stableKey",
"online",
"lastSeenTs",
};
softbus::core::models::ProtocolType protocolFromString(const QString& s) softbus::core::models::ProtocolType protocolFromString(const QString& s)
{ {
const QString x = s.trimmed().toUpper(); const QString x = s.trimmed().toUpper();
@@ -47,16 +150,190 @@ softbus::core::models::ProtocolType protocolFromString(const QString& s)
return softbus::core::models::ProtocolType::UNKNOWN; return softbus::core::models::ProtocolType::UNKNOWN;
} }
QJsonObject parseJsonBody(const QHttpServerRequest& request) QJsonObject parseJsonBody(const QHttpServerRequest& request, bool* ok = nullptr)
{ {
QJsonParseError err; QJsonParseError err;
const auto doc = QJsonDocument::fromJson(request.body(), &err); const auto doc = QJsonDocument::fromJson(request.body(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) { const bool parsed = err.error == QJsonParseError::NoError && doc.isObject();
if (ok) {
*ok = parsed;
}
if (!parsed) {
return {}; return {};
} }
return doc.object(); return doc.object();
} }
QHttpServerResponse jsonResponse(const QJsonObject& obj,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
{
return withCorsHeaders(QHttpServerResponse(obj, code));
}
QHttpServerResponse okDataResponse(const QJsonValue& data,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
{
return jsonResponse(QJsonObject{{QStringLiteral("ok"), true}, {QStringLiteral("data"), data}}, code);
}
QHttpServerResponse errorResponse(const QString& error,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::BadRequest)
{
return jsonResponse(QJsonObject{{QStringLiteral("ok"), false}, {QStringLiteral("error"), error}}, code);
}
QJsonObject deviceTreeNodeToJson(const DeviceTreeNode& node)
{
QJsonObject obj;
obj.insert(QStringLiteral("id"), node.id);
obj.insert(QStringLiteral("name"), node.name);
obj.insert(QStringLiteral("type"), node.type);
obj.insert(QStringLiteral("endpoint"), node.endpoint);
obj.insert(QStringLiteral("driver"), node.driver);
obj.insert(QStringLiteral("capabilitiesRef"), node.capabilitiesRef);
obj.insert(QStringLiteral("params"), node.params);
obj.insert(QStringLiteral("enabled"), node.enabled);
QJsonArray children;
for (const auto& child : node.childrenIds) {
children.push_back(child);
}
obj.insert(QStringLiteral("children"), children);
return obj;
}
QJsonArray devicesToJson(const std::vector<softbus::devices::DeviceInfo>& devices)
{
QJsonArray out;
for (const auto& device : devices) {
out.push_back(device.toJsonObject());
}
return out;
}
QString resolveConfigBaseDir()
{
QString configBase = qEnvironmentVariable("SOFTBUS_CONFIG_DIR");
if (!configBase.isEmpty()) {
return configBase.endsWith(QChar('/')) ? configBase : configBase + QChar('/');
}
return QDir::homePath() + QStringLiteral("/softbus/config/");
}
QString resolveConfigFile(const QString& fileName, const QString& fallbackRelative)
{
const QString homePath = resolveConfigBaseDir() + fileName;
return QFileInfo::exists(homePath) ? homePath : fallbackRelative;
}
bool ensureMetadataLoaded(softbus::core::metadata::MetadataRegistry& registry, QString* error)
{
static std::mutex mtx;
static bool attempted = false;
static bool loaded = false;
std::lock_guard<std::mutex> lk(mtx);
if (!attempted) {
attempted = true;
loaded = registry.loadFromFile(
resolveConfigFile(QStringLiteral("metadata_dictionary.json"),
QStringLiteral("config/metadata_dictionary.json")));
}
if (!loaded && error) {
*error = registry.lastError();
}
return loaded;
}
softbus::core::metadata::MetadataRegistry& metadataRegistry()
{
static softbus::core::metadata::MetadataRegistry registry;
return registry;
}
bool ensureProfileLoaded(softbus::core::metadata::ProfileRegistry& registry, QString* error)
{
static std::mutex mtx;
static bool attempted = false;
static bool loaded = false;
std::lock_guard<std::mutex> lk(mtx);
if (!attempted) {
attempted = true;
loaded = registry.loadFromFile(
resolveConfigFile(QStringLiteral("device_profiles.json"),
QStringLiteral("config/device_profiles.json")));
}
if (!loaded && error) {
*error = registry.lastError();
}
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) {
if (key == QLatin1String(value)) {
return true;
}
}
return false;
}
bool applyNodePatch(DeviceTreeNode& node, const QJsonObject& patch)
{
bool changed = false;
if (patch.contains(QStringLiteral("name")) && patch.value(QStringLiteral("name")).isString()) {
node.name = patch.value(QStringLiteral("name")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("type")) && patch.value(QStringLiteral("type")).isString()) {
node.type = patch.value(QStringLiteral("type")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("endpoint")) && patch.value(QStringLiteral("endpoint")).isString()) {
node.endpoint = patch.value(QStringLiteral("endpoint")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("driver")) && patch.value(QStringLiteral("driver")).isString()) {
node.driver = patch.value(QStringLiteral("driver")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("capabilitiesRef")) && patch.value(QStringLiteral("capabilitiesRef")).isString()) {
node.capabilitiesRef = patch.value(QStringLiteral("capabilitiesRef")).toString();
changed = true;
}
if (patch.contains(QStringLiteral("enabled")) && patch.value(QStringLiteral("enabled")).isBool()) {
node.enabled = patch.value(QStringLiteral("enabled")).toBool();
changed = true;
}
if (patch.contains(QStringLiteral("children")) && patch.value(QStringLiteral("children")).isArray()) {
QVector<QString> children;
for (const auto& child : patch.value(QStringLiteral("children")).toArray()) {
if (child.isString()) {
children.push_back(child.toString());
}
}
node.childrenIds = children;
changed = true;
}
if (patch.contains(QStringLiteral("params")) && patch.value(QStringLiteral("params")).isObject()) {
const auto paramsPatch = patch.value(QStringLiteral("params")).toObject();
for (auto it = paramsPatch.begin(); it != paramsPatch.end(); ++it) {
if (isProtectedNodeKey(it.key())) {
continue;
}
node.params.insert(it.key(), it.value());
changed = true;
}
}
return changed;
}
void registerDiscoveryRoutes(QHttpServer* server, QObject* routeContext) void registerDiscoveryRoutes(QHttpServer* server, QObject* routeContext)
{ {
server->route(QStringLiteral("/api/v1/discovery/start"), QHttpServerRequest::Method::Post, routeContext, server->route(QStringLiteral("/api/v1/discovery/start"), QHttpServerRequest::Method::Post, routeContext,
@@ -65,25 +342,25 @@ void registerDiscoveryRoutes(QHttpServer* server, QObject* routeContext)
const auto payload = parseJsonBody(request); const auto payload = parseJsonBody(request);
const std::int64_t ttlMs = payload.value(QStringLiteral("ttlMs")).toInteger(0); const std::int64_t ttlMs = payload.value(QStringLiteral("ttlMs")).toInteger(0);
pool.startSniffing(ttlMs); pool.startSniffing(ttlMs);
return QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok); return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
}); });
server->route(QStringLiteral("/api/v1/discovery/stop"), QHttpServerRequest::Method::Post, routeContext, []() { server->route(QStringLiteral("/api/v1/discovery/stop"), QHttpServerRequest::Method::Post, routeContext, []() {
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance(); auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
pool.stopSniffing(); pool.stopSniffing();
return QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok); return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
}); });
server->route(QStringLiteral("/api/v1/discovery/status"), QHttpServerRequest::Method::Get, routeContext, []() { server->route(QStringLiteral("/api/v1/discovery/status"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance(); auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
pool.tick(); pool.tick();
return QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok); return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
}); });
server->route(QStringLiteral("/api/v1/discovery/clear"), QHttpServerRequest::Method::Post, routeContext, []() { server->route(QStringLiteral("/api/v1/discovery/clear"), QHttpServerRequest::Method::Post, routeContext, []() {
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance(); auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
pool.clear(); pool.clear();
return QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok); return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
}); });
server->route(QStringLiteral("/api/v1/discovery/samples"), QHttpServerRequest::Method::Get, routeContext, server->route(QStringLiteral("/api/v1/discovery/samples"), QHttpServerRequest::Method::Get, routeContext,
@@ -108,7 +385,7 @@ void registerDiscoveryRoutes(QHttpServer* server, QObject* routeContext)
o.insert(QStringLiteral("offset"), static_cast<qint64>(offset)); o.insert(QStringLiteral("offset"), static_cast<qint64>(offset));
o.insert(QStringLiteral("limit"), static_cast<qint64>(limit)); o.insert(QStringLiteral("limit"), static_cast<qint64>(limit));
o.insert(QStringLiteral("status"), pool.status()); o.insert(QStringLiteral("status"), pool.status());
return QHttpServerResponse(o, QHttpServerResponse::StatusCode::Ok); return withCorsHeaders(QHttpServerResponse(o, QHttpServerResponse::StatusCode::Ok));
}); });
} }
@@ -126,14 +403,14 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance(); auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
if (!mappingReg.upsert(rule)) { if (!mappingReg.upsert(rule)) {
return QHttpServerResponse( return withCorsHeaders(QHttpServerResponse(
QJsonObject{{QStringLiteral("error"), QStringLiteral("invalid_mapping_rule")}}, QJsonObject{{QStringLiteral("error"), QStringLiteral("invalid_mapping_rule")}},
QHttpServerResponse::StatusCode::BadRequest); QHttpServerResponse::StatusCode::BadRequest));
} }
return QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true}, return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true},
{QStringLiteral("compositeKey"), {QStringLiteral("compositeKey"),
QString::number(rule.compositeKey)}}, QString::number(rule.compositeKey)}},
QHttpServerResponse::StatusCode::Ok); QHttpServerResponse::StatusCode::Ok));
}); });
server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Post, routeContext, server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Post, routeContext,
@@ -143,20 +420,264 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
payload.value(QStringLiteral("compositeKey")).toString().toULongLong(); payload.value(QStringLiteral("compositeKey")).toString().toULongLong();
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance(); auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
const bool ok = mappingReg.remove(compositeKey); const bool ok = mappingReg.remove(compositeKey);
return QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), ok}, return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), ok},
{QStringLiteral("compositeKey"), {QStringLiteral("compositeKey"),
QString::number(compositeKey)}}, QString::number(compositeKey)}},
ok ? QHttpServerResponse::StatusCode::Ok ok ? QHttpServerResponse::StatusCode::Ok
: QHttpServerResponse::StatusCode::NotFound); : QHttpServerResponse::StatusCode::NotFound));
}); });
server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Get, routeContext, []() { server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance(); auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
return QHttpServerResponse(QJsonObject{{QStringLiteral("items"), mappingReg.list()}}, return withCorsHeaders(
QHttpServerResponse::StatusCode::Ok); QHttpServerResponse(QJsonObject{{QStringLiteral("items"), mappingReg.list()}},
QHttpServerResponse::StatusCode::Ok));
}); });
} }
void registerDeviceRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/device-tree"), QHttpServerRequest::Method::Get, routeContext, []() {
auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
if (!model) {
return errorResponse(QStringLiteral("device_tree_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(model->toDeviceTreeJson());
});
server->route(QStringLiteral("/api/v1/devices"), QHttpServerRequest::Method::Get, routeContext, []() {
return okDataResponse(devicesToJson(softbus::device_bus::DeviceBus::instance().listDevices()));
});
server->route(QStringLiteral("/api/v1/devices/<arg>"), QHttpServerRequest::Method::Get, routeContext,
[](int id) {
const auto device = softbus::device_bus::DeviceBus::instance().findDeviceById(id);
if (!device.has_value()) {
return errorResponse(QStringLiteral("device_not_found"),
QHttpServerResponse::StatusCode::NotFound);
}
return okDataResponse(device->toJsonObject());
});
server->route(QStringLiteral("/api/v1/device-tree/nodes/<arg>"), QHttpServerRequest::Method::Patch, routeContext,
[](const QString& id, const QHttpServerRequest& request) {
bool bodyOk = false;
const auto patch = parseJsonBody(request, &bodyOk);
if (!bodyOk) {
return errorResponse(QStringLiteral("invalid_json"),
QHttpServerResponse::StatusCode::BadRequest);
}
auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
if (!model) {
return errorResponse(QStringLiteral("device_tree_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
const int index = model->findIndexById(id);
if (index < 0) {
return errorResponse(QStringLiteral("device_tree_node_not_found"),
QHttpServerResponse::StatusCode::NotFound);
}
auto& node = model->nodesMutable()[index];
const bool changed = applyNodePatch(node, patch);
if (!changed) {
return errorResponse(QStringLiteral("no_mutable_fields_provided"),
QHttpServerResponse::StatusCode::BadRequest);
}
model->rebuildIndex();
auto& bus = softbus::device_bus::DeviceBus::instance();
bus.markDeviceTreeDirty();
(void)bus.reconcileFromTreeModel();
return okDataResponse(deviceTreeNodeToJson(model->nodes().at(index)));
});
}
void registerRuleRoutes(QHttpServer* server,
QObject* routeContext,
softbus::api::ipc::CommandDispatcher* dispatcher)
{
server->route(QStringLiteral("/api/v1/rules/status"), QHttpServerRequest::Method::Get, routeContext, []() {
return okDataResponse(softbus::message_bus::pipeline::DynamicRuleRegistry::instance().status());
});
server->route(QStringLiteral("/api/v1/rules/filter"), QHttpServerRequest::Method::Post, routeContext,
[dispatcher](const QHttpServerRequest& request) {
bool bodyOk = false;
const auto payload = parseJsonBody(request, &bodyOk);
if (!bodyOk) {
return errorResponse(QStringLiteral("invalid_json"),
QHttpServerResponse::StatusCode::BadRequest);
}
const auto result = dispatcher->dispatch(QStringLiteral("add_filter"), payload);
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
});
server->route(QStringLiteral("/api/v1/rules/filter/<arg>"), QHttpServerRequest::Method::Delete, routeContext,
[dispatcher](const QString& id) {
const auto result = dispatcher->dispatch(QStringLiteral("remove_filter"),
QJsonObject{{QStringLiteral("id"), id}});
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
});
server->route(QStringLiteral("/api/v1/rules/transform"), QHttpServerRequest::Method::Post, routeContext,
[dispatcher](const QHttpServerRequest& request) {
bool bodyOk = false;
const auto payload = parseJsonBody(request, &bodyOk);
if (!bodyOk) {
return errorResponse(QStringLiteral("invalid_json"),
QHttpServerResponse::StatusCode::BadRequest);
}
const auto result = dispatcher->dispatch(QStringLiteral("add_transform_rule"), payload);
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
});
server->route(QStringLiteral("/api/v1/rules/transform/<arg>"), QHttpServerRequest::Method::Delete, routeContext,
[dispatcher](const QString& id) {
const auto result = dispatcher->dispatch(QStringLiteral("remove_transform_rule"),
QJsonObject{{QStringLiteral("id"), id}});
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
});
}
void registerPipelineRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/pipeline/status"), QHttpServerRequest::Method::Get, routeContext, []() {
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
if (!pipeline) {
return errorResponse(QStringLiteral("pipeline_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pipeline->status());
});
server->route(QStringLiteral("/api/v1/pipeline/counters"), QHttpServerRequest::Method::Get, routeContext, []() {
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
if (!pipeline) {
return errorResponse(QStringLiteral("pipeline_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pipeline->countersJson());
});
server->route(QStringLiteral("/api/v1/pipeline/counters/clear"), QHttpServerRequest::Method::Post, routeContext, []() {
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
if (!pipeline) {
return errorResponse(QStringLiteral("pipeline_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
pipeline->resetCounters();
return okDataResponse(pipeline->countersJson());
});
server->route(QStringLiteral("/api/v1/pipeline/config"), QHttpServerRequest::Method::Get, routeContext, []() {
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
if (!pipeline) {
return errorResponse(QStringLiteral("pipeline_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pipeline->runtimeConfigJson());
});
}
void registerPluginRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/plugins"), QHttpServerRequest::Method::Get, routeContext, []() {
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
if (!pluginManager) {
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
QJsonArray items;
for (const auto& entry : pluginManager->protocolPluginsSnapshot()) {
items.push_back(entry);
}
for (const auto& entry : pluginManager->protocolFramerPluginsSnapshot()) {
items.push_back(entry);
}
for (const auto& entry : pluginManager->protocolMapperPluginsSnapshot()) {
items.push_back(entry);
}
return okDataResponse(items);
});
server->route(QStringLiteral("/api/v1/plugins/protocols"), QHttpServerRequest::Method::Get, routeContext, []() {
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
if (!pluginManager) {
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pluginManager->protocolCapabilitiesSnapshot());
});
server->route(QStringLiteral("/api/v1/plugins/mappers"), QHttpServerRequest::Method::Get, routeContext, []() {
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
if (!pluginManager) {
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(pluginManager->protocolMapperPluginsSnapshot());
});
}
void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/metadata"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& registry = metadataRegistry();
QString error;
if (!ensureMetadataLoaded(registry, &error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("metadata_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.list());
});
server->route(QStringLiteral("/api/v1/metadata/<arg>"), QHttpServerRequest::Method::Get, routeContext,
[](const QString& metadataId) {
auto& registry = metadataRegistry();
QString error;
if (!ensureMetadataLoaded(registry, &error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("metadata_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
const auto items = registry.list();
for (const auto& item : items) {
const auto obj = item.toObject();
if (obj.value(QStringLiteral("metadataId")).toString() == metadataId) {
return okDataResponse(obj);
}
}
return errorResponse(QStringLiteral("metadata_not_found"),
QHttpServerResponse::StatusCode::NotFound);
});
}
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)) {
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.list());
});
server->route(QStringLiteral("/api/v1/profiles/<arg>"), QHttpServerRequest::Method::Get, routeContext,
[](const QString& deviceId) {
auto& registry = profileRegistry();
QString error;
if (!ensureProfileLoaded(registry, &error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.listByDeviceId(deviceId));
});
}
} // namespace } // namespace
void registerSoftbusRestApiRoutes(QHttpServer* server, void registerSoftbusRestApiRoutes(QHttpServer* server,
@@ -168,24 +689,33 @@ void registerSoftbusRestApiRoutes(QHttpServer* server,
} }
server->route(QStringLiteral("/health"), QHttpServerRequest::Method::Get, routeContext, []() { server->route(QStringLiteral("/health"), QHttpServerRequest::Method::Get, routeContext, []() {
return QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true}}, QHttpServerResponse::StatusCode::Ok); return withCorsHeaders(
QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true}}, QHttpServerResponse::StatusCode::Ok));
}); });
server->route(QStringLiteral("/v1/status"), QHttpServerRequest::Method::Get, routeContext, [dispatcher]() { server->route(QStringLiteral("/v1/status"), QHttpServerRequest::Method::Get, routeContext, [dispatcher]() {
return makeCommandHttpResponse(QString(), dispatcher->dispatch(QStringLiteral("get_status"), {})); return withCorsHeaders(makeCommandHttpResponse(QString(), dispatcher->dispatch(QStringLiteral("get_status"), {})));
}); });
server->route(QStringLiteral("/v1/command"), QHttpServerRequest::Method::Post, routeContext, server->route(QStringLiteral("/v1/command"), QHttpServerRequest::Method::Post, routeContext,
[dispatcher](const QHttpServerRequest& request) { [dispatcher](const QHttpServerRequest& request) {
return handleExecuteCommandHttpBody(request.body(), *dispatcher); return withCorsHeaders(handleExecuteCommandHttpBody(request.body(), *dispatcher));
}); });
registerCorsPreflightRoutes(server, routeContext);
registerDiscoveryRoutes(server, routeContext); registerDiscoveryRoutes(server, routeContext);
registerMappingRoutes(server, routeContext); registerMappingRoutes(server, routeContext);
registerDeviceRoutes(server, routeContext);
registerRuleRoutes(server, routeContext, dispatcher);
registerPipelineRoutes(server, routeContext);
registerPluginRoutes(server, routeContext);
registerMetadataRoutes(server, routeContext);
registerProfileRoutes(server, routeContext);
server->setMissingHandler(routeContext, [](const QHttpServerRequest&, QHttpServerResponder& responder) { server->setMissingHandler(routeContext, [](const QHttpServerRequest&, QHttpServerResponder& responder) {
QHttpServerResponse resp(QJsonObject{{QStringLiteral("error"), QStringLiteral("not_found")}}, QHttpServerResponse resp(QJsonObject{{QStringLiteral("error"), QStringLiteral("not_found")}},
QHttpServerResponse::StatusCode::NotFound); QHttpServerResponse::StatusCode::NotFound);
responder.sendResponse(std::move(resp)); responder.sendResponse(withCorsHeaders(std::move(resp)));
}); });
} }

View File

@@ -1,8 +1,17 @@
#include "api/ipc_dbus/CommandDispatcher.h" #include "api/ipc_dbus/CommandDispatcher.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QSaveFile>
#include "device_bus/DeviceBus.h"
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h" #include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
#include "message_bus/pipeline/discovery/DiscoveryPool.h" #include "message_bus/pipeline/discovery/DiscoveryPool.h"
#include "message_bus/pipeline/mapping/MappingRegistry.h" #include "message_bus/pipeline/mapping/MappingRegistry.h"
#include "utils/logs/logging.h"
namespace softbus::api::ipc namespace softbus::api::ipc
{ {
@@ -10,6 +19,16 @@ namespace softbus::api::ipc
namespace namespace
{ {
QString rulesConfigPath()
{
const QString daemonConfigPath = softbus::device_bus::DeviceBus::instance().configPath();
if (!daemonConfigPath.isEmpty()) {
const QFileInfo fi(daemonConfigPath);
return fi.dir().filePath(QStringLiteral("runtime_rules.json"));
}
return QDir::homePath() + QStringLiteral("/softbus/config/runtime_rules.json");
}
softbus::message_bus::pipeline::RuleScope parseScope(const QJsonObject& params) softbus::message_bus::pipeline::RuleScope parseScope(const QJsonObject& params)
{ {
softbus::message_bus::pipeline::RuleScope scope; softbus::message_bus::pipeline::RuleScope scope;
@@ -19,6 +38,89 @@ softbus::message_bus::pipeline::RuleScope parseScope(const QJsonObject& params)
return scope; return scope;
} }
QJsonObject scopeToJson(const softbus::message_bus::pipeline::RuleScope& scope)
{
QJsonObject out;
if (scope.endpointHash != 0) {
out.insert(QStringLiteral("endpointHash"), static_cast<qint64>(scope.endpointHash));
}
if (scope.deviceId >= 0) {
out.insert(QStringLiteral("deviceId"), scope.deviceId);
}
if (!scope.stableKey.isEmpty()) {
out.insert(QStringLiteral("stableKey"), scope.stableKey);
}
return out;
}
QJsonObject filterRuleToJson(const softbus::message_bus::pipeline::FilterRule& rule)
{
QJsonObject out;
out.insert(QStringLiteral("id"), rule.id);
out.insert(QStringLiteral("scope"), scopeToJson(rule.scope));
out.insert(QStringLiteral("condition"), rule.condition);
return out;
}
QJsonObject transformRuleToJson(const softbus::message_bus::pipeline::TransformRule& rule)
{
QJsonObject out;
out.insert(QStringLiteral("id"), rule.id);
out.insert(QStringLiteral("scope"), scopeToJson(rule.scope));
out.insert(QStringLiteral("scale"), rule.scale);
out.insert(QStringLiteral("offset"), rule.offset);
if (!rule.unit.isEmpty()) {
out.insert(QStringLiteral("unit"), rule.unit);
}
return out;
}
bool persistRulesToDedicatedConfig(const softbus::message_bus::pipeline::DynamicRuleRegistry& reg)
{
const QString configPath = rulesConfigPath();
if (configPath.isEmpty()) {
LOG_ERROR() << "CommandDispatcher: rules config path is empty, cannot persist rules";
return false;
}
const QFileInfo fi(configPath);
QJsonArray filtersJson;
for (const auto& rule : reg.snapshotAllFilterRules()) {
filtersJson.push_back(filterRuleToJson(rule));
}
QJsonArray transformsJson;
for (const auto& rule : reg.snapshotAllTransformRules()) {
transformsJson.push_back(transformRuleToJson(rule));
}
QJsonObject root;
root.insert(QStringLiteral("filter"), filtersJson);
root.insert(QStringLiteral("transform"), transformsJson);
if (!QDir().mkpath(fi.dir().absolutePath())) {
LOG_ERROR() << "CommandDispatcher: failed to create rules config directory:" << fi.dir().absolutePath();
return false;
}
const QByteArray bytes = QJsonDocument(root).toJson(QJsonDocument::Indented);
QSaveFile out(configPath);
if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
LOG_ERROR() << "CommandDispatcher: failed to open rules config for write:" << configPath;
return false;
}
if (out.write(bytes) != bytes.size()) {
LOG_ERROR() << "CommandDispatcher: failed to write rules config:" << configPath;
out.cancelWriting();
return false;
}
if (!out.commit()) {
LOG_ERROR() << "CommandDispatcher: failed to commit rules config:" << configPath;
return false;
}
LOG_INFO() << "CommandDispatcher: rules persisted to" << configPath;
return true;
}
} // namespace } // namespace
DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObject& params) const DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObject& params) const
@@ -34,11 +136,17 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
if (!reg.addFilterRule(rule)) { if (!reg.addFilterRule(rule)) {
return {4001, QStringLiteral("invalid_filter_rule"), {}}; return {4001, QStringLiteral("invalid_filter_rule"), {}};
} }
if (!persistRulesToDedicatedConfig(reg)) {
return {5001, QStringLiteral("persist_rules_failed"), {}};
}
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), rule.id}}}; return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), rule.id}}};
} }
if (action == QStringLiteral("remove_filter")) { if (action == QStringLiteral("remove_filter")) {
const QString id = params.value(QStringLiteral("id")).toString(); const QString id = params.value(QStringLiteral("id")).toString();
const bool ok = reg.removeFilterRule(id); const bool ok = reg.removeFilterRule(id);
if (ok && !persistRulesToDedicatedConfig(reg)) {
return {5001, QStringLiteral("persist_rules_failed"), {}};
}
return {ok ? 0 : 4041, ok ? QStringLiteral("ok") : QStringLiteral("filter_not_found"), return {ok ? 0 : 4041, ok ? QStringLiteral("ok") : QStringLiteral("filter_not_found"),
QJsonObject{{QStringLiteral("id"), id}}}; QJsonObject{{QStringLiteral("id"), id}}};
} }
@@ -52,11 +160,17 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
if (!reg.addTransformRule(rule)) { if (!reg.addTransformRule(rule)) {
return {4002, QStringLiteral("invalid_transform_rule"), {}}; return {4002, QStringLiteral("invalid_transform_rule"), {}};
} }
if (!persistRulesToDedicatedConfig(reg)) {
return {5001, QStringLiteral("persist_rules_failed"), {}};
}
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), rule.id}}}; return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), rule.id}}};
} }
if (action == QStringLiteral("remove_transform_rule")) { if (action == QStringLiteral("remove_transform_rule")) {
const QString id = params.value(QStringLiteral("id")).toString(); const QString id = params.value(QStringLiteral("id")).toString();
const bool ok = reg.removeTransformRule(id); const bool ok = reg.removeTransformRule(id);
if (ok && !persistRulesToDedicatedConfig(reg)) {
return {5001, QStringLiteral("persist_rules_failed"), {}};
}
return {ok ? 0 : 4042, ok ? QStringLiteral("ok") : QStringLiteral("transform_rule_not_found"), return {ok ? 0 : 4042, ok ? QStringLiteral("ok") : QStringLiteral("transform_rule_not_found"),
QJsonObject{{QStringLiteral("id"), id}}}; QJsonObject{{QStringLiteral("id"), id}}};
} }

View File

@@ -65,6 +65,65 @@ DataType parseDataType(const QString& s)
return DataType::FLOAT64; return DataType::FLOAT64;
} }
QString domainToString(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::HEALTH_DIAG:
return QStringLiteral("HEALTH_DIAG");
case DataDomain::CONFIG_PARAM:
return QStringLiteral("CONFIG_PARAM");
case DataDomain::UNKNOWN:
default:
return QStringLiteral("UNKNOWN");
}
}
QString dataTypeToString(DataType type)
{
switch (type) {
case DataType::BOOL:
return QStringLiteral("BOOL");
case DataType::INT32:
return QStringLiteral("INT32");
case DataType::UINT32:
return QStringLiteral("UINT32");
case DataType::INT64:
return QStringLiteral("INT64");
case DataType::FLOAT32:
return QStringLiteral("FLOAT32");
case DataType::FLOAT64:
return QStringLiteral("FLOAT64");
case DataType::STRING:
return QStringLiteral("STRING");
case DataType::BYTE_ARRAY:
return QStringLiteral("BYTE_ARRAY");
default:
return QStringLiteral("FLOAT64");
}
}
QJsonObject toJsonObject(const softbus::core::models::MetadataDef& def)
{
return QJsonObject{
{QStringLiteral("metadataId"), def.metadataId},
{QStringLiteral("name"), def.name},
{QStringLiteral("description"), def.description},
{QStringLiteral("domain"), domainToString(def.domain)},
{QStringLiteral("semanticType"), dataTypeToString(def.semanticType)},
{QStringLiteral("unit"), def.unit},
{QStringLiteral("minValue"), def.minValue},
{QStringLiteral("maxValue"), def.maxValue},
{QStringLiteral("deadband"), def.deadband},
{QStringLiteral("isWriteable"), def.isWriteable},
};
}
} // namespace } // namespace
bool MetadataRegistry::loadFromFile(const QString& filePath) bool MetadataRegistry::loadFromFile(const QString& filePath)
@@ -114,6 +173,15 @@ const softbus::core::models::MetadataDef* MetadataRegistry::findById(const QStri
return &it.value(); return &it.value();
} }
QJsonArray MetadataRegistry::list() const
{
QJsonArray out;
for (auto it = m_defsById.cbegin(); it != m_defsById.cend(); ++it) {
out.push_back(toJsonObject(it.value()));
}
return out;
}
std::optional<softbus::core::models::MetadataDef> MetadataRegistry::parseOne(const QJsonObject& o) std::optional<softbus::core::models::MetadataDef> MetadataRegistry::parseOne(const QJsonObject& o)
{ {
softbus::core::models::MetadataDef def; softbus::core::models::MetadataDef def;

View File

@@ -3,6 +3,7 @@
#include <optional> #include <optional>
#include <QHash> #include <QHash>
#include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QString> #include <QString>
@@ -17,6 +18,7 @@ public:
bool loadFromFile(const QString& filePath); bool loadFromFile(const QString& filePath);
const softbus::core::models::MetadataDef* findById(const QString& metadataId) const; const softbus::core::models::MetadataDef* findById(const QString& metadataId) const;
QJsonArray list() const;
bool empty() const { return m_defsById.isEmpty(); } bool empty() const { return m_defsById.isEmpty(); }
const QString& lastError() const { return m_lastError; } const QString& lastError() const { return m_lastError; }

View File

@@ -16,6 +16,75 @@ QString normalizedProtocol(const QString& protocol)
return protocol.trimmed().toLower(); return protocol.trimmed().toLower();
} }
QString dataTypeToString(softbus::core::models::DataType type)
{
using softbus::core::models::DataType;
switch (type) {
case DataType::BOOL:
return QStringLiteral("BOOL");
case DataType::INT32:
return QStringLiteral("INT32");
case DataType::UINT32:
return QStringLiteral("UINT32");
case DataType::INT64:
return QStringLiteral("INT64");
case DataType::FLOAT32:
return QStringLiteral("FLOAT32");
case DataType::FLOAT64:
return QStringLiteral("FLOAT64");
case DataType::STRING:
return QStringLiteral("STRING");
case DataType::BYTE_ARRAY:
return QStringLiteral("BYTE_ARRAY");
default:
return QStringLiteral("INT32");
}
}
QString endiannessToString(softbus::core::models::Endianness endianness)
{
using softbus::core::models::Endianness;
switch (endianness) {
case Endianness::LittleEndian:
return QStringLiteral("LITTLE_ENDIAN");
case Endianness::BigEndianByteSwap:
return QStringLiteral("BIG_ENDIAN_BYTE_SWAP");
case Endianness::BigEndian:
default:
return QStringLiteral("BIG_ENDIAN");
}
}
QJsonObject mappingToJsonObject(const softbus::core::models::DataPointMapping& mapping)
{
return QJsonObject{
{QStringLiteral("pointId"), mapping.pointId},
{QStringLiteral("metadataId"), mapping.metadataId},
{QStringLiteral("registerAddress"), static_cast<qint64>(mapping.registerAddress)},
{QStringLiteral("byteOffset"), static_cast<int>(mapping.byteOffset)},
{QStringLiteral("byteLength"), static_cast<int>(mapping.byteLength)},
{QStringLiteral("bitOffset"), static_cast<int>(mapping.bitOffset)},
{QStringLiteral("endianness"), endiannessToString(mapping.endianness)},
{QStringLiteral("rawType"), dataTypeToString(mapping.rawType)},
{QStringLiteral("scaleFactor"), mapping.scaleFactor},
{QStringLiteral("offsetFactor"), mapping.offsetFactor},
{QStringLiteral("expression"), mapping.expression},
};
}
QJsonObject profileToJsonObject(const softbus::core::models::DeviceDataProfile& profile)
{
QJsonArray mappings;
for (const auto& mapping : profile.mappings) {
mappings.push_back(mappingToJsonObject(mapping));
}
return QJsonObject{
{QStringLiteral("deviceId"), profile.deviceId},
{QStringLiteral("protocol"), profile.protocol},
{QStringLiteral("mappings"), mappings},
};
}
} // namespace } // namespace
bool ProfileRegistry::loadFromFile(const QString& filePath) bool ProfileRegistry::loadFromFile(const QString& filePath)
@@ -86,6 +155,26 @@ const softbus::core::models::DeviceDataProfile* ProfileRegistry::find(const QStr
return nullptr; return nullptr;
} }
QJsonArray ProfileRegistry::list() const
{
QJsonArray out;
for (auto it = m_profilesByKey.cbegin(); it != m_profilesByKey.cend(); ++it) {
out.push_back(profileToJsonObject(it.value()));
}
return out;
}
QJsonArray ProfileRegistry::listByDeviceId(const QString& deviceId) const
{
QJsonArray out;
for (auto it = m_profilesByKey.cbegin(); it != m_profilesByKey.cend(); ++it) {
if (it.value().deviceId == deviceId) {
out.push_back(profileToJsonObject(it.value()));
}
}
return out;
}
QString ProfileRegistry::makeKey(const QString& deviceId, const QString& protocol) QString ProfileRegistry::makeKey(const QString& deviceId, const QString& protocol)
{ {
return deviceId + QStringLiteral("::") + protocol; return deviceId + QStringLiteral("::") + protocol;

View File

@@ -3,6 +3,7 @@
#include <optional> #include <optional>
#include <QHash> #include <QHash>
#include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QString> #include <QString>
@@ -17,6 +18,8 @@ public:
bool loadFromFile(const QString& filePath); bool loadFromFile(const QString& filePath);
const softbus::core::models::DeviceDataProfile* find(const QString& deviceId, const softbus::core::models::DeviceDataProfile* find(const QString& deviceId,
const QString& protocol) const; const QString& protocol) const;
QJsonArray list() const;
QJsonArray listByDeviceId(const QString& deviceId) const;
const QString& lastError() const { return m_lastError; } const QString& lastError() const { return m_lastError; }
bool empty() const { return m_profilesByKey.isEmpty(); } bool empty() const { return m_profilesByKey.isEmpty(); }

View File

@@ -3,6 +3,44 @@
namespace softbus::core::plugin_system namespace softbus::core::plugin_system
{ {
namespace
{
QString protocolTypeToString(softbus::core::models::ProtocolType protocol)
{
switch (protocol) {
case softbus::core::models::ProtocolType::MODBUS_RTU:
return QStringLiteral("MODBUS_RTU");
case softbus::core::models::ProtocolType::MODBUS_ASCII:
return QStringLiteral("MODBUS_ASCII");
case softbus::core::models::ProtocolType::MODBUS_TCP:
return QStringLiteral("MODBUS_TCP");
case softbus::core::models::ProtocolType::CAN_RAW:
return QStringLiteral("CAN_RAW");
case softbus::core::models::ProtocolType::CAN_OPEN:
return QStringLiteral("CAN_OPEN");
case softbus::core::models::ProtocolType::UDP_CUSTOM:
return QStringLiteral("UDP_CUSTOM");
case softbus::core::models::ProtocolType::UNKNOWN:
default:
return QStringLiteral("UNKNOWN");
}
}
QJsonObject makePluginObject(const QString& category,
const QString& pluginId,
softbus::core::models::ProtocolType protocol)
{
return QJsonObject{
{QStringLiteral("category"), category},
{QStringLiteral("pluginId"), pluginId},
{QStringLiteral("protocol"), protocolTypeToString(protocol)},
{QStringLiteral("protocolValue"), static_cast<int>(protocol)},
};
}
} // namespace
void PluginManager::registerProtocolPlugin(std::shared_ptr<IProtocolPlugin> plugin) void PluginManager::registerProtocolPlugin(std::shared_ptr<IProtocolPlugin> plugin)
{ {
if (!plugin) { if (!plugin) {
@@ -95,4 +133,93 @@ QStringList PluginManager::registeredProtocolMapperPluginIds() const
return m_protocolMapperPluginsById.keys(); return m_protocolMapperPluginsById.keys();
} }
QJsonArray PluginManager::protocolPluginsSnapshot() const
{
QJsonArray out;
for (auto it = m_protocolPluginsById.cbegin(); it != m_protocolPluginsById.cend(); ++it) {
if (!it.value()) {
continue;
}
out.push_back(makePluginObject(QStringLiteral("parser"), it.value()->pluginId(), it.value()->protocolType()));
}
return out;
}
QJsonArray PluginManager::protocolFramerPluginsSnapshot() const
{
QJsonArray out;
for (auto it = m_protocolFramerPluginsById.cbegin(); it != m_protocolFramerPluginsById.cend(); ++it) {
if (!it.value()) {
continue;
}
out.push_back(makePluginObject(QStringLiteral("framer"), it.value()->pluginId(), it.value()->protocolType()));
}
return out;
}
QJsonArray PluginManager::protocolMapperPluginsSnapshot() const
{
QJsonArray out;
for (auto it = m_protocolMapperPluginsById.cbegin(); it != m_protocolMapperPluginsById.cend(); ++it) {
if (!it.value()) {
continue;
}
out.push_back(makePluginObject(QStringLiteral("mapper"), it.value()->pluginId(), it.value()->protocolType()));
}
return out;
}
QJsonArray PluginManager::protocolCapabilitiesSnapshot() const
{
QHash<softbus::core::models::ProtocolType, QJsonObject> caps;
for (auto it = m_protocolPluginsByType.cbegin(); it != m_protocolPluginsByType.cend(); ++it) {
if (!it.value()) {
continue;
}
auto obj = caps.value(it.key(), QJsonObject{
{QStringLiteral("protocol"), protocolTypeToString(it.key())},
{QStringLiteral("protocolValue"), static_cast<int>(it.key())},
{QStringLiteral("parserPluginId"), QString()},
{QStringLiteral("framerPluginId"), QString()},
{QStringLiteral("mapperPluginId"), QString()},
});
obj.insert(QStringLiteral("parserPluginId"), it.value()->pluginId());
caps.insert(it.key(), obj);
}
for (auto it = m_protocolFramerPluginsByType.cbegin(); it != m_protocolFramerPluginsByType.cend(); ++it) {
if (!it.value()) {
continue;
}
auto obj = caps.value(it.key(), QJsonObject{
{QStringLiteral("protocol"), protocolTypeToString(it.key())},
{QStringLiteral("protocolValue"), static_cast<int>(it.key())},
{QStringLiteral("parserPluginId"), QString()},
{QStringLiteral("framerPluginId"), QString()},
{QStringLiteral("mapperPluginId"), QString()},
});
obj.insert(QStringLiteral("framerPluginId"), it.value()->pluginId());
caps.insert(it.key(), obj);
}
for (auto it = m_protocolMapperPluginsByType.cbegin(); it != m_protocolMapperPluginsByType.cend(); ++it) {
if (!it.value()) {
continue;
}
auto obj = caps.value(it.key(), QJsonObject{
{QStringLiteral("protocol"), protocolTypeToString(it.key())},
{QStringLiteral("protocolValue"), static_cast<int>(it.key())},
{QStringLiteral("parserPluginId"), QString()},
{QStringLiteral("framerPluginId"), QString()},
{QStringLiteral("mapperPluginId"), QString()},
});
obj.insert(QStringLiteral("mapperPluginId"), it.value()->pluginId());
caps.insert(it.key(), obj);
}
QJsonArray out;
for (auto it = caps.cbegin(); it != caps.cend(); ++it) {
out.push_back(it.value());
}
return out;
}
} // namespace softbus::core::plugin_system } // namespace softbus::core::plugin_system

View File

@@ -3,6 +3,7 @@
#include <memory> #include <memory>
#include <QHash> #include <QHash>
#include <QJsonArray>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@@ -32,6 +33,10 @@ public:
QStringList registeredProtocolPluginIds() const; QStringList registeredProtocolPluginIds() const;
QStringList registeredProtocolFramerPluginIds() const; QStringList registeredProtocolFramerPluginIds() const;
QStringList registeredProtocolMapperPluginIds() const; QStringList registeredProtocolMapperPluginIds() const;
QJsonArray protocolPluginsSnapshot() const;
QJsonArray protocolFramerPluginsSnapshot() const;
QJsonArray protocolMapperPluginsSnapshot() const;
QJsonArray protocolCapabilitiesSnapshot() const;
private: private:
QHash<QString, std::shared_ptr<IProtocolPlugin>> m_protocolPluginsById; QHash<QString, std::shared_ptr<IProtocolPlugin>> m_protocolPluginsById;

View File

@@ -156,6 +156,41 @@ std::shared_ptr<DeviceTreeModel> DeviceBus::deviceTreeModel() const {
return d->treeModel; return d->treeModel;
} }
std::vector<softbus::devices::DeviceInfo> DeviceBus::listDevices() const {
if (!d || !d->manager) {
return {};
}
return d->manager->listDevices();
}
std::optional<softbus::devices::DeviceInfo> DeviceBus::findDeviceById(int id) const {
if (!d || !d->manager) {
return std::nullopt;
}
return d->manager->findDeviceById(id);
}
bool DeviceBus::reconcileFromTreeModel() {
if (!d || !d->manager) {
return false;
}
return d->manager->reconcileFromTreeModel();
}
softbus::message_bus::pipeline::PipelineEngine* DeviceBus::pipelineEngine() const {
if (!d || !d->manager) {
return nullptr;
}
return d->manager->pipelineEngine();
}
std::shared_ptr<softbus::core::plugin_system::PluginManager> DeviceBus::pluginManager() const {
if (!d || !d->manager) {
return {};
}
return d->manager->pluginManager();
}
void DeviceBus::markDeviceTreeDirty() { void DeviceBus::markDeviceTreeDirty() {
if (!d) return; if (!d) return;
d->deviceTreeDirty = true; d->deviceTreeDirty = true;

View File

@@ -3,8 +3,21 @@
#include <QJsonObject> #include <QJsonObject>
#include <QString> #include <QString>
#include <memory> #include <memory>
#include <optional>
#include <vector>
#include "device_bus/tree/DeviceTreeModel.h" #include "device_bus/tree/DeviceTreeModel.h"
#include "devices/DeviceTypes.h"
namespace softbus::core::plugin_system
{
class PluginManager;
}
namespace softbus::message_bus::pipeline
{
class PipelineEngine;
}
namespace softbus::device_bus { namespace softbus::device_bus {
@@ -22,6 +35,11 @@ public:
// 共享设备树模型(单一真源) // 共享设备树模型(单一真源)
std::shared_ptr<DeviceTreeModel> deviceTreeModel() const; std::shared_ptr<DeviceTreeModel> deviceTreeModel() const;
std::vector<softbus::devices::DeviceInfo> listDevices() const;
std::optional<softbus::devices::DeviceInfo> findDeviceById(int id) const;
bool reconcileFromTreeModel();
softbus::message_bus::pipeline::PipelineEngine* pipelineEngine() const;
std::shared_ptr<softbus::core::plugin_system::PluginManager> pluginManager() const;
// 注册/更新设备树后调用,确保 shutdown 会落盘 // 注册/更新设备树后调用,确保 shutdown 会落盘
void markDeviceTreeDirty(); void markDeviceTreeDirty();

View File

@@ -3,18 +3,128 @@
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument>
#include <QSaveFile>
#include <QSet> #include <QSet>
// #include <algorithm> // #include <algorithm>
#include "device_bus/DeviceBus.h"
#include "message_bus/egress/EgressRouter.h" #include "message_bus/egress/EgressRouter.h"
#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 "message_bus/pipeline/rules/DynamicRuleRegistry.h"
#include "core/plugin_system/PluginManager.h" #include "core/plugin_system/PluginManager.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"
namespace
{
softbus::message_bus::pipeline::RuleScope parseRuleScope(const QJsonObject& obj)
{
softbus::message_bus::pipeline::RuleScope scope;
scope.endpointHash = static_cast<std::uint32_t>(obj.value(QStringLiteral("endpointHash")).toInteger(0));
scope.deviceId = obj.value(QStringLiteral("deviceId")).toInt(-1);
scope.stableKey = obj.value(QStringLiteral("stableKey")).toString();
return scope;
}
QString rulesConfigPath()
{
const QString daemonConfigPath = softbus::device_bus::DeviceBus::instance().configPath();
if (!daemonConfigPath.isEmpty()) {
const QFileInfo fi(daemonConfigPath);
return fi.dir().filePath(QStringLiteral("runtime_rules.json"));
}
return QDir::homePath() + QStringLiteral("/softbus/config/runtime_rules.json");
}
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;
}
QJsonParseError pe;
const QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &pe);
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
LOG_WARNING() << "DeviceBusManager: invalid rules config json:" << path << "err=" << pe.errorString();
return;
}
const QJsonObject rulesObj = doc.object();
const QJsonArray filterRules = rulesObj.value(QStringLiteral("filter")).toArray();
const QJsonArray transformRules = rulesObj.value(QStringLiteral("transform")).toArray();
int loadedFilterCount = 0;
for (const auto& item : filterRules) {
const QJsonObject obj = item.toObject();
softbus::message_bus::pipeline::FilterRule rule;
rule.id = obj.value(QStringLiteral("id")).toString();
rule.scope = parseRuleScope(obj.value(QStringLiteral("scope")).toObject());
rule.condition = obj.value(QStringLiteral("condition")).toObject();
if (!rule.id.isEmpty()) {
(void)reg.addFilterRule(rule);
++loadedFilterCount;
}
}
int loadedTransformCount = 0;
for (const auto& item : transformRules) {
const QJsonObject obj = item.toObject();
softbus::message_bus::pipeline::TransformRule rule;
rule.id = obj.value(QStringLiteral("id")).toString();
rule.scope = parseRuleScope(obj.value(QStringLiteral("scope")).toObject());
rule.scale = obj.value(QStringLiteral("scale")).toDouble(1.0);
rule.offset = obj.value(QStringLiteral("offset")).toDouble(0.0);
rule.unit = obj.value(QStringLiteral("unit")).toString();
if (!rule.id.isEmpty()) {
(void)reg.addTransformRule(rule);
++loadedTransformCount;
}
}
LOG_INFO() << "DeviceBusManager: loaded dynamic rules from" << path
<< "filter=" << loadedFilterCount
<< "transform=" << loadedTransformCount;
}
} // namespace
DeviceBusManager::~DeviceBusManager() DeviceBusManager::~DeviceBusManager()
{ {
shutdown(); shutdown();
@@ -97,6 +207,7 @@ bool DeviceBusManager::initialize(const QJsonObject& rootConfig)
m_pipelineEngine->setMemoryPool(m_memoryPool); m_pipelineEngine->setMemoryPool(m_memoryPool);
m_pipelineEngine->loadRuntimeConfigFromJson(rootConfig.value(QStringLiteral("pipeline")).toObject()); m_pipelineEngine->loadRuntimeConfigFromJson(rootConfig.value(QStringLiteral("pipeline")).toObject());
m_pipelineEngine->setPluginManager(m_pluginManager); m_pipelineEngine->setPluginManager(m_pluginManager);
loadDynamicRulesFromDedicatedConfig();
m_pipelineEngine->start(); m_pipelineEngine->start();
} }
if (!m_ingressAdapter) { if (!m_ingressAdapter) {
@@ -198,6 +309,32 @@ void DeviceBusManager::shutdown()
} }
} }
std::vector<softbus::devices::DeviceInfo> DeviceBusManager::listDevices() const
{
if (!m_registry) {
return {};
}
return m_registry->listDevices();
}
std::optional<softbus::devices::DeviceInfo> DeviceBusManager::findDeviceById(int id) const
{
if (!m_registry) {
return std::nullopt;
}
return m_registry->findById(id);
}
softbus::message_bus::pipeline::PipelineEngine* DeviceBusManager::pipelineEngine() const
{
return m_pipelineEngine.get();
}
std::shared_ptr<softbus::core::plugin_system::PluginManager> DeviceBusManager::pluginManager() const
{
return m_pluginManager;
}
void DeviceBusManager::initializeSerialDevice(const QString& endpoint, const QJsonObject& params) void DeviceBusManager::initializeSerialDevice(const QString& endpoint, const QJsonObject& params)
{ {
softbus::device_bus::manager::SerialInitDeps deps; softbus::device_bus::manager::SerialInitDeps deps;

View File

@@ -34,6 +34,10 @@ public:
bool initialize(const QJsonObject& rootConfig); bool initialize(const QJsonObject& rootConfig);
bool reconcileFromTreeModel(); bool reconcileFromTreeModel();
void shutdown(); void shutdown();
std::vector<softbus::devices::DeviceInfo> listDevices() const;
std::optional<softbus::devices::DeviceInfo> findDeviceById(int id) const;
softbus::message_bus::pipeline::PipelineEngine* pipelineEngine() const;
std::shared_ptr<softbus::core::plugin_system::PluginManager> pluginManager() const;
private: private:
void initializeSerialDevice(const QString& endpoint, const QJsonObject& params); void initializeSerialDevice(const QString& endpoint, const QJsonObject& params);

View File

@@ -23,6 +23,29 @@ enum class DeviceKind {
Distributed, Distributed,
}; };
inline QString deviceKindToString(DeviceKind kind)
{
switch (kind) {
case DeviceKind::Serial:
return QStringLiteral("serial");
case DeviceKind::Can:
return QStringLiteral("can");
case DeviceKind::Tcp:
return QStringLiteral("tcp");
case DeviceKind::Udp:
return QStringLiteral("udp");
case DeviceKind::Network:
return QStringLiteral("network");
case DeviceKind::Virtual:
return QStringLiteral("virtual");
case DeviceKind::Distributed:
return QStringLiteral("distributed");
case DeviceKind::Unknown:
default:
return QStringLiteral("unknown");
}
}
struct DeviceInfo struct DeviceInfo
{ {
int id{0}; int id{0};
@@ -38,7 +61,7 @@ struct DeviceInfo
bool isActive{true}; bool isActive{true};
QDateTime lastSeen; QDateTime lastSeen;
QString toJson() const QJsonObject toJsonObject() const
{ {
QJsonObject obj; QJsonObject obj;
obj[QStringLiteral("id")] = id; obj[QStringLiteral("id")] = id;
@@ -46,6 +69,7 @@ struct DeviceInfo
obj[QStringLiteral("name")] = name; obj[QStringLiteral("name")] = name;
obj[QStringLiteral("type")] = type; obj[QStringLiteral("type")] = type;
obj[QStringLiteral("kind")] = static_cast<int>(kind); obj[QStringLiteral("kind")] = static_cast<int>(kind);
obj[QStringLiteral("kindName")] = deviceKindToString(kind);
obj[QStringLiteral("address")] = address; obj[QStringLiteral("address")] = address;
obj[QStringLiteral("protocol")] = protocol; obj[QStringLiteral("protocol")] = protocol;
obj[QStringLiteral("protocol_detail")] = protocolDetail; obj[QStringLiteral("protocol_detail")] = protocolDetail;
@@ -55,7 +79,12 @@ struct DeviceInfo
if (lastSeen.isValid()) { if (lastSeen.isValid()) {
obj[QStringLiteral("lastSeen")] = lastSeen.toString(Qt::ISODate); obj[QStringLiteral("lastSeen")] = lastSeen.toString(Qt::ISODate);
} }
const QJsonDocument doc(obj); return obj;
}
QString toJson() const
{
const QJsonDocument doc(toJsonObject());
return QString::fromUtf8(doc.toJson(QJsonDocument::Compact)); return QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
} }
}; };

View File

@@ -5,7 +5,9 @@
#include <sstream> #include <sstream>
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include "core/models/MessageRoutingUtils.h" #include "core/models/MessageRoutingUtils.h"
#include "message_bus/pipeline/PipelineEngineApi.h"
#include "message_bus/pipeline/discovery/DiscoveryPool.h" #include "message_bus/pipeline/discovery/DiscoveryPool.h"
#include "message_bus/pipeline/mapping/MappingRegistry.h" #include "message_bus/pipeline/mapping/MappingRegistry.h"
#include "message_bus/pipeline/stages/3_filters/ProtocolParseFilter.h" #include "message_bus/pipeline/stages/3_filters/ProtocolParseFilter.h"
@@ -287,6 +289,82 @@ void PipelineEngine::setPluginManager(std::shared_ptr<softbus::core::plugin_syst
m_pluginManager = std::move(pluginManager); m_pluginManager = std::move(pluginManager);
} }
void PipelineEngine::resetCounters()
{
resetPipelineCounters(m_counters);
}
QJsonObject PipelineEngine::countersJson() const
{
QJsonObject out;
out.insert(QStringLiteral("in"), static_cast<qint64>(m_counters.in.load(std::memory_order_relaxed)));
out.insert(QStringLiteral("out"), static_cast<qint64>(m_counters.out.load(std::memory_order_relaxed)));
out.insert(QStringLiteral("drop"), static_cast<qint64>(m_counters.drop.load(std::memory_order_relaxed)));
out.insert(QStringLiteral("error"), static_cast<qint64>(m_counters.error.load(std::memory_order_relaxed)));
out.insert(QStringLiteral("mergeDrop"), static_cast<qint64>(m_counters.mergeDrop.load(std::memory_order_relaxed)));
return out;
}
QJsonObject PipelineEngine::runtimeConfigJson() const
{
QJsonObject out;
out.insert(QStringLiteral("parallelPipeline"), m_rtConfig.parallelPipeline);
out.insert(QStringLiteral("parseWorkerCount"), m_rtConfig.parseWorkerCount);
out.insert(QStringLiteral("orderedEgress"), m_rtConfig.orderedEgress);
out.insert(QStringLiteral("framedQueueMax"), static_cast<qint64>(m_rtConfig.framedQueueMax));
out.insert(QStringLiteral("maxReorderDepth"), static_cast<qint64>(m_rtConfig.maxReorderDepth));
return out;
}
QJsonObject PipelineEngine::status() const
{
int framerSessionCount = 0;
int parserSessionCount = 0;
int mapperSessionCount = 0;
int endpointFramerCount = 0;
int trackedEndpointCount = 0;
{
std::lock_guard<std::mutex> lk(m_framerSessionMutex);
framerSessionCount = m_framerSessions.size();
}
{
std::lock_guard<std::mutex> lk(m_parserSessionMutex);
parserSessionCount = m_parserSessions.size();
}
{
std::lock_guard<std::mutex> lk(m_mapperSessionMutex);
mapperSessionCount = m_mapperSessions.size();
}
{
std::lock_guard<std::mutex> lk(m_endpointMutex);
endpointFramerCount = m_endpointFramers.size();
}
{
std::lock_guard<std::mutex> lk(m_seqMutex);
trackedEndpointCount = m_nextFrameSeqByEndpoint.size();
}
QJsonObject out;
out.insert(QStringLiteral("running"), m_running.load(std::memory_order_acquire));
out.insert(QStringLiteral("runtimeConfig"), runtimeConfigJson());
out.insert(QStringLiteral("counters"), countersJson());
out.insert(QStringLiteral("parseWorkerCount"), static_cast<qint64>(m_parseThreads.size()));
out.insert(QStringLiteral("framerSessionCount"), framerSessionCount);
out.insert(QStringLiteral("parserSessionCount"), parserSessionCount);
out.insert(QStringLiteral("mapperSessionCount"), mapperSessionCount);
out.insert(QStringLiteral("endpointFramerCount"), endpointFramerCount);
out.insert(QStringLiteral("trackedEndpointCount"), trackedEndpointCount);
out.insert(QStringLiteral("ingressQueueCapacity"), static_cast<qint64>(m_ingressQ.capacity()));
out.insert(QStringLiteral("egressQueueCapacity"), static_cast<qint64>(m_egressQ.capacity()));
out.insert(QStringLiteral("hasPluginManager"), m_pluginManager != nullptr);
if (m_pluginManager) {
out.insert(QStringLiteral("parserPlugins"), QJsonArray::fromStringList(m_pluginManager->registeredProtocolPluginIds()));
out.insert(QStringLiteral("framerPlugins"), QJsonArray::fromStringList(m_pluginManager->registeredProtocolFramerPluginIds()));
out.insert(QStringLiteral("mapperPlugins"), QJsonArray::fromStringList(m_pluginManager->registeredProtocolMapperPluginIds()));
}
return out;
}
// 功能 :确保端点帧解析器 // 功能 :确保端点帧解析器
// 参数 endpoint 端点 // 参数 endpoint 端点
// 返回值 :无 // 返回值 :无
@@ -494,7 +572,18 @@ bool PipelineEngine::runStages234(const softbus::core::models::RawBusMessage& ct
if (!stage4Validator(msg)) { if (!stage4Validator(msg)) {
continue; continue;
} }
(void)applyTransformAndEnrich(ctx, msg); const auto enriched = applyTransformAndEnrich(ctx, msg);
const QString engineeringValueText = std::visit(
[](const auto& x) { return QString::number(static_cast<double>(x), 'g', 12); },
enriched.engineeringValue);
LOG_INFO() << "PipelineEngine: enriched message"
<< "sourceMessageId=" << enriched.sourceMessageId
<< "logicalName=" << enriched.logicalName
<< "engineeringValue=" << engineeringValueText
<< "unit=" << enriched.unit
<< "quality=" << static_cast<int>(enriched.quality)
<< "alarm=" << enriched.alarm
<< "traceCount=" << static_cast<int>(enriched.traceLog.size());
} }
return true; return true;
} }
@@ -521,6 +610,7 @@ std::vector<softbus::core::models::DOMMessage> PipelineEngine::stage2Parser(cons
if (!parserPlugin) { if (!parserPlugin) {
return {}; return {};
} }
// 创建解析器会话
const SessionKey parserKey{ctx.endpointHash, parserPlugin->pluginId()}; const SessionKey parserKey{ctx.endpointHash, parserPlugin->pluginId()};
std::shared_ptr<softbus::core::plugin_system::IProtocolSession> parserSession; std::shared_ptr<softbus::core::plugin_system::IProtocolSession> parserSession;
{ {

View File

@@ -83,6 +83,10 @@ public:
void loadRuntimeConfigFromJson(const QJsonObject& obj); void loadRuntimeConfigFromJson(const QJsonObject& obj);
const Counters& counters() const { return m_counters; } const Counters& counters() const { return m_counters; }
void resetCounters();
QJsonObject countersJson() const;
QJsonObject runtimeConfigJson() const;
QJsonObject status() const;
private: private:
struct MergeItem struct MergeItem
@@ -129,16 +133,16 @@ private:
PipelineRuntimeConfig m_rtConfig; PipelineRuntimeConfig m_rtConfig;
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; mutable std::mutex m_framerSessionMutex;
QHash<SessionKey, std::shared_ptr<softbus::core::plugin_system::IProtocolFramerSession>> m_framerSessions; QHash<SessionKey, std::shared_ptr<softbus::core::plugin_system::IProtocolFramerSession>> m_framerSessions;
std::mutex m_parserSessionMutex; mutable std::mutex m_parserSessionMutex;
QHash<SessionKey, std::shared_ptr<softbus::core::plugin_system::IProtocolSession>> m_parserSessions; QHash<SessionKey, std::shared_ptr<softbus::core::plugin_system::IProtocolSession>> m_parserSessions;
std::mutex m_mapperSessionMutex; mutable std::mutex m_mapperSessionMutex;
QHash<SessionKey, std::shared_ptr<softbus::core::plugin_system::IProtocolMapperSession>> m_mapperSessions; QHash<SessionKey, std::shared_ptr<softbus::core::plugin_system::IProtocolMapperSession>> m_mapperSessions;
std::unique_ptr<softbus::core::plugin_system::IMappingLookup> m_mappingLookup; std::unique_ptr<softbus::core::plugin_system::IMappingLookup> m_mappingLookup;
std::unique_ptr<softbus::core::plugin_system::IDiscoverySink> m_discoverySink; std::unique_ptr<softbus::core::plugin_system::IDiscoverySink> m_discoverySink;
std::mutex m_seqMutex; mutable std::mutex m_seqMutex;
QHash<std::uint32_t, std::uint64_t> m_nextFrameSeqByEndpoint; QHash<std::uint32_t, std::uint64_t> m_nextFrameSeqByEndpoint;
std::unique_ptr<softbus::core::threading::MutexQueue<MergeItem>> m_framedQ; std::unique_ptr<softbus::core::threading::MutexQueue<MergeItem>> m_framedQ;
@@ -146,7 +150,7 @@ private:
std::vector<std::thread> m_parseThreads; std::vector<std::thread> m_parseThreads;
std::thread m_mergerThread; std::thread m_mergerThread;
std::mutex m_endpointMutex; mutable std::mutex m_endpointMutex;
QHash<std::uint32_t, std::shared_ptr<EndpointFramingState>> m_endpointFramers; QHash<std::uint32_t, std::shared_ptr<EndpointFramingState>> m_endpointFramers;
std::mutex m_mergeStateMutex; std::mutex m_mergeStateMutex;

View File

@@ -0,0 +1,15 @@
#include "message_bus/pipeline/PipelineEngineApi.h"
namespace softbus::message_bus::pipeline
{
void resetPipelineCounters(PipelineEngine::Counters& counters)
{
counters.in.store(0, std::memory_order_relaxed);
counters.out.store(0, std::memory_order_relaxed);
counters.drop.store(0, std::memory_order_relaxed);
counters.error.store(0, std::memory_order_relaxed);
counters.mergeDrop.store(0, std::memory_order_relaxed);
}
} // namespace softbus::message_bus::pipeline

View File

@@ -0,0 +1,10 @@
#pragma once
#include "message_bus/pipeline/PipelineEngine.h"
namespace softbus::message_bus::pipeline
{
void resetPipelineCounters(PipelineEngine::Counters& counters);
} // namespace softbus::message_bus::pipeline

View File

@@ -6,6 +6,32 @@
namespace softbus::message_bus::pipeline namespace softbus::message_bus::pipeline
{ {
namespace
{
// 规则里可能写完整 routingKey 的十进制串(与 routingKeyToStableKey 一致),也可能写 UI 侧常用的
// "ep_<十进制 endpointHash>"(仅按物理端点匹配,不含 deviceId 位)。
bool stableKeyMatches(const QString& pattern,
const QString& messageStableKey,
std::uint32_t endpointHash)
{
if (pattern.isEmpty()) {
return true;
}
if (pattern == messageStableKey) {
return true;
}
if (pattern.startsWith(QStringLiteral("ep_"), Qt::CaseInsensitive)) {
bool ok = false;
const QString digits = pattern.mid(3);
const quint32 parsed = digits.toUInt(&ok, 10);
return ok && parsed == endpointHash;
}
return false;
}
} // namespace
DynamicRuleRegistry& DynamicRuleRegistry::instance() DynamicRuleRegistry& DynamicRuleRegistry::instance()
{ {
static DynamicRuleRegistry registry; static DynamicRuleRegistry registry;
@@ -72,6 +98,28 @@ std::vector<TransformRule> DynamicRuleRegistry::snapshotTransformRules(
return out; return out;
} }
std::vector<FilterRule> DynamicRuleRegistry::snapshotAllFilterRules() 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) {
out.push_back(it.value());
}
return out;
}
std::vector<TransformRule> DynamicRuleRegistry::snapshotAllTransformRules() 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) {
out.push_back(it.value());
}
return out;
}
QJsonObject DynamicRuleRegistry::status() const QJsonObject DynamicRuleRegistry::status() const
{ {
std::shared_lock<std::shared_mutex> lk(m_mtx); std::shared_lock<std::shared_mutex> lk(m_mtx);
@@ -86,7 +134,7 @@ bool DynamicRuleRegistry::matchScope(
{ {
const bool endpointHashOk = (scope.endpointHash == 0) || (scope.endpointHash == endpointHash); const bool endpointHashOk = (scope.endpointHash == 0) || (scope.endpointHash == endpointHash);
const bool deviceOk = scope.deviceId < 0 || scope.deviceId == deviceId; const bool deviceOk = scope.deviceId < 0 || scope.deviceId == deviceId;
const bool stableKeyOk = scope.stableKey.isEmpty() || scope.stableKey == stableKey; const bool stableKeyOk = stableKeyMatches(scope.stableKey, stableKey, endpointHash);
return endpointHashOk && deviceOk && stableKeyOk; return endpointHashOk && deviceOk && stableKeyOk;
} }

View File

@@ -46,6 +46,8 @@ public:
std::vector<FilterRule> snapshotFilterRules(std::uint32_t endpointHash, int deviceId, const QString& stableKey) const; std::vector<FilterRule> snapshotFilterRules(std::uint32_t endpointHash, int deviceId, const QString& stableKey) const;
std::vector<TransformRule> snapshotTransformRules(std::uint32_t endpointHash, int deviceId, const QString& stableKey) const; std::vector<TransformRule> snapshotTransformRules(std::uint32_t endpointHash, int deviceId, const QString& stableKey) const;
std::vector<FilterRule> snapshotAllFilterRules() const;
std::vector<TransformRule> snapshotAllTransformRules() const;
QJsonObject status() const; QJsonObject status() const;