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

252 lines
9.8 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.
# 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 TCPMBAP |
| `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 线协议