16 KiB
softbus_edge
路径: src/softbus_edge
构建目标: softbus_edge(EXECUTABLE)
层级: L3 可执行层
1. 概述
softbus_edge 是边缘代理进程,运行在靠近现场设备的一侧,负责:
- 物理域采集(ingress):按
physical_*.json轮询 Modbus / CANopen / 裸以太网设备,产出RawPacket - 上行(egress/uplink):经 TCP 将
RawPacket、心跳、注册信息发送给softbus_daemon - 下行(egress):接收 daemon 下发的
ExecuteCommand,写回物理设备(Modbus 写寄存器等)
edge 不加载逻辑点表、不操作 OPC UA、不运行 protocol_modbus 插件。协议解析与 OPC 绑定均在 daemon 侧完成。
| 模式 | 说明 |
|---|---|
| 正常模式 | CompositeIngress(Modbus + CANopen + Eth),读真实硬件 |
| Mock 模式 | --mock,无硬件,模拟 Modbus 帧上行 |
| 独立模式 | daemon 未启动时 edge 仍可运行,后台重连 uplink |
2. 与 daemon 的职责边界(双域)
┌─────────────────────────────────────────────────────────────────┐
│ edge(物理域) daemon(逻辑域) │
├─────────────────────────────────────────────────────────────────┤
│ config/edge/ config/daemon/ │
│ edge.json daemon_profile.json │
│ physical_channels.json logical_points.json │
│ physical_devices.json bindings_global.json │
│ bindings_local.json(可选) topic_rule.json │
│ │
│ 读硬件 → RawPacket RawPacket → 插件解析 │
│ 写硬件 ← physicalPointId → bindings → OPC UA 节点 │
└─────────────────────────────────────────────────────────────────┘
| 配置 | edge 是否加载 | 用途 |
|---|---|---|
physical_channels.json |
是 | 串口 / TCP 通道参数 |
physical_devices.json |
是 | 从站、寄存器、物理点 ID |
bindings_local.json |
可选 | 下行写命令的本地备用解析(正常联调几乎不用) |
logical_points.json |
否 | daemon 专用 |
bindings_global.json |
否 | daemon 专用(上行绑定 + 下行路由) |
edge 上行时不做 binding 初筛:所有已配置物理点都会采集并上报;是否绑定 OPC 由 daemon 的 bindings_global.json 决定(未绑定点进入 edge/unbound/raw/...)。
3. 配置加载流程
3.1 入口文件
默认配置:config/edge/edge.json。子路径相对 edge.json 所在目录解析。
{
"edge": {
"edgeId": "edge01",
"deviceId": "EdgeDevice_01",
"daemonHost": "127.0.0.1",
"daemonPort": 9000,
"heartbeatPeriodMs": 1000,
"uplinkReconnectPeriodMs": 5000,
"physicalChannels": "physical_channels.json",
"physicalDevices": "physical_devices.json",
"bindingsLocal": "bindings_local.json"
}
}
| 字段 | 说明 |
|---|---|
edgeId |
边缘实例 ID,uplink 注册与 daemon 路由用 |
deviceId |
逻辑设备名(ACK 等场景) |
daemonHost / daemonPort |
核心 uplink 地址 |
heartbeatPeriodMs |
心跳周期(仅 uplink 已连接时发送) |
uplinkReconnectPeriodMs |
daemon 不可达时的重连间隔 |
physicalChannels / physicalDevices |
物理层配置 |
bindingsLocal |
可选;空则跳过 |
bindingsPatch |
可选;热加载绑定补丁 |
3.2 启动时加载顺序(EdgeRuntime::init)
1. EdgeConfig.loadFromFile(--config)
2. PhysicalRegistry.loadChannels(physical_channels.json)
3. PhysicalRegistry.loadDevices(physical_devices.json, edgeId)
→ 物理点 ID 自动加前缀:edge01/phy_modbus_s1_r40001
4. [可选] BindingRegistry.loadLocal(bindings_local.json)
5. UplinkClient + CommandExecutor 初始化
6. DeviceFactory.createIngress → CompositeIngress(按 protocol 创建子 ingress)
7. 各 ChannelManager.openChannel(libmodbus / SocketCAN / asio)
8. uplink.connect();成功则 sendEdgeRegister(edgeId)
→ 失败仅 WARN,edge 独立运行并后台重连
3.3 物理通道(physical_channels.json)
{
"channels": [
{
"channelId": "serial_0",
"protocol": "modbus",
"transport": "rtu",
"port": "/dev/ttyS3",
"baudRate": 9600,
"parity": "N",
"dataBits": 8,
"stopBits": 1,
"pollIntervalMs": 500
}
]
}
| 字段 | 说明 |
|---|---|
transport |
rtu(串口)或 tcp(Modbus TCP,需 host + tcpPort) |
pollIntervalMs |
通道默认轮询周期(点可覆盖) |
interface / bitrate |
CANopen 通道:can0、500000 |
host / tcpPort |
Modbus TCP 或裸以太网 |
CANopen 通道示例见 config/edge/physical_channels_can.json。
3.4 物理设备与点(physical_devices.json)
{
"devices": [
{
"physicalDeviceId": "phy_modbus_slave_1",
"channelId": "serial_0",
"protocol": "modbus",
"slaveId": 1,
"points": [
{
"physicalPointId": "phy_modbus_s1_r40001",
"register": 40001,
"function": 3,
"scale": 0.1,
"signalType": "AI",
"writable": false,
"pollIntervalMs": 1000,
"reportOnChange": true,
"deadband": 0.5
}
]
}
]
}
点级可选字段:
| 字段 | 说明 |
|---|---|
pollIntervalMs |
覆盖通道默认周期 |
reportOnChange |
true 时启用变化上报 |
deadband |
工程值死区(配合 reportOnChange) |
objectIndex / subIndex |
CANopen 对象字典索引 |
pdoType |
tpdo / sdo / rpdo |
cobId |
PDO COB-ID 覆盖 |
nodeId / edsPath |
设备级 CANopen 字段 |
运行时 physicalPointId 会变为 {edgeId}/{physicalPointId},作为 RawPacket.sourceId 上报。
CANopen 示例见 config/edge/physical_devices_can.json 与 config/edge/samples/minimal.eds。
3.5 热加载
| 信号 | 行为 |
|---|---|
SIGHUP |
reloadPhysical():重载 channels + devices,重建 Modbus 连接 |
SIGHUP |
reloadBindingsPatch():若配置了 bindingsPatch 则增量应用 |
| 文件变更 | ConfigWatcher 监视 physical_channels、physical_devices、bindingsPatch |
4. 组件架构
4.0 目录结构
include/softbus_edge/
ingress/ IIngressPort, CompositeIngress
modbus/ ModbusIngress, MockModbusIngress
can/ CanIngress
eth/ EthIngress
driver/
modbus/ ModbusChannelManager (libmodbus)
can/ CanChannelManager (SocketCAN)
eth/ EthChannelManager (asio)
egress/
modbus/ ModbusEgress
can/ CanEgress (SDO 写)
eth/ EthEgress
scheduler/ ModbusPollScheduler, PollPlanner, ChangeFilter
canopen/ NmtManager, SdoClient, PdoMapper
config/ runtime/ factory/ discovery/
main.cpp
└── EdgeRuntime
├── PhysicalRegistry
├── CompositeIngress
│ ├── ModbusIngress + ModbusPollScheduler / PollPlanner / ChangeFilter
│ ├── CanIngress + NmtManager / PdoMapper / SdoClient
│ └── EthIngress
├── ModbusEgress / CanEgress / EthEgress
├── CommandExecutor ← 按 protocol 分发写
└── UplinkClient
4.1 IIngressPort — 入站抽象
| 方法 | 说明 |
|---|---|
open() |
打开采集通道 |
close() |
关闭采集通道 |
poll() |
轮询,返回 vector<RawPacket> |
ModbusIngress:PollPlanner 调度到期点 → ModbusPollScheduler 合并相邻寄存器批量读 → ChangeFilter 变化过滤 → RawPacket。
CanIngress:启动时 NMT Start + 加载 EDS;TPDO 监听 + SDO 慢点轮询;protocolTag: "canopen"。
EthIngress:裸 TCP/UDP 收字节;protocolTag: "raw_eth"。
MockModbusIngress:--mock 模式,无硬件模拟帧。
4.2 ModbusChannelManager — 硬件 I/O
- 构建时链接 libmodbus(系统包或
third_party/libmodbusvendor) - RTU:
modbus_new_rtu+modbus_read_registers/modbus_write_register - TCP:
modbus_new_tcp(transport: tcp时) - 无 libmodbus 时退化为模拟读写(编译宏
SOFTBUS_HAS_LIBMODBUS=0)
edge 不使用 daemon 侧的
protocol_modbus.so插件;插件仅用于 daemon 解析上行 RawPacket。
4.3 UplinkClient — 上行/下行
| 方法 | 说明 |
|---|---|
connect() / disconnect() |
TCP 连接 daemon |
sendEdgeRegister(edgeId) |
注册边缘实例 |
sendHeartbeat(deviceId) |
心跳 |
sendRawPacket(RawPacket) |
上行原始包 |
sendAck(SoftbusAck) |
写命令 ACK |
poll() |
接收下行 ExecuteCommand |
setCommandHandler(handler) |
注册命令回调 |
协议详见 UPLINK_WIRE_PROTOCOL.md。
4.4 CommandExecutor — 下行写
daemon 经 bindings_global 在命令中注入 physicalPointId + edgeId,edge 按协议写:
modbus → ModbusEgress
canopen → CanEgress (SDO download)
raw → EthEgress
仅当命令缺少 physicalPointId 时,才用 bindings_local.json 做 logicalPointId → physicalPointId 备用解析。
5. 数据流
5.1 上行(采集 → OPC)
sequenceDiagram
participant HW as 现场设备
participant Edge as softbus_edge
participant Daemon as softbus_daemon
participant OPC as OPC UA
HW->>Edge: Modbus RTU/TCP 读寄存器
Edge->>Edge: ModbusIngress.poll → RawPacket
Note over Edge: sourceId = edge01/phy_xxx<br/>protocolTag = modbus
Edge->>Daemon: TCP rawPacket + edgeRegister/heartbeat
Daemon->>Daemon: protocol_modbus 解析帧
Daemon->>Daemon: bindings_global → logicalPointId
Daemon->>Daemon: logical_points → objectRef + scale
Daemon->>OPC: UaModelBinder 写节点值
逐步说明:
- edge:
ModbusIngress读硬件 →RawPacket{ sourceId=physicalPointId, payload=Modbus帧 } - edge:
UplinkClient.sendRawPacket→ TCP JSON 行 - daemon:
TransportBusBridge发布到总线edge/raw/{physicalPointId} - daemon:
PipelineEngine+protocol_modbus插件解析 → 寄存器值 - daemon:
bindings_global查绑定 →logical_points得 OPC 路径 → scale → 写 OPC UA
未绑定物理点:daemon 发布到 edge/unbound/raw/{physicalPointId},不写 OPC。
5.2 下行(OPC 写 → 设备)
sequenceDiagram
participant OPC as OPC UA 客户端
participant Daemon as softbus_daemon
participant Edge as softbus_edge
participant HW as 现场设备
OPC->>Daemon: ExecuteCommand (objectRef + value)
Daemon->>Daemon: bindings_global → physicalPointId + edgeId
Daemon->>Edge: TCP executeCommand
Edge->>Edge: CommandExecutor → ModbusEgress
Edge->>HW: libmodbus 写寄存器
Edge->>Daemon: TCP ack
5.3 主循环(main.cpp)
while (!shouldStop) {
ConfigWatcher.poll();
if (SIGHUP) { reloadPhysical(); reloadBindingsPatch(); }
runtime.tick(); // uplink.poll + ingress.poll + sendRawPacket
if (uplinkConnected && heartbeatDue)
sendHeartbeat(edgeId);
sleep(50ms);
}
6. CLI 参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--config <path> |
config/edge/edge.json |
边缘入口配置 |
--mock |
关闭 | 使用 MockModbusIngress,跳过 physical 配置与 libmodbus |
--object-ref <path> |
DemoSite/.../MotorTemperature |
Mock 模式下的模拟 sourceId |
不再使用
--daemon host:port;uplink 地址由edge.json的daemonHost/daemonPort指定。
7. 依赖
| 依赖 | 用途 |
|---|---|
softbus_core |
RawPacket、Logger、PlatformSignal、ConfigWatcher |
softbus_registry |
BindingRegistry(仅 local 可选) |
softbus_transport |
TcpUplinkClient、UplinkWire |
softbus_api |
ExecuteCommand、SoftbusAck |
| libmodbus | Modbus RTU/TCP 硬件 I/O |
| SocketCAN | Linux linux/can.h(SOFTBUS_HAS_SOCKETCAN) |
| softbus_canopen_common | EDS 解析、对象字典 |
| asio | Uplink + EthChannel |
8. 构建与运行
8.1 libmodbus
构建时自动选择:系统 libmodbus-dev 优先,否则编译 third_party/libmodbus 静态库进 edge。
cmake -B build
cmake --build build --target softbus_edge
串口权限:sudo usermod -aG dialout $USER
8.2 运行示例
# 1. 先启动 daemon
./build/bin/softbus_daemon \
--config config/daemon/daemon_profile.json \
--logical-points config/daemon/logical_points.json \
--bindings-global config/daemon/bindings_global.json
# 2. 启动 edge(真实 Modbus)
./build/bin/softbus_edge --config config/edge/edge.json
# 3. 无硬件 / 无 daemon 调试
./build/bin/softbus_edge --config config/edge/edge.json --mock
# 4. 热加载物理配置
kill -HUP $(pidof softbus_edge)
更多配置说明见 edge_device_setup.md。
9. 多协议与物理层优化(已实现)
9.1 多 Ingress 并存
physical_channels.json 中可混合 protocol: modbus | canopen | raw,DeviceFactory 为每种协议创建子 ingress,由 CompositeIngress 统一 poll()。
9.2 Modbus 采集优化
| 组件 | 行为 |
|---|---|
| ModbusPollScheduler | 同 (channel, slave, function) 下合并相邻寄存器,单次最多 125 寄存器 |
| PollPlanner | 每点独立 pollIntervalMs(缺省继承通道) |
| ChangeFilter | reportOnChange + deadband 物理层过滤 |
9.3 CANopen(edge 已实现)
| 组件 | 说明 |
|---|---|
| CanChannelManager | SocketCAN 收发(无头文件时模拟降级) |
| NmtManager | 启动时 NMT Start Remote Node |
| EdsDcfLoader | softbus_canopen_common 解析 EDS |
| PdoMapper | TPDO 订阅表 + 帧解包 |
| SdoClient | SDO upload(慢点)/ download(下行写) |
| daemon | protocol_canopen.so 解析仍占位 |
CAN 前置:sudo ip link set can0 up type can bitrate 500000
9.4 裸以太网(骨架)
protocol: "raw" + transport: tcp|udp → EthIngress 原样上行;EthEgress 支持原始字节写。
Modbus TCP 仍走 protocol: modbus + transport: tcp。
9.5 待增强
| 项 | 说明 |
|---|---|
| DeviceDiscovery | 总线扫描、动态写入 physical_devices |
| daemon CANopen 解析 | CanopenParser 完整实现 |
| Eth 帧解析 | 自定义帧格式插件化 |
10. 约束(CONSTRAINTS)
- edge 不得直接操作 OPC UA 或加载
protocol_*.so解析插件 - edge 不得加载
logical_points.json/bindings_global.json - ingress 实现须遵循
IIngressPort,便于接入 CANopen/Ethernet driver - 上行统一为
RawPacket;sourceId必须是 physicalPointId(含 edgeId 前缀) - daemon 不可达时 edge 必须能独立启动并采集(uplink 后台重连)
11. 相关文档
| 文档 | 内容 |
|---|---|
| edge/tcp_rtu_callstack.md | Modbus TCP 点完整调用栈(含 RTU-over-TCP) |
| edge_device_setup.md | 配置目录、双域、libmodbus、Mock |
| UPLINK_WIRE_PROTOCOL.md | TCP JSON 线协议 |
| softbus_daemon.md | daemon 侧 Pipeline 与 OPC |
| plugins.md | protocol 插件(daemon 解析用) |