9.8 KiB
9.8 KiB
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. 配置锚点
{
"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 解析,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()。
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);
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 — edge 总览
- edge_device_setup.md — 配置与启动
- UPLINK_WIRE_PROTOCOL.md — uplink 线协议