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

9.8 KiB
Raw Blame History

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.jsonphy_modbus_tcp_slave_255
  • 通道:config/edge/physical_channels.jsoneth_modbus_tcp_0transport: "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 层:TcpRtuSessionRTU 帧跑在 TCP 上)

自定义协议分支在 ModbusChannelManager 内,由 transport 字段触发,上层 ModbusIngress 无感知。

physical_devices.json (点)
        ↓
ModbusIngress统一采集
        ↓
ModbusChannelManager::openChannel / readRegisters
        ├─ transport: rtu     → libmodbus 串口
        ├─ transport: tcp     → libmodbus 标准 TCP
        └─ transport: tcp_rtu → TcpRtuSession

2. 配置锚点

{
  "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
    }
  ]
}
{
  "channelId": "eth_modbus_tcp_0",
  "protocol": "modbus",
  "transport": "tcp_rtu",
  "host": "192.168.31.253",
  "tcpPort": 502
}

3. 阶段 A启动一次性

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 解析,qualifyPointIdedge01/phy_modbus_tcp_s1_r40001
4 PhysicalRegistry::rebuildPollIndex PhysicalRegistry.cpp:142-145 组装 ResolvedPhysicalPoint{point, device, channel} 写入 pointIndex_
5 EdgeRuntime::rebuildIngress EdgeRuntime.cpp:16-19 createIngressingress_->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_rtuTcpRtuSession::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:76EdgeRuntime::tick()pollIngress()ModbusIngress::poll()

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. 自定义协议触发点(仅两处)

打开通道(一次性):

    if (isTcpRtuTransport(channel)) {
        handle.tcpRtu = std::make_unique<TcpRtuSession>();
        if (!handle.tcpRtu->connect(channel.host, channel.tcpPort)) {
            // ...
        }

读寄存器(每次轮询):

    if (it->second.tcpRtu) {
        return it->second.tcpRtu->readHoldingRegisters(
            point.device.slaveId, startAddr, count);

ModbusIngressModbusIngress.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. 相关文档