Files
softbus_OPC/docs/softbus_edge.md

465 lines
16 KiB
Markdown
Raw Normal View History

2026-06-09 17:27:24 +08:00
# softbus_edge
**路径:** `src/softbus_edge`
**构建目标:** `softbus_edge`EXECUTABLE
**层级:** L3 可执行层
## 1. 概述
2026-06-12 18:34:49 +08:00
`softbus_edge` 是边缘代理进程,运行在靠近现场设备的一侧,负责:
2026-06-09 17:27:24 +08:00
2026-06-12 21:23:21 +08:00
- **物理域采集ingress**:按 `physical_*.json` 轮询 Modbus / CANopen / 裸以太网设备,产出 `RawPacket`
2026-06-12 18:34:49 +08:00
- **上行egress/uplink**:经 TCP 将 `RawPacket`、心跳、注册信息发送给 `softbus_daemon`
- **下行egress**:接收 daemon 下发的 `ExecuteCommand`写回物理设备Modbus 写寄存器等)
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
edge **不**加载逻辑点表、**不**操作 OPC UA、**不**运行 `protocol_modbus` 插件。协议解析与 OPC 绑定均在 daemon 侧完成。
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
| 模式 | 说明 |
|------|------|
2026-06-12 21:23:21 +08:00
| **正常模式** | `CompositeIngress`Modbus + CANopen + Eth读真实硬件 |
2026-06-12 18:34:49 +08:00
| **Mock 模式** | `--mock`,无硬件,模拟 Modbus 帧上行 |
| **独立模式** | daemon 未启动时 edge 仍可运行,后台重连 uplink |
---
## 2. 与 daemon 的职责边界(双域)
2026-06-09 17:27:24 +08:00
```
2026-06-12 18:34:49 +08:00
┌─────────────────────────────────────────────────────────────────┐
│ 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` 所在目录**解析。
```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` | 边缘实例 IDuplink 注册与 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 初始化
2026-06-12 21:23:21 +08:00
6. DeviceFactory.createIngress → CompositeIngress按 protocol 创建子 ingress
7. 各 ChannelManager.openChannellibmodbus / SocketCAN / asio
2026-06-12 18:34:49 +08:00
8. uplink.connect();成功则 sendEdgeRegister(edgeId)
→ 失败仅 WARNedge 独立运行并后台重连
```
### 3.3 物理通道(`physical_channels.json`
```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` |
2026-06-12 21:23:21 +08:00
| `pollIntervalMs` | 通道默认轮询周期(点可覆盖) |
| `interface` / `bitrate` | CANopen 通道:`can0``500000` |
| `host` / `tcpPort` | Modbus TCP 或裸以太网 |
CANopen 通道示例见 `config/edge/physical_channels_can.json`
2026-06-12 18:34:49 +08:00
### 3.4 物理设备与点(`physical_devices.json`
```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",
2026-06-12 21:23:21 +08:00
"writable": false,
"pollIntervalMs": 1000,
"reportOnChange": true,
"deadband": 0.5
2026-06-12 18:34:49 +08:00
}
]
}
]
}
2026-06-09 17:27:24 +08:00
```
2026-06-12 21:23:21 +08:00
**点级可选字段:**
| 字段 | 说明 |
|------|------|
| `pollIntervalMs` | 覆盖通道默认周期 |
| `reportOnChange` | `true` 时启用变化上报 |
| `deadband` | 工程值死区(配合 `reportOnChange` |
| `objectIndex` / `subIndex` | CANopen 对象字典索引 |
| `pdoType` | `tpdo` / `sdo` / `rpdo` |
| `cobId` | PDO COB-ID 覆盖 |
| `nodeId` / `edsPath` | 设备级 CANopen 字段 |
2026-06-12 18:34:49 +08:00
运行时 `physicalPointId` 会变为 `{edgeId}/{physicalPointId}`,作为 `RawPacket.sourceId` 上报。
2026-06-12 21:23:21 +08:00
CANopen 示例见 `config/edge/physical_devices_can.json``config/edge/samples/minimal.eds`
2026-06-12 18:34:49 +08:00
### 3.5 热加载
| 信号 | 行为 |
|------|------|
| `SIGHUP` | `reloadPhysical()`:重载 channels + devices重建 Modbus 连接 |
| `SIGHUP` | `reloadBindingsPatch()`:若配置了 `bindingsPatch` 则增量应用 |
| 文件变更 | `ConfigWatcher` 监视 `physical_channels``physical_devices``bindingsPatch` |
---
## 4. 组件架构
2026-06-12 21:23:21 +08:00
### 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/
```
2026-06-12 18:34:49 +08:00
```
main.cpp
└── EdgeRuntime
2026-06-12 21:23:21 +08:00
├── PhysicalRegistry
├── CompositeIngress
│ ├── ModbusIngress + ModbusPollScheduler / PollPlanner / ChangeFilter
│ ├── CanIngress + NmtManager / PdoMapper / SdoClient
│ └── EthIngress
├── ModbusEgress / CanEgress / EthEgress
├── CommandExecutor ← 按 protocol 分发写
└── UplinkClient
2026-06-12 18:34:49 +08:00
```
2026-06-09 17:27:24 +08:00
### 4.1 IIngressPort — 入站抽象
| 方法 | 说明 |
|------|------|
| `open()` | 打开采集通道 |
| `close()` | 关闭采集通道 |
| `poll()` | 轮询,返回 `vector<RawPacket>` |
2026-06-12 21:23:21 +08:00
**ModbusIngress**`PollPlanner` 调度到期点 → `ModbusPollScheduler` 合并相邻寄存器批量读 → `ChangeFilter` 变化过滤 → `RawPacket`
2026-06-12 18:34:49 +08:00
2026-06-12 21:23:21 +08:00
**CanIngress**:启动时 NMT Start + 加载 EDSTPDO 监听 + SDO 慢点轮询;`protocolTag: "canopen"`
**EthIngress**:裸 TCP/UDP 收字节;`protocolTag: "raw_eth"`
**MockModbusIngress**`--mock` 模式,无硬件模拟帧。
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
### 4.2 ModbusChannelManager — 硬件 I/O
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
- 构建时链接 **libmodbus**(系统包或 `third_party/libmodbus` vendor
- RTU`modbus_new_rtu` + `modbus_read_registers` / `modbus_write_register`
- TCP`modbus_new_tcp``transport: tcp` 时)
- 无 libmodbus 时退化为模拟读写(编译宏 `SOFTBUS_HAS_LIBMODBUS=0`
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
> edge **不使用** daemon 侧的 `protocol_modbus.so` 插件;插件仅用于 daemon 解析上行 RawPacket。
### 4.3 UplinkClient — 上行/下行
2026-06-09 17:27:24 +08:00
| 方法 | 说明 |
|------|------|
2026-06-12 18:34:49 +08:00
| `connect()` / `disconnect()` | TCP 连接 daemon |
| `sendEdgeRegister(edgeId)` | 注册边缘实例 |
| `sendHeartbeat(deviceId)` | 心跳 |
| `sendRawPacket(RawPacket)` | 上行原始包 |
| `sendAck(SoftbusAck)` | 写命令 ACK |
| `poll()` | 接收下行 `ExecuteCommand` |
2026-06-09 17:27:24 +08:00
| `setCommandHandler(handler)` | 注册命令回调 |
2026-06-12 18:34:49 +08:00
协议详见 [UPLINK_WIRE_PROTOCOL.md](UPLINK_WIRE_PROTOCOL.md)。
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
### 4.4 CommandExecutor — 下行写
2026-06-12 21:23:21 +08:00
daemon 经 `bindings_global` 在命令中注入 `physicalPointId` + `edgeId`edge 按协议写:
2026-06-12 18:34:49 +08:00
```
2026-06-12 21:23:21 +08:00
modbus → ModbusEgress
canopen → CanEgress (SDO download)
raw → EthEgress
2026-06-12 18:34:49 +08:00
```
仅当命令**缺少** `physicalPointId` 时,才用 `bindings_local.json``logicalPointId → physicalPointId` 备用解析。
---
## 5. 数据流
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
### 5.1 上行(采集 → OPC
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
```mermaid
sequenceDiagram
participant HW as 现场设备
participant Edge as softbus_edge
participant Daemon as softbus_daemon
participant OPC as OPC UA
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
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 写节点值
```
逐步说明:
1. **edge**`ModbusIngress` 读硬件 → `RawPacket{ sourceId=physicalPointId, payload=Modbus帧 }`
2. **edge**`UplinkClient.sendRawPacket` → TCP JSON 行
3. **daemon**`TransportBusBridge` 发布到总线 `edge/raw/{physicalPointId}`
4. **daemon**`PipelineEngine` + `protocol_modbus` 插件解析 → 寄存器值
5. **daemon**`bindings_global` 查绑定 → `logical_points` 得 OPC 路径 → scale → 写 OPC UA
未绑定物理点daemon 发布到 `edge/unbound/raw/{physicalPointId}`,不写 OPC。
### 5.2 下行OPC 写 → 设备)
```mermaid
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`
2026-06-09 17:27:24 +08:00
```cpp
2026-06-12 18:34:49 +08:00
while (!shouldStop) {
ConfigWatcher.poll();
if (SIGHUP) { reloadPhysical(); reloadBindingsPatch(); }
runtime.tick(); // uplink.poll + ingress.poll + sendRawPacket
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
if (uplinkConnected && heartbeatDue)
sendHeartbeat(edgeId);
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
sleep(50ms);
2026-06-09 17:27:24 +08:00
}
```
2026-06-12 18:34:49 +08:00
---
2026-06-09 17:27:24 +08:00
## 6. CLI 参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
2026-06-12 18:34:49 +08:00
| `--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 |
2026-06-12 21:23:21 +08:00
| **libmodbus** | Modbus RTU/TCP 硬件 I/O |
| **SocketCAN** | Linux `linux/can.h``SOFTBUS_HAS_SOCKETCAN` |
| **softbus_canopen_common** | EDS 解析、对象字典 |
| asio | Uplink + EthChannel |
2026-06-12 18:34:49 +08:00
---
## 8. 构建与运行
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
### 8.1 libmodbus
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
构建时自动选择:系统 `libmodbus-dev` 优先,否则编译 `third_party/libmodbus` 静态库进 edge。
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
```bash
cmake -B build
cmake --build build --target softbus_edge
2026-06-09 17:27:24 +08:00
```
2026-06-12 18:34:49 +08:00
串口权限:`sudo usermod -aG dialout $USER`
### 8.2 运行示例
```bash
# 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)
2026-06-09 17:27:24 +08:00
```
2026-06-12 18:34:49 +08:00
更多配置说明见 [edge_device_setup.md](edge_device_setup.md)。
---
2026-06-12 21:23:21 +08:00
## 9. 多协议与物理层优化(已实现)
2026-06-12 18:34:49 +08:00
2026-06-12 21:23:21 +08:00
### 9.1 多 Ingress 并存
2026-06-12 18:34:49 +08:00
2026-06-12 21:23:21 +08:00
`physical_channels.json` 中可混合 `protocol: modbus | canopen | raw``DeviceFactory` 为每种协议创建子 ingress`CompositeIngress` 统一 `poll()`
2026-06-09 17:27:24 +08:00
2026-06-12 21:23:21 +08:00
### 9.2 Modbus 采集优化
2026-06-09 17:27:24 +08:00
2026-06-12 21:23:21 +08:00
| 组件 | 行为 |
|------|------|
| **ModbusPollScheduler** | 同 (channel, slave, function) 下合并相邻寄存器,单次最多 125 寄存器 |
| **PollPlanner** | 每点独立 `pollIntervalMs`(缺省继承通道) |
| **ChangeFilter** | `reportOnChange` + `deadband` 物理层过滤 |
2026-06-09 17:27:24 +08:00
2026-06-12 21:23:21 +08:00
### 9.3 CANopenedge 已实现)
2026-06-09 17:27:24 +08:00
2026-06-12 21:23:21 +08:00
| 组件 | 说明 |
2026-06-12 18:34:49 +08:00
|------|------|
2026-06-12 21:23:21 +08:00
| **CanChannelManager** | SocketCAN 收发(无头文件时模拟降级) |
| **NmtManager** | 启动时 NMT Start Remote Node |
| **EdsDcfLoader** | `softbus_canopen_common` 解析 EDS |
| **PdoMapper** | TPDO 订阅表 + 帧解包 |
| **SdoClient** | SDO upload慢点/ download下行写 |
| **daemon** | `protocol_canopen.so` 解析仍占位 |
2026-06-12 18:34:49 +08:00
2026-06-12 21:23:21 +08:00
CAN 前置:`sudo ip link set can0 up type can bitrate 500000`
2026-06-12 18:34:49 +08:00
2026-06-12 21:23:21 +08:00
### 9.4 裸以太网(骨架)
2026-06-12 18:34:49 +08:00
2026-06-12 21:23:21 +08:00
`protocol: "raw"` + `transport: tcp|udp``EthIngress` 原样上行;`EthEgress` 支持原始字节写。
2026-06-12 18:34:49 +08:00
2026-06-12 21:23:21 +08:00
Modbus TCP 仍走 `protocol: modbus` + `transport: tcp`
2026-06-09 17:27:24 +08:00
2026-06-12 21:23:21 +08:00
### 9.5 待增强
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
| 项 | 说明 |
|----|------|
| **DeviceDiscovery** | 总线扫描、动态写入 `physical_devices` |
2026-06-12 21:23:21 +08:00
| **daemon CANopen 解析** | `CanopenParser` 完整实现 |
| **Eth 帧解析** | 自定义帧格式插件化 |
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
---
2026-06-09 17:27:24 +08:00
2026-06-12 18:34:49 +08:00
## 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. 相关文档
| 文档 | 内容 |
|------|------|
2026-06-12 21:23:21 +08:00
| [edge/tcp_rtu_callstack.md](edge/tcp_rtu_callstack.md) | Modbus TCP 点完整调用栈(含 RTU-over-TCP |
2026-06-12 18:34:49 +08:00
| [edge_device_setup.md](edge_device_setup.md) | 配置目录、双域、libmodbus、Mock |
| [UPLINK_WIRE_PROTOCOL.md](UPLINK_WIRE_PROTOCOL.md) | TCP JSON 线协议 |
| [softbus_daemon.md](softbus_daemon.md) | daemon 侧 Pipeline 与 OPC |
| [plugins.md](plugins.md) | protocol 插件daemon 解析用) |