Files
soft_bus_edge/README.md

106 lines
11 KiB
Markdown
Raw Permalink Normal View History

2026-05-13 16:46:07 +08:00
# softbus_edge_agent
2026-05-19 16:38:09 +08:00
独立边缘进程:连接核心 `softbus_daemon`**edgeIngress** TCP 端口,发送 **HELLO**、**DEBUG_TUNNEL**(含周期性边缘心跳)、**DATA_RAWBUS**(真实总线数据),以及通过极简 **本地 HTTP**`GET /``GET /ui``POST /propose` 等)触发 **TREE_PROPOSE** 反向同步。
2026-05-13 16:46:07 +08:00
## 目录布局(与 `softbus_daemon` 的 `src/` 对齐)
| 路径 | 说明 |
|------|------|
| `src/device_bus/tree/` | 与核心同源的 `DeviceTreeModel`(本地子树、`TREE_FULL` 落盘加载) |
2026-05-19 16:38:09 +08:00
| `src/device_bus/monitor/` | **udev 串口发现**`EdgeDiscoveryCoordinator`(防抖后:有核心则 `TREE_PROPOSE`,无核心则写本地树并 reconcile |
| `src/device_bus/manager/` | `SerialDeviceManager``CanDeviceManager`;串口可选 **`EdgeStreamPipeline`**daemon 同源 `PluginHost` + framer/parser/mapper |
2026-05-13 16:46:07 +08:00
| `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
2026-05-19 16:38:09 +08:00
# 无参:使用 $HOME/softbus/edge/edge_manifest.json首次启动会自动创建目录并生成默认 manifest 与 local_tree.json
./build/bin/softbus_edge_agent
# 或显式指定 manifest例如仓库内示例示例中 localTreePath 使用 $HOME/ 前缀便于各用户通用):
2026-05-13 16:46:07 +08:00
./build/bin/softbus_edge_agent config/edge_manifest.example.json
```
依赖 **Qt6 Core、Network、SerialPort**;若 Qt 安装包含 **SerialBus** 模块CMake 会自动检测并链接,从而在边缘启用 **SocketCAN reconcile**(设备树 `can:...` 节点。未安装时仍可完整构建CAN 节点将被忽略。
## 配置 `edge_manifest.json`(可选)
2026-05-19 16:38:09 +08:00
无命令行参数时,默认 manifest 为 **`$HOME/softbus/edge/edge_manifest.json`**(启动前会 `mkpath` 该目录;若缺少 `edge_manifest.json` / `local_tree.json` 则写入首次启动模板)。若该文件仍无法打开、或 JSON 非法、或你传入的其它 manifest 路径无效,进程会使用**内置默认值**合并/回退,并在日志中提示。
2026-05-13 16:46:07 +08:00
仍可通过命令行指定路径覆盖默认文件位置:
```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)。
要点:
2026-05-19 16:38:09 +08:00
- **`coreHost` / `corePort`**:与 `daemon_config.json``edgeIngress` 一致。核心**晚于**边缘启动时,进程会**自动每约 3 秒重试 TCP**,连上后自动发送 **HELLO**`currentTreeRevision` 为「核心侧已知 revision」与「本地设备树 revision」的**较大值**),之后 `DATA_RAWBUS` / `TREE_PROPOSE` 才会真正发到核心。
2026-05-13 16:46:07 +08:00
- **`edgeId`**HELLO 与会话绑定;`TREE_PROPOSE` 必须相同。
2026-05-19 16:38:09 +08:00
- **`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` 一致)。
2026-05-13 16:46:07 +08:00
## 运维 HTTP
2026-05-19 16:38:09 +08:00
`opsListenPort > 0`,在 TCP 连接核心**之前**即启动监听(便于核心未启动时仍能打开本页排查配置)。默认 **`opsListenAllInterfaces` 为 false**:仅绑定 **`127.0.0.1`**,必须在**运行 edge_agent 的本机**浏览器打开。若需从局域网其他电脑访问,在 manifest 中设 **`"opsListenAllInterfaces": true`**(绑定 `0.0.0.0`,请自行评估内网安全)。
2026-05-13 16:46:07 +08:00
- `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` 规则。)
2026-05-19 16:38:09 +08:00
### 打不开 `/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 前退出,表现为「完全打不开」。
2026-05-13 16:46:07 +08:00
## 本地树文件
2026-05-19 16:38:09 +08:00
`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`** 限制缓冲上限。
2026-05-13 16:46:07 +08:00
### 发现 → 注册流程(简要)
1. 边缘连接核心并 **HELLO**;核心下发 **`TREE_FULL`**,本地落盘并 reconcile 已有节点。
2026-05-19 16:38:09 +08:00
2. `edgeDiscoveryEnabled` 为真时,`EdgeDiscoveryCoordinator` 扫描/监听 udev 串口 **add/remove**:若已拿到核心 **`baseTreeRevision`** 则发送 **`TREE_PROPOSE`**;若**无核心**(或 HELLO 未成功)则把 **`op: upsert` / `patch`** 应用到**本地** `DeviceTreeModel` 并落盘 `local_tree.json`,再 **reconcile** 串口。核心恢复后仍通过 **`TREE_PROPOSE`** 与 **`TREE_FULL`** 与真源对齐。
2026-05-13 16:46:07 +08:00
3. 核心 `applyEdgePropose` 成功后再次 **`TREE_FULL`**;边缘 reload 树文件后对 **Serial/CAN** 执行 reconcile**raw 仅上行**。