252 lines
9.8 KiB
Markdown
252 lines
9.8 KiB
Markdown
# 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 线协议
|