Files
softbus_OPC/docs/softbus_edge.md
flower_linux 628ccf1409 phase 2 serial
2026-06-12 18:34:49 +08:00

437 lines
15 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 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` 所在目录**解析。
```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 → ModbusIngress 或 MockModbusIngress
7. ModbusChannelManager.openChannel逐通道 libmodbus 连接)
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` | 该通道下所有点的轮询周期(当前整通道统一) |
### 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
}
]
}
]
}
```
运行时 `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/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
```
ExecuteCommand.params.physicalPointId → PhysicalRegistry → ModbusEgress.write
```
仅当命令**缺少** `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/Ovendor 或系统包) |
| asio | TcpUplinkClient 底层 |
---
## 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. 未来扩展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已可用
```json
{
"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](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 解析用) |