Files
softbus_OPC/docs/softbus_edge.md
flower_linux ebff25e0d5 phase 2 tcp
2026-06-12 21:23:21 +08:00

465 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 所在目录**解析。
```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 初始化
6. DeviceFactory.createIngress → CompositeIngress按 protocol 创建子 ingress
7. 各 ChannelManager.openChannellibmodbus / SocketCAN / asio
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` |
| `pollIntervalMs` | 通道默认轮询周期(点可覆盖) |
| `interface` / `bitrate` | CANopen 通道:`can0``500000` |
| `host` / `tcpPort` | Modbus TCP 或裸以太网 |
CANopen 通道示例见 `config/edge/physical_channels_can.json`
### 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",
"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 + 加载 EDSTPDO 监听 + SDO 慢点轮询;`protocolTag: "canopen"`
**EthIngress**:裸 TCP/UDP 收字节;`protocolTag: "raw_eth"`
**MockModbusIngress**`--mock` 模式,无硬件模拟帧。
### 4.2 ModbusChannelManager — 硬件 I/O
- 构建时链接 **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`
> 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](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
```mermaid
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 写节点值
```
逐步说明:
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`
```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。
```bash
cmake -B build
cmake --build build --target softbus_edge
```
串口权限:`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)
```
更多配置说明见 [edge_device_setup.md](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 CANopenedge 已实现)
| 组件 | 说明 |
|------|------|
| **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](edge/tcp_rtu_callstack.md) | Modbus TCP 点完整调用栈(含 RTU-over-TCP |
| [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 解析用) |