# 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(); 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 线协议