15 KiB
softbus_edge
路径: src/softbus_edge
构建目标: softbus_edge(EXECUTABLE)
层级: L3 可执行层
1. 概述
softbus_edge 是边缘代理进程,运行在靠近现场设备的一侧,负责:
- 物理域采集(ingress):按
physical_*.json轮询 Modbus RTU/TCP 等设备,产出RawPacket - 上行(egress/uplink):经 TCP 将
RawPacket、心跳、注册信息发送给softbus_daemon - 下行(egress):接收 daemon 下发的
ExecuteCommand,写回物理设备(Modbus 写寄存器等)
edge 不加载逻辑点表、不操作 OPC UA、不运行 protocol_modbus 插件。协议解析与 OPC 绑定均在 daemon 侧完成。
| 模式 | 说明 |
|---|---|
| 正常模式 | libmodbus + ModbusIngress,读真实硬件 |
| 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 → ModbusIngress 或 MockModbusIngress
7. ModbusChannelManager.openChannel(逐通道 libmodbus 连接)
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 |
该通道下所有点的轮询周期(当前整通道统一) |
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
}
]
}
]
}
运行时 physicalPointId 会变为 {edgeId}/{physicalPointId},作为 RawPacket.sourceId 上报。
3.5 热加载
| 信号 | 行为 |
|---|---|
SIGHUP |
reloadPhysical():重载 channels + devices,重建 Modbus 连接 |
SIGHUP |
reloadBindingsPatch():若配置了 bindingsPatch 则增量应用 |
| 文件变更 | ConfigWatcher 监视 physical_channels、physical_devices、bindingsPatch |
4. 组件架构
main.cpp
└── EdgeRuntime
├── PhysicalRegistry ← physical_*.json
├── BindingRegistry (local) ← bindings_local.json(可选)
├── ModbusChannelManager ← libmodbus RTU/TCP I/O
├── IIngressPort
│ ├── ModbusIngress ← 正常模式
│ └── MockModbusIngress ← --mock
├── ModbusEgress ← 下行写寄存器
├── CommandExecutor ← ExecuteCommand → Modbus 写 / mock
└── UplinkClient ← TcpUplinkClient 封装
4.1 IIngressPort — 入站抽象
| 方法 | 说明 |
|---|---|
open() |
打开采集通道 |
close() |
关闭采集通道 |
poll() |
轮询,返回 vector<RawPacket> |
ModbusIngress:按 pollIntervalMs 遍历 pollPoints(),经 ModbusChannelManager 读寄存器,组装 Modbus 0x03 响应帧作为 payload。
MockModbusIngress:无硬件,生成递增值模拟帧。
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:
ExecuteCommand.params.physicalPointId → PhysicalRegistry → ModbusEgress.write
仅当命令缺少 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(vendor 或系统包) |
| asio | TcpUplinkClient 底层 |
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. 未来扩展:CANopen 与以太网设备
当前仅 Modbus RTU/TCP 在 edge 侧有完整 ingress/egress。CANopen 与更多以太网协议按同一双域模型扩展。
9.1 目标架构
physical_channels.json ──→ ChannelDriver(按 protocol 选择)
physical_devices.json ──→ PhysicalRegistry
│
┌───────────┼───────────┐
▼ ▼ ▼
ModbusIngress CanIngress EthIngress(规划)
│ │ │
└───────────┴───────────┘
▼
RawPacket → UplinkClient
daemon 侧已有对应解析插件(protocol_modbus、protocol_canopen),edge 只需换 ingress/driver,上行仍是 RawPacket + protocolTag。
9.2 CANopen(规划)
| 层级 | 计划 |
|---|---|
| edge | CanIngress + SocketCAN/can0 通道;SDO/PDO 读对象字典 → RawPacket,protocolTag: "canopen" |
| 配置 | physical_channels 增加 transport: can,interface: can0,bitrate: 500000;physical_devices 增加 nodeId、对象索引 |
| daemon | 已有 protocol_canopen.so(Framer/Parser 占位),绑定仍走 bindings_global |
| EDS/DCF | edge 侧加载 EDS 映射物理点 ↔ COB-ID/索引;daemon 不接收 EDS 全量 |
9.3 以太网设备(规划)
| 类型 | edge 侧 | 说明 |
|---|---|---|
| Modbus TCP | 已支持 | physical_channels 设 transport: tcp,host + tcpPort |
| 裸 TCP / 自定义帧 | EthIngress(规划) |
按 channel 配置帧格式或插件化 driver |
| UDP 组播 | 待定 | 适用于部分工业相机/传感器 |
以太网通道示例(Modbus TCP,已可用):
{
"channelId": "eth_modbus_0",
"protocol": "modbus",
"transport": "tcp",
"host": "192.168.1.10",
"tcpPort": 502,
"pollIntervalMs": 200
}
9.4 edge 侧待增强(Modbus 及通用)
| 项 | 说明 |
|---|---|
| ModbusPollScheduler | 合并相邻寄存器读,减少串口占用 |
| 按点 pollInterval | 不同信号类型不同采集周期 |
| 变化上报 / deadband | 物理层过滤,减少 uplink 流量 |
| DeviceDiscovery | 总线扫描、动态写入 physical_devices |
| 多 ingress 并存 | 同一 edge 上 Modbus + CANopen 并行 |
上述优化均在物理域完成,不改变 daemon 双域模型。
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_device_setup.md | 配置目录、双域、libmodbus、Mock |
| UPLINK_WIRE_PROTOCOL.md | TCP JSON 线协议 |
| softbus_daemon.md | daemon 侧 Pipeline 与 OPC |
| plugins.md | protocol 插件(daemon 解析用) |