11 KiB
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(若仓库路径不同请自行调整相对链接)。
构建
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 路径无效,进程会使用内置默认值合并/回退,并在日志中提示。
仍可通过命令行指定路径覆盖默认文件位置:
./build/bin/softbus_edge_agent /path/to/edge_manifest.json
见 config/edge_manifest.schema.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_heartbeatJSON(含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 路径上启用:
- Framer:优先使用插件内的
IProtocolFramerSession(如 Modbus RTU 粘包);否则退回 PassthroughFramer(每块 read 视为一帧,与未启用插件时行为一致)。 - 分帧线程与有界队列:与 daemon
EndpointFramingState对齐——按endpointHash各起一条消费线程,串口读回调仅tryEnqueue到 SPSCLockFreeQueue(容量见 manifestedgeSerialChunkQueueCapacity,默认4096;edgeUseEndpointFramerThreads默认true,false时在同线程同步分帧供调试)。队列满时上报edge stream chunk queue full。 - Parser + Mapper:与核心
PipelineEngine::stage2Parser一致;mapper 在边缘使用 sniffingEnabled=true 且无mappingLookup,故无映射规则时仍会产出带unmapped标记的 DOM 样本(仅 日志EdgeStreamPipeline: parsed DOM ...,不上送 DOM 到核心)。 - 上行:仍只对 完整帧 发送
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_PROPOSEJSON(含edgeId、baseTreeRevision、entries),将原样经 TCP 发往核心。
示例:
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 时排查
opsListenPort是否为 0:为 0 则完全不监听;示例见config/edge_manifest.example.json(如9080)。- 是否用错地址:默认只能
http://127.0.0.1:<端口>/ui在边缘本机打开;在别的电脑浏览器里填边缘的127.0.0.1是无效的,需opsListenAllInterfaces: true后用http://<边缘局域网IP>:<端口>/ui,或 SSH 端口转发。 - 看进程日志:启动成功会打印
ops UI http://127.0.0.1:...或http://0.0.0.0:...;若出现OpsUiHttp: listen failed,多为端口被占用。 - 核心连不上:自本版本起,只要
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(0~500,默认 0):读字节先入驱动内缓冲,静音该毫秒数后合并再交给上层;启用流式 pipeline 时主路径为 每 endpoint 分帧线程 + 有界队列,一般保持 0 即可,仅在仍需驱动侧攒包时再调大。可选 serialReadBufferCapBytes 限制缓冲上限。
发现 → 注册流程(简要)
- 边缘连接核心并 HELLO;核心下发
TREE_FULL,本地落盘并 reconcile 已有节点。 edgeDiscoveryEnabled为真时,EdgeDiscoveryCoordinator扫描/监听 udev 串口 add/remove:若已拿到核心baseTreeRevision则发送TREE_PROPOSE;若无核心(或 HELLO 未成功)则把op: upsert/patch应用到本地DeviceTreeModel并落盘local_tree.json,再 reconcile 串口。核心恢复后仍通过TREE_PROPOSE与TREE_FULL与真源对齐。- 核心
applyEdgePropose成功后再次TREE_FULL;边缘 reload 树文件后对 Serial/CAN 执行 reconcile,raw 仅上行。