Files
soft_bus_edge/README.md
flower_linux 5e5e608dad first commit
2026-05-19 16:38:09 +08:00

106 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# softbus_edge_agent
独立边缘进程:连接核心 `softbus_daemon`**edgeIngress** TCP 端口,发送 **HELLO**、**DEBUG_TUNNEL**(含周期性边缘心跳)、**DATA_RAWBUS**(真实总线数据),以及通过极简 **本地 HTTP**`GET /``GET /ui``POST /propose` 等)触发 **TREE_PROPOSE** 反向同步。
## 目录布局(与 `softbus_daemon` 的 `src/` 对齐)
| 路径 | 说明 |
|------|------|
| `src/device_bus/tree/` | 与核心同源的 `DeviceTreeModel`(本地子树、`TREE_FULL` 落盘加载) |
| `src/device_bus/monitor/` | **udev 串口发现**`EdgeDiscoveryCoordinator`(防抖后:有核心则 `TREE_PROPOSE`,无核心则写本地树并 reconcile |
| `src/device_bus/manager/` | `SerialDeviceManager``CanDeviceManager`;串口可选 **`EdgeStreamPipeline`**daemon 同源 `PluginHost` + framer/parser/mapper |
| `src/device_bus/EdgeDeviceBus.*` | 边缘总线编排:加载本地树、**reconcile** 串口与 **SocketCAN** 实例 |
| `src/devices/``src/hardware/` | 与核心同源的 `SerialDevice``SerialQtDriver` 等 |
| `src/message_bus/ingress/` | `EdgeSession``EdgeUplinkWire``EdgeTcpUplinkPort``RawBusMessage` → SBUP 上行) |
协议与核心侧文档对齐:
- 核心权威说明:[softbus_daemon/docs/EDGE_UPLINK_PROTOCOL_v1.md](../softbus_daemon/docs/EDGE_UPLINK_PROTOCOL_v1.md)(若仓库路径不同请自行调整相对链接)。
## 构建
```bash
cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x/gcc_64
cmake --build build
# 无参:使用 $HOME/softbus/edge/edge_manifest.json首次启动会自动创建目录并生成默认 manifest 与 local_tree.json
./build/bin/softbus_edge_agent
# 或显式指定 manifest例如仓库内示例示例中 localTreePath 使用 $HOME/ 前缀便于各用户通用):
./build/bin/softbus_edge_agent config/edge_manifest.example.json
```
依赖 **Qt6 Core、Network、SerialPort**;若 Qt 安装包含 **SerialBus** 模块CMake 会自动检测并链接,从而在边缘启用 **SocketCAN reconcile**(设备树 `can:...` 节点。未安装时仍可完整构建CAN 节点将被忽略。
## 配置 `edge_manifest.json`(可选)
无命令行参数时,默认 manifest 为 **`$HOME/softbus/edge/edge_manifest.json`**(启动前会 `mkpath` 该目录;若缺少 `edge_manifest.json` / `local_tree.json` 则写入首次启动模板)。若该文件仍无法打开、或 JSON 非法、或你传入的其它 manifest 路径无效,进程会使用**内置默认值**合并/回退,并在日志中提示。
仍可通过命令行指定路径覆盖默认文件位置:
```text
./build/bin/softbus_edge_agent /path/to/edge_manifest.json
```
见 [config/edge_manifest.schema.json](config/edge_manifest.schema.json) 与示例 [config/edge_manifest.example.json](config/edge_manifest.example.json)。
要点:
- **`coreHost` / `corePort`**:与 `daemon_config.json``edgeIngress` 一致。核心**晚于**边缘启动时,进程会**自动每约 3 秒重试 TCP**,连上后自动发送 **HELLO**`currentTreeRevision` 为「核心侧已知 revision」与「本地设备树 revision」的**较大值**),之后 `DATA_RAWBUS` / `TREE_PROPOSE` 才会真正发到核心。
- **`edgeId`**HELLO 与会话绑定;`TREE_PROPOSE` 必须相同。
- **`edgeHeartbeatIntervalMs`**int默认 `5000``0` 关闭):已连接核心 TCP 时,周期性经 **`DEBUG_TUNNEL`** 上送 **`kind: edge_heartbeat`** JSON**`treeRevision`**、**`nodeCount`**、**`treeFingerprint`**(设备树 `tree` 紧凑 JSON 的 `qHash`)、**`coreTreeRevisionKnown`**、时间戳),核心在 `EdgeTcpIngressService` 中打日志便于对账。未设置时可回退读取旧字段 **`fakeIntervalMs`**。
- **`edgeDiscoveryEnabled`**bool默认 `true`):启动 udev 串口发现。已连接核心且收到有效 **`treeRevision`** 后,将串口以 `op: upsert`**`TREE_PROPOSE`** 同步到核心;**核心不可达或 HELLO 失败**时仍运行发现,并把结果写入**本地** `localTreePath`(与核心上线后的双向同步兼容)。设为 `false` 可仅依赖手工 `TREE_PROPOSE` 或核心下发树。需 Linux + `libudev`
- **真实串口**:由设备树节点驱动(`type`/`endpoint` 可识别为 Serial`params.online` 为 true。节点 `params.deviceId` 会写入上行 `RawBusMessage::deviceId`(未设则 `-1`**`endpoint` / `protocol`** 等字段写在设备树节点 `params` 中(与核心约定一致),不再在 manifest 顶层使用假数据字段。
## 协议插件与流式解析(与 daemon Pipeline 对齐)
当 manifest 中 **`plugins.protocols`** 非空时,进程会加载与 `softbus_daemon` **相同 ABI**`.so`(如 `libmodbus_rtu.so`),并在串口 `read` 路径上启用:
1. **Framer**:优先使用插件内的 `IProtocolFramerSession`(如 Modbus RTU 粘包);否则退回 **PassthroughFramer**(每块 read 视为一帧,与未启用插件时行为一致)。
2. **分帧线程与有界队列**:与 daemon `EndpointFramingState` 对齐——按 **`endpointHash`** 各起一条消费线程,串口读回调仅 **`tryEnqueue`** 到 **SPSC `LockFreeQueue`**(容量见 manifest **`edgeSerialChunkQueueCapacity`**,默认 `4096`**`edgeUseEndpointFramerThreads`** 默认 `true``false` 时在同线程同步分帧供调试)。队列满时上报 **`edge stream chunk queue full`**。
3. **Parser + Mapper**:与核心 `PipelineEngine::stage2Parser` 一致mapper 在边缘使用 **sniffingEnabled=true** 且无 `mappingLookup`,故无映射规则时仍会产出带 `unmapped` 标记的 DOM 样本(仅 **日志** `EdgeStreamPipeline: parsed DOM ...`**不上送 DOM 到核心**)。
4. **上行**:仍只对 **完整帧** 发送 **`DATA_RAWBUS`**(与核心解码路径兼容)。
**`plugins.protocols` 路径**:可为绝对路径,或与 manifest 文件同目录的相对路径。请确保边缘运行时能 `dlopen` 到该文件(通常与核心构建产物 `bin/plugins/*.so` 一致)。
## 运维 HTTP
`opsListenPort > 0`,在 TCP 连接核心**之前**即启动监听(便于核心未启动时仍能打开本页排查配置)。默认 **`opsListenAllInterfaces` 为 false**:仅绑定 **`127.0.0.1`**,必须在**运行 edge_agent 的本机**浏览器打开。若需从局域网其他电脑访问,在 manifest 中设 **`"opsListenAllInterfaces": true`**(绑定 `0.0.0.0`,请自行评估内网安全)。
- `GET /`:返回 `localTreePath` 文件内容(无文件则返回占位 HTML含指向配置页的链接
- **`GET /ui`****阶段 5** 简易配置页——从 **`GET /discovered`** 同源列表选择已发现串口节点,填写 `params.protocol`(如 `modbus_rtu`)、波特率等,提交后由边缘组装 **`op: upsert`** 的 `TREE_PROPOSE` 发往核心(使用当前会话的 **`baseTreeRevision`**,须已 HELLO 且收到过 `TREE_RESULT`)。
- **`POST /register_discovered`**:可由浏览器表单(`application/x-www-form-urlencoded`)或 **JSON** 调用;字段含 `nodeId`(必填,一般为 `edges/<edgeId>/discovered/<stableKey>`)、`devnode`(可选,若不在发现列表中需填 `/dev/tty…`)、`protocol``baudRate``name`。表单提交成功返回简短 **HTML** 确认页JSON 请求返回 `{"accepted":true,"sentNodeId":...}`。若尚未拿到有效 `treeRevision`,返回 **503**
- **`GET /discovered`**:返回最近 udev 发现事件 JSON`{ "items": [ ... ] }`),便于无核心时调试;与 `EdgeDiscoveryCoordinator` 内存环缓冲一致(最多约 64 条)。
- `POST /propose`body 为 **完整** `CTRL_TREE_PROPOSE` JSON`edgeId``baseTreeRevision``entries`),将原样经 TCP 发往核心。
示例:
```bash
curl -s http://127.0.0.1:9080/
curl -s http://127.0.0.1:9080/ui
curl -s http://127.0.0.1:9080/discovered
curl -s -X POST http://127.0.0.1:9080/register_discovered \
-H 'Content-Type: application/json' \
-d '{"nodeId":"edges/edge-dev-1/discovered/12345","protocol":"modbus_rtu","baudRate":115200}'
curl -s -X POST http://127.0.0.1:9080/propose \
-H 'Content-Type: application/json' \
-d '{"edgeId":"edge-dev-1","baseTreeRevision":1,"entries":[{"id":"edges/edge-dev-1/gw","patch":{"name":"gw"}}]}'
```
`entries` 中节点 id 必须满足核心 `DeviceTreeModel::nodeBelongsToEdge` 规则。)
### 打不开 `/ui` 时排查
1. **`opsListenPort` 是否为 0**:为 0 则完全不监听;示例见 `config/edge_manifest.example.json`(如 `9080`)。
2. **是否用错地址**:默认只能 **`http://127.0.0.1:<端口>/ui`** 在**边缘本机**打开;在别的电脑浏览器里填边缘的 `127.0.0.1` 是无效的,需 **`opsListenAllInterfaces: true`** 后用 **`http://<边缘局域网IP>:<端口>/ui`**,或 SSH 端口转发。
3. **看进程日志**:启动成功会打印 `ops UI http://127.0.0.1:...``http://0.0.0.0:...`;若出现 `OpsUiHttp: listen failed`,多为端口被占用。
4. **核心连不上**:自本版本起,只要 **`opsListenPort > 0`**,即使核心 TCP/HELLO 失败进程也会继续运行并保留运维 HTTP日志中会提示 `continuing with ops UI only`);此前版本会在启动 Ops 前退出,表现为「完全打不开」。
## 本地树文件
`TREE_FULL` 落盘格式为 `{ "treeRevision", "edgeId", "tree": [ ... ] }`。**`EdgeDeviceBus`** 会将其中的 `tree` 当作 `device_tree` 加载,并对 **Serial****CAN**`endpoint` 形如 `can:vcan0``type`/`endpoint` 推断为 CAN执行 **reconcile**:打开设备后读到的数据经 `EdgeTcpUplinkPort` 编码为 `DATA_RAWBUS` 发往核心。串口节点可在 **`params`** 中设置 **`serialReadCoalesceMs`**0500默认 **0**):读字节先入驱动内缓冲,静音该毫秒数后合并再交给上层;启用流式 pipeline 时主路径为 **每 endpoint 分帧线程 + 有界队列**,一般保持 **0** 即可,仅在仍需驱动侧攒包时再调大。可选 **`serialReadBufferCapBytes`** 限制缓冲上限。
### 发现 → 注册流程(简要)
1. 边缘连接核心并 **HELLO**;核心下发 **`TREE_FULL`**,本地落盘并 reconcile 已有节点。
2. `edgeDiscoveryEnabled` 为真时,`EdgeDiscoveryCoordinator` 扫描/监听 udev 串口 **add/remove**:若已拿到核心 **`baseTreeRevision`** 则发送 **`TREE_PROPOSE`**;若**无核心**(或 HELLO 未成功)则把 **`op: upsert` / `patch`** 应用到**本地** `DeviceTreeModel` 并落盘 `local_tree.json`,再 **reconcile** 串口。核心恢复后仍通过 **`TREE_PROPOSE`** 与 **`TREE_FULL`** 与真源对齐。
3. 核心 `applyEdgePropose` 成功后再次 **`TREE_FULL`**;边缘 reload 树文件后对 **Serial/CAN** 执行 reconcile**raw 仅上行**。