phase 2 tcp
This commit is contained in:
251
docs/edge/tcp_rtu_callstack.md
Normal file
251
docs/edge/tcp_rtu_callstack.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Edge Modbus TCP 点调用栈(含 RTU-over-TCP)
|
||||
|
||||
本文以配置点 **`phy_modbus_tcp_s1_r40001`** 为例,说明从 `physical_devices.json` 到硬件读数、再到 uplink 上报的完整调用路径。
|
||||
|
||||
运行时物理点 ID 为 `{edgeId}/{physicalPointId}`,例如 `edge01/phy_modbus_tcp_s1_r40001`。
|
||||
|
||||
相关配置:
|
||||
|
||||
- 点:`config/edge/physical_devices.json` → `phy_modbus_tcp_slave_255`
|
||||
- 通道:`config/edge/physical_channels.json` → `eth_modbus_tcp_0`,`transport: "tcp_rtu"`
|
||||
|
||||
---
|
||||
|
||||
## 1. 设计要点:没有独立的 TcpRtuIngress
|
||||
|
||||
edge **不按传输方式** 创建 ingress,而按 **`protocol`** 创建:
|
||||
|
||||
| 配置字段 | 作用 |
|
||||
|----------|------|
|
||||
| `protocol: "modbus"` | 走 `ModbusIngress`(串口 + TCP + RTU-over-TCP 共用) |
|
||||
| `transport: "rtu"` | driver 层:`libmodbus` 串口 |
|
||||
| `transport: "tcp"` | driver 层:`libmodbus` 标准 Modbus TCP(MBAP) |
|
||||
| `transport: "tcp_rtu"` | driver 层:`TcpRtuSession`(RTU 帧跑在 TCP 上) |
|
||||
|
||||
自定义协议分支在 **`ModbusChannelManager`** 内,由 `transport` 字段触发,上层 `ModbusIngress` 无感知。
|
||||
|
||||
```
|
||||
physical_devices.json (点)
|
||||
↓
|
||||
ModbusIngress(统一采集)
|
||||
↓
|
||||
ModbusChannelManager::openChannel / readRegisters
|
||||
├─ transport: rtu → libmodbus 串口
|
||||
├─ transport: tcp → libmodbus 标准 TCP
|
||||
└─ transport: tcp_rtu → TcpRtuSession
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 配置锚点
|
||||
|
||||
```json
|
||||
{
|
||||
"physicalDeviceId": "phy_modbus_tcp_slave_255",
|
||||
"channelId": "eth_modbus_tcp_0",
|
||||
"protocol": "modbus",
|
||||
"slaveId": 255,
|
||||
"points": [
|
||||
{
|
||||
"physicalPointId": "phy_modbus_tcp_s1_r40001",
|
||||
"register": 40001,
|
||||
"function": 3,
|
||||
"scale": 0.1,
|
||||
"pollIntervalMs": 500
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"channelId": "eth_modbus_tcp_0",
|
||||
"protocol": "modbus",
|
||||
"transport": "tcp_rtu",
|
||||
"host": "192.168.31.253",
|
||||
"tcpPort": 502
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 阶段 A:启动(一次性)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["main:52 runtime.init()"] --> B["EdgeRuntime::init:35 loadDevices"]
|
||||
B --> C["PhysicalRegistry::loadDevices:89 qualifyPointId"]
|
||||
C --> D["rebuildPollIndex:143 写入 pointIndex_"]
|
||||
D --> E["EdgeRuntime::rebuildIngress:16 createIngress"]
|
||||
E --> F["DeviceFactory::createIngress:34 ModbusIngress"]
|
||||
F --> G["ModbusIngress::open:24 openChannel(eth_modbus_tcp_0)"]
|
||||
G --> H{"transport == tcp_rtu?"}
|
||||
H -->|是| I["ModbusChannel::openChannel:39-51 TcpRtuSession::connect:31"]
|
||||
G --> K["PollPlanner::reset:18 注册点周期 500ms"]
|
||||
```
|
||||
|
||||
| 步骤 | 函数 | 文件:行号 | 说明 |
|
||||
|------|------|-----------|------|
|
||||
| 1 | `main` | `src/softbus_edge/src/main.cpp:52` | `runtime.init(config, ...)` |
|
||||
| 2 | `EdgeRuntime::init` | `src/softbus_edge/src/EdgeRuntime.cpp:35` | `physicalRegistry_.loadDevices(..., edgeId)` |
|
||||
| 3 | `PhysicalRegistry::loadDevices` | `src/softbus_edge/src/PhysicalRegistry.cpp:89-91` | JSON 解析,`qualifyPointId` → `edge01/phy_modbus_tcp_s1_r40001` |
|
||||
| 4 | `PhysicalRegistry::rebuildPollIndex` | `PhysicalRegistry.cpp:142-145` | 组装 `ResolvedPhysicalPoint{point, device, channel}` 写入 `pointIndex_` |
|
||||
| 5 | `EdgeRuntime::rebuildIngress` | `EdgeRuntime.cpp:16-19` | `createIngress` → `ingress_->open()` |
|
||||
| 6 | `DeviceFactory::createIngress` | `src/softbus_edge/src/DeviceFactory.cpp:33-35` | 创建 `ModbusIngress` |
|
||||
| 7 | `ModbusIngress::open` | `src/softbus_edge/src/ingress/modbus/ModbusIngress.cpp:23-24` | 对所有 `protocol==modbus` 通道调用 `openChannel` |
|
||||
| 8 | `ModbusChannelManager::openChannel` | `src/softbus_edge/src/driver/modbus/ModbusChannel.cpp:39-51` | `tcp_rtu` → `TcpRtuSession::connect(host, port)` |
|
||||
| 9 | `TcpRtuSession::connect` | `src/softbus_edge/src/driver/modbus/TcpRtuSession.cpp:31-62` | TCP `socket` + `connect`,保存 `socketFd_` |
|
||||
| 10 | `PollPlanner::reset` | `src/softbus_edge/src/scheduler/PollPlanner.cpp:9-18` | 注册该点 `intervalMs=500` |
|
||||
|
||||
启动成功后日志示例:
|
||||
|
||||
```
|
||||
Opened RTU-over-TCP channel eth_modbus_tcp_0 192.168.31.253:502
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 阶段 B:运行循环(每 50ms tick,点每 500ms 到期)
|
||||
|
||||
主循环:`main.cpp:76` → `EdgeRuntime::tick()` → `pollIngress()` → `ModbusIngress::poll()`。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant M as main.cpp:76
|
||||
participant RT as EdgeRuntime::tick:153
|
||||
participant PI as EdgeRuntime::pollIngress:141
|
||||
participant MI as ModbusIngress::poll:57
|
||||
participant PP as PollPlanner
|
||||
participant MPS as ModbusPollScheduler
|
||||
participant MCM as ModbusChannelManager
|
||||
participant TR as TcpRtuSession
|
||||
participant UL as UplinkClient
|
||||
|
||||
M->>RT: tick()
|
||||
RT->>PI: pollIngress()
|
||||
PI->>MI: ingress_->poll()
|
||||
MI->>PP: duePointIds()
|
||||
MI->>MI: resolvePoint
|
||||
MI->>MPS: planBatches()
|
||||
MI->>MCM: readRegisters(startAddr, count)
|
||||
MCM->>TR: readHoldingRegisters(255, 0, 2)
|
||||
TR->>TR: transaction RTU send/recv
|
||||
TR-->>MCM: values
|
||||
MCM-->>MI: values
|
||||
MI->>MI: buildModbusFrame RawPacket
|
||||
PI->>UL: sendRawPacket (uplink 已连接时)
|
||||
```
|
||||
|
||||
### 4.1 文本调用栈(带行号)
|
||||
|
||||
```
|
||||
main.cpp:76
|
||||
└─ EdgeRuntime::tick() EdgeRuntime.cpp:153-156
|
||||
└─ EdgeRuntime::pollIngress() EdgeRuntime.cpp:141-150
|
||||
└─ ModbusIngress::poll() ModbusIngress.cpp:57-130
|
||||
│
|
||||
├─ PollPlanner::duePointIds() PollPlanner.cpp:22-31
|
||||
│ → 含 "edge01/phy_modbus_tcp_s1_r40001"(500ms 周期)
|
||||
│
|
||||
├─ PhysicalRegistry::resolvePoint() PhysicalRegistry.cpp:183-190
|
||||
│ → ResolvedPhysicalPoint {
|
||||
│ point.registerAddress = 40001
|
||||
│ device.slaveId = 255
|
||||
│ channel.channelId = "eth_modbus_tcp_0"
|
||||
│ channel.transport = "tcp_rtu"
|
||||
│ }
|
||||
│
|
||||
├─ ModbusPollScheduler::planBatches() ModbusPollScheduler.cpp:31-104
|
||||
│ → modbusAddress(40001) = 0
|
||||
│ → 常与 r40002 合并 batch{startAddr=0, count=2}
|
||||
│
|
||||
├─ ModbusChannelManager::readRegisters() ModbusChannel.cpp:121-132
|
||||
│ → channels_["eth_modbus_tcp_0"].tcpRtu 非空
|
||||
│ └─ TcpRtuSession::readHoldingRegisters(255, 0, 2) TcpRtuSession.cpp:97-137
|
||||
│ ├─ 组 PDU: FF 03 00 00 00 02 :106-113
|
||||
│ └─ TcpRtuSession::transaction(pdu) :73-94
|
||||
│ ├─ crc16 + append CRC :79-82
|
||||
│ ├─ send(socketFd_) :84
|
||||
│ └─ recv(socketFd_) :88-94
|
||||
│
|
||||
├─ ChangeFilter::shouldReport() ModbusIngress.cpp:112-115
|
||||
├─ buildModbusFrame(255, raw) ModbusIngress.cpp:44-54, :124
|
||||
├─ packet.sourceId = "edge01/phy_modbus_tcp_s1_r40001" :122
|
||||
├─ packet.protocolTag = "modbus" :123
|
||||
└─ PollPlanner::markPolled() ModbusIngress.cpp:126
|
||||
PollPlanner.cpp:34-41
|
||||
|
||||
└─ UplinkClient::sendRawPacket(packet) EdgeRuntime.cpp:147-148
|
||||
└─ UplinkClient::sendRawPacket() UplinkClient.cpp:30-32
|
||||
└─ TcpUplinkClient::sendRawPacket() (softbus_transport)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 自定义协议触发点(仅两处)
|
||||
|
||||
**打开通道(一次性):**
|
||||
|
||||
```39:51:src/softbus_edge/src/driver/modbus/ModbusChannel.cpp
|
||||
if (isTcpRtuTransport(channel)) {
|
||||
handle.tcpRtu = std::make_unique<TcpRtuSession>();
|
||||
if (!handle.tcpRtu->connect(channel.host, channel.tcpPort)) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**读寄存器(每次轮询):**
|
||||
|
||||
```129:131:src/softbus_edge/src/driver/modbus/ModbusChannel.cpp
|
||||
if (it->second.tcpRtu) {
|
||||
return it->second.tcpRtu->readHoldingRegisters(
|
||||
point.device.slaveId, startAddr, count);
|
||||
```
|
||||
|
||||
`ModbusIngress` 在 `ModbusIngress.cpp:89` 统一调用 `readRegisters`,不区分底层实现。
|
||||
|
||||
---
|
||||
|
||||
## 6. 本点一次读写的实际参数
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| `physicalPointId` | `edge01/phy_modbus_tcp_s1_r40001` |
|
||||
| `channelId` | `eth_modbus_tcp_0` |
|
||||
| `slaveId` | `255` → RTU 帧首字节 `0xFF` |
|
||||
| `register 40001` | 内部地址 `startAddr = 0` |
|
||||
| 合并读 | 常与 `r40002` 一次 `count=2` |
|
||||
| 网线 payload | `FF 03 00 00 00 02` + CRC(**非** Modbus TCP MBAP) |
|
||||
| 上行 RawPacket | `protocolTag="modbus"`,payload 为模拟 0x03 响应帧 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 为何需要 tcp_rtu
|
||||
|
||||
部分设备(如 `192.168.31.253:502`)在 TCP 502 端口上收发的是 **Modbus RTU 帧**,而非标准 Modbus TCP(无 MBAP 事务头)。
|
||||
|
||||
| 方式 | 结果 |
|
||||
|------|------|
|
||||
| `transport: "tcp"` + libmodbus | 连接成功,但读寄存器 `Invalid data` |
|
||||
| `transport: "tcp_rtu"` + TcpRtuSession | 读 slave 255 寄存器成功 |
|
||||
|
||||
daemon 侧仍用 `protocol_modbus` 解析上行 `RawPacket`,**无需知道** 底层是串口还是 RTU-over-TCP。
|
||||
|
||||
---
|
||||
|
||||
## 8. 与串口点的对比
|
||||
|
||||
| 层级 | 串口 `phy_modbus_s1_r40001` | TCP `phy_modbus_tcp_s1_r40001` |
|
||||
|------|----------------------------|--------------------------------|
|
||||
| Ingress | 同一 `ModbusIngress::poll` | 同一 `ModbusIngress::poll` |
|
||||
| openChannel | `ModbusChannel.cpp:55-76` libmodbus RTU | `ModbusChannel.cpp:39-51` TcpRtuSession |
|
||||
| readRegisters | `modbus_read_registers` | `TcpRtuSession::readHoldingRegisters` |
|
||||
| 上行 protocolTag | `modbus` | `modbus` |
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关文档
|
||||
|
||||
- [softbus_edge.md](../softbus_edge.md) — edge 总览
|
||||
- [edge_device_setup.md](../edge_device_setup.md) — 配置与启动
|
||||
- [UPLINK_WIRE_PROTOCOL.md](../UPLINK_WIRE_PROTOCOL.md) — uplink 线协议
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
`softbus_edge` 是边缘代理进程,运行在靠近现场设备的一侧,负责:
|
||||
|
||||
- **物理域采集(ingress)**:按 `physical_*.json` 轮询 Modbus RTU/TCP 等设备,产出 `RawPacket`
|
||||
- **物理域采集(ingress)**:按 `physical_*.json` 轮询 Modbus / CANopen / 裸以太网设备,产出 `RawPacket`
|
||||
- **上行(egress/uplink)**:经 TCP 将 `RawPacket`、心跳、注册信息发送给 `softbus_daemon`
|
||||
- **下行(egress)**:接收 daemon 下发的 `ExecuteCommand`,写回物理设备(Modbus 写寄存器等)
|
||||
|
||||
@@ -16,7 +16,7 @@ edge **不**加载逻辑点表、**不**操作 OPC UA、**不**运行 `protocol_
|
||||
|
||||
| 模式 | 说明 |
|
||||
|------|------|
|
||||
| **正常模式** | libmodbus + `ModbusIngress`,读真实硬件 |
|
||||
| **正常模式** | `CompositeIngress`(Modbus + CANopen + Eth),读真实硬件 |
|
||||
| **Mock 模式** | `--mock`,无硬件,模拟 Modbus 帧上行 |
|
||||
| **独立模式** | daemon 未启动时 edge 仍可运行,后台重连 uplink |
|
||||
|
||||
@@ -93,8 +93,8 @@ edge 上行时**不做** binding 初筛:所有已配置物理点都会采集
|
||||
→ 物理点 ID 自动加前缀:edge01/phy_modbus_s1_r40001
|
||||
4. [可选] BindingRegistry.loadLocal(bindings_local.json)
|
||||
5. UplinkClient + CommandExecutor 初始化
|
||||
6. DeviceFactory.createIngress → ModbusIngress 或 MockModbusIngress
|
||||
7. ModbusChannelManager.openChannel(逐通道 libmodbus 连接)
|
||||
6. DeviceFactory.createIngress → CompositeIngress(按 protocol 创建子 ingress)
|
||||
7. 各 ChannelManager.openChannel(libmodbus / SocketCAN / asio)
|
||||
8. uplink.connect();成功则 sendEdgeRegister(edgeId)
|
||||
→ 失败仅 WARN,edge 独立运行并后台重连
|
||||
```
|
||||
@@ -122,7 +122,11 @@ edge 上行时**不做** binding 初筛:所有已配置物理点都会采集
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `transport` | `rtu`(串口)或 `tcp`(Modbus TCP,需 `host` + `tcpPort`) |
|
||||
| `pollIntervalMs` | 该通道下所有点的轮询周期(当前整通道统一) |
|
||||
| `pollIntervalMs` | 通道默认轮询周期(点可覆盖) |
|
||||
| `interface` / `bitrate` | CANopen 通道:`can0`、`500000` |
|
||||
| `host` / `tcpPort` | Modbus TCP 或裸以太网 |
|
||||
|
||||
CANopen 通道示例见 `config/edge/physical_channels_can.json`。
|
||||
|
||||
### 3.4 物理设备与点(`physical_devices.json`)
|
||||
|
||||
@@ -141,7 +145,10 @@ edge 上行时**不做** binding 初筛:所有已配置物理点都会采集
|
||||
"function": 3,
|
||||
"scale": 0.1,
|
||||
"signalType": "AI",
|
||||
"writable": false
|
||||
"writable": false,
|
||||
"pollIntervalMs": 1000,
|
||||
"reportOnChange": true,
|
||||
"deadband": 0.5
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -149,8 +156,22 @@ edge 上行时**不做** binding 初筛:所有已配置物理点都会采集
|
||||
}
|
||||
```
|
||||
|
||||
**点级可选字段:**
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `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 热加载
|
||||
|
||||
| 信号 | 行为 |
|
||||
@@ -163,18 +184,38 @@ edge 上行时**不做** binding 初筛:所有已配置物理点都会采集
|
||||
|
||||
## 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 ← physical_*.json
|
||||
├── BindingRegistry (local) ← bindings_local.json(可选)
|
||||
├── ModbusChannelManager ← libmodbus RTU/TCP I/O
|
||||
├── IIngressPort
|
||||
│ ├── ModbusIngress ← 正常模式
|
||||
│ └── MockModbusIngress ← --mock
|
||||
├── ModbusEgress ← 下行写寄存器
|
||||
├── CommandExecutor ← ExecuteCommand → Modbus 写 / mock
|
||||
└── UplinkClient ← TcpUplinkClient 封装
|
||||
├── PhysicalRegistry
|
||||
├── CompositeIngress
|
||||
│ ├── ModbusIngress + ModbusPollScheduler / PollPlanner / ChangeFilter
|
||||
│ ├── CanIngress + NmtManager / PdoMapper / SdoClient
|
||||
│ └── EthIngress
|
||||
├── ModbusEgress / CanEgress / EthEgress
|
||||
├── CommandExecutor ← 按 protocol 分发写
|
||||
└── UplinkClient
|
||||
```
|
||||
|
||||
### 4.1 IIngressPort — 入站抽象
|
||||
@@ -185,9 +226,13 @@ main.cpp
|
||||
| `close()` | 关闭采集通道 |
|
||||
| `poll()` | 轮询,返回 `vector<RawPacket>` |
|
||||
|
||||
**ModbusIngress**:按 `pollIntervalMs` 遍历 `pollPoints()`,经 `ModbusChannelManager` 读寄存器,组装 Modbus 0x03 响应帧作为 `payload`。
|
||||
**ModbusIngress**:`PollPlanner` 调度到期点 → `ModbusPollScheduler` 合并相邻寄存器批量读 → `ChangeFilter` 变化过滤 → `RawPacket`。
|
||||
|
||||
**MockModbusIngress**:无硬件,生成递增值模拟帧。
|
||||
**CanIngress**:启动时 NMT Start + 加载 EDS;TPDO 监听 + SDO 慢点轮询;`protocolTag: "canopen"`。
|
||||
|
||||
**EthIngress**:裸 TCP/UDP 收字节;`protocolTag: "raw_eth"`。
|
||||
|
||||
**MockModbusIngress**:`--mock` 模式,无硬件模拟帧。
|
||||
|
||||
### 4.2 ModbusChannelManager — 硬件 I/O
|
||||
|
||||
@@ -214,10 +259,12 @@ main.cpp
|
||||
|
||||
### 4.4 CommandExecutor — 下行写
|
||||
|
||||
daemon 经 `bindings_global` 在命令中注入 `physicalPointId` + `edgeId`,edge 直接写 Modbus:
|
||||
daemon 经 `bindings_global` 在命令中注入 `physicalPointId` + `edgeId`,edge 按协议写:
|
||||
|
||||
```
|
||||
ExecuteCommand.params.physicalPointId → PhysicalRegistry → ModbusEgress.write
|
||||
modbus → ModbusEgress
|
||||
canopen → CanEgress (SDO download)
|
||||
raw → EthEgress
|
||||
```
|
||||
|
||||
仅当命令**缺少** `physicalPointId` 时,才用 `bindings_local.json` 做 `logicalPointId → physicalPointId` 备用解析。
|
||||
@@ -310,8 +357,10 @@ while (!shouldStop) {
|
||||
| `softbus_registry` | BindingRegistry(仅 local 可选) |
|
||||
| `softbus_transport` | TcpUplinkClient、UplinkWire |
|
||||
| `softbus_api` | ExecuteCommand、SoftbusAck |
|
||||
| **libmodbus** | Modbus RTU/TCP 硬件 I/O(vendor 或系统包) |
|
||||
| asio | TcpUplinkClient 底层 |
|
||||
| **libmodbus** | Modbus RTU/TCP 硬件 I/O |
|
||||
| **SocketCAN** | Linux `linux/can.h`(`SOFTBUS_HAS_SOCKETCAN`) |
|
||||
| **softbus_canopen_common** | EDS 解析、对象字典 |
|
||||
| asio | Uplink + EthChannel |
|
||||
|
||||
---
|
||||
|
||||
@@ -351,68 +400,46 @@ kill -HUP $(pidof softbus_edge)
|
||||
|
||||
---
|
||||
|
||||
## 9. 未来扩展:CANopen 与以太网设备
|
||||
## 9. 多协议与物理层优化(已实现)
|
||||
|
||||
当前仅 **Modbus RTU/TCP** 在 edge 侧有完整 ingress/egress。CANopen 与更多以太网协议按同一双域模型扩展。
|
||||
### 9.1 多 Ingress 并存
|
||||
|
||||
### 9.1 目标架构
|
||||
`physical_channels.json` 中可混合 `protocol: modbus | canopen | raw`,`DeviceFactory` 为每种协议创建子 ingress,由 `CompositeIngress` 统一 `poll()`。
|
||||
|
||||
```
|
||||
physical_channels.json ──→ ChannelDriver(按 protocol 选择)
|
||||
physical_devices.json ──→ PhysicalRegistry
|
||||
│
|
||||
┌───────────┼───────────┐
|
||||
▼ ▼ ▼
|
||||
ModbusIngress CanIngress EthIngress(规划)
|
||||
│ │ │
|
||||
└───────────┴───────────┘
|
||||
▼
|
||||
RawPacket → UplinkClient
|
||||
```
|
||||
### 9.2 Modbus 采集优化
|
||||
|
||||
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 全量 |
|
||||
| **ModbusPollScheduler** | 同 (channel, slave, function) 下合并相邻寄存器,单次最多 125 寄存器 |
|
||||
| **PollPlanner** | 每点独立 `pollIntervalMs`(缺省继承通道) |
|
||||
| **ChangeFilter** | `reportOnChange` + `deadband` 物理层过滤 |
|
||||
|
||||
### 9.3 以太网设备(规划)
|
||||
### 9.3 CANopen(edge 已实现)
|
||||
|
||||
| 类型 | edge 侧 | 说明 |
|
||||
|------|---------|------|
|
||||
| **Modbus TCP** | 已支持 | `physical_channels` 设 `transport: tcp`,`host` + `tcpPort` |
|
||||
| **裸 TCP / 自定义帧** | `EthIngress`(规划) | 按 channel 配置帧格式或插件化 driver |
|
||||
| **UDP 组播** | 待定 | 适用于部分工业相机/传感器 |
|
||||
| 组件 | 说明 |
|
||||
|------|------|
|
||||
| **CanChannelManager** | SocketCAN 收发(无头文件时模拟降级) |
|
||||
| **NmtManager** | 启动时 NMT Start Remote Node |
|
||||
| **EdsDcfLoader** | `softbus_canopen_common` 解析 EDS |
|
||||
| **PdoMapper** | TPDO 订阅表 + 帧解包 |
|
||||
| **SdoClient** | SDO upload(慢点)/ download(下行写) |
|
||||
| **daemon** | `protocol_canopen.so` 解析仍占位 |
|
||||
|
||||
以太网通道示例(Modbus TCP,已可用):
|
||||
CAN 前置:`sudo ip link set can0 up type can bitrate 500000`
|
||||
|
||||
```json
|
||||
{
|
||||
"channelId": "eth_modbus_0",
|
||||
"protocol": "modbus",
|
||||
"transport": "tcp",
|
||||
"host": "192.168.1.10",
|
||||
"tcpPort": 502,
|
||||
"pollIntervalMs": 200
|
||||
}
|
||||
```
|
||||
### 9.4 裸以太网(骨架)
|
||||
|
||||
### 9.4 edge 侧待增强(Modbus 及通用)
|
||||
`protocol: "raw"` + `transport: tcp|udp` → `EthIngress` 原样上行;`EthEgress` 支持原始字节写。
|
||||
|
||||
Modbus TCP 仍走 `protocol: modbus` + `transport: tcp`。
|
||||
|
||||
### 9.5 待增强
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| **ModbusPollScheduler** | 合并相邻寄存器读,减少串口占用 |
|
||||
| **按点 pollInterval** | 不同信号类型不同采集周期 |
|
||||
| **变化上报 / deadband** | 物理层过滤,减少 uplink 流量 |
|
||||
| **DeviceDiscovery** | 总线扫描、动态写入 `physical_devices` |
|
||||
| **多 ingress 并存** | 同一 edge 上 Modbus + CANopen 并行 |
|
||||
|
||||
上述优化均在**物理域**完成,不改变 daemon 双域模型。
|
||||
| **daemon CANopen 解析** | `CanopenParser` 完整实现 |
|
||||
| **Eth 帧解析** | 自定义帧格式插件化 |
|
||||
|
||||
---
|
||||
|
||||
@@ -430,6 +457,7 @@ daemon 侧已有对应解析插件(`protocol_modbus`、`protocol_canopen`)
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| [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 |
|
||||
|
||||
Reference in New Issue
Block a user