You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过PHP+Socket读取Teltonika TRB145网关的电力数据?

如何通过PHP+Socket读取Teltonika TRB145网关的电力数据?

看起来你已经推进了不少步骤——网关能成功连到你的PHP服务器,但就是拿不到电表的Modbus数据,这种“差一步”的情况确实挺闹心的。我来结合你的问题和代码,拆解下核心问题和调整方向:

首先搞懂核心误解:Serial Over IP的本质是「RTU透传」,不是Modbus TCP

Teltonika TRB145的Serial Over IP功能,简单说就是把RS485串口的Modbus RTU数据通过TCP通道直接透传给你的服务器,它不会帮你把RTU转换成Modbus TCP格式。而你现在的代码用的是ModbusTcpClient库来构造TCP格式的请求,这相当于给只懂RTU的电表发了一堆它完全看不懂的“外星文”,自然不会有回应。

你的代码需要做这几个关键调整

1. 把Modbus请求从TCP格式改成RTU格式

Modbus RTU的帧结构没有TCP的MBAP头(事务ID、协议ID、长度组成的头),而是由「设备地址字节 + 功能码字节 + 数据段 + CRC16校验(2字节)」组成。你需要构造这种格式的请求,发给网关(网关会透传给电表)。

比如,构造一个读取地址为1的电表、起始寄存器0、共10个保持寄存器的RTU请求:

// 电表的Modbus地址(比如1)
$deviceAddress = 1;
// 功能码:读取保持寄存器是0x03
$functionCode = 0x03;
// 起始寄存器地址(大端字节序)
$startAddress = 0;
// 要读取的寄存器数量(大端字节序)
$registerCount = 10;

// 组装RTU请求的核心数据段
$dataSegment = pack('n*', $startAddress, $registerCount);
// 组装完整帧(地址+功能码+数据段)
$rtuFrame = pack('CC', $deviceAddress, $functionCode) . $dataSegment;
// 计算Modbus RTU的CRC16校验
function calculateModbusCrc16($data) {
    $crc = 0xFFFF;
    for ($i = 0; $i < strlen($data); $i++) {
        $crc ^= ord($data[$i]);
        for ($j = 0; $j < 8; $j++) {
            if ($crc & 0x0001) {
                $crc >>= 1;
                $crc ^= 0xA001;
            } else {
                $crc >>= 1;
            }
        }
    }
    // CRC结果是低字节在前,高字节在后
    return pack('v', $crc);
}
// 拼接校验码,得到最终的RTU请求包
$rtuRequest = $rtuFrame . calculateModbusCrc16($rtuFrame);

// 在连接建立后,发送这个请求
$conn->write($rtuRequest);

2. 处理TCP流的「粘包/拆包」问题

TCP是流协议,你收到的$data可能是不完整的RTU帧,或者多个帧粘在一起。你需要缓存收到的数据,通过CRC校验来识别完整的帧,再解析:

在你的data事件处理逻辑里修改:

$receivedData .= $data;
// 循环处理缓存中的数据,直到找不到完整帧
while (strlen($receivedData) >= 4) { // RTU帧最小长度:1+1+2=4字节
    $frameFound = false;
    // 尝试从缓存中找到完整的RTU帧(通过CRC校验)
    for ($frameLength = 4; $frameLength <= strlen($receivedData); $frameLength++) {
        $candidateFrame = substr($receivedData, 0, $frameLength);
        // 计算候选帧的CRC(去掉最后2字节的校验码)
        $calculatedCrc = calculateModbusCrc16(substr($candidateFrame, 0, $frameLength - 2));
        // 取出候选帧末尾的2字节作为收到的CRC
        $receivedCrc = substr($candidateFrame, -2);
        
        if ($calculatedCrc === $receivedCrc) {
            // 找到完整帧,解析它
            parseRtuResponse($candidateFrame);
            // 从缓存中移除已处理的帧
            $receivedData = substr($receivedData, $frameLength);
            $frameFound = true;
            break;
        }
    }
    if (!$frameFound) {
        // 没有找到完整帧,继续等待后续数据
        break;
    }
}

// 新增解析RTU响应的函数
function parseRtuResponse($frame) {
    global $logger;
    $deviceAddress = ord(substr($frame, 0, 1));
    $functionCode = ord(substr($frame, 1, 1));
    
    if ($functionCode & 0x80) {
        // 功能码最高位为1,说明是错误响应
        $errorCode = ord(substr($frame, 2, 1));
        $logger->error("Device {$deviceAddress} returned error: code {$errorCode}");
        return;
    }
    
    // 以读取保持寄存器的响应为例,解析数据
    if ($functionCode === 0x03) {
        $byteCount = ord(substr($frame, 2, 1));
        $registerData = substr($frame, 3, $byteCount);
        $registerCount = $byteCount / 2; // 每个寄存器2字节
        
        $registers = [];
        for ($i = 0; $i < $registerCount; $i++) {
            // 把2字节的大端数据转成整数
            $regValue = unpack('n', substr($registerData, $i*2, 2))[1];
            $registers[] = $regValue;
        }
        $logger->info("Device {$deviceAddress} returned registers: " . implode(', ', $registers));
    }
}

3. 移除不需要的Modbus TCP相关代码

你现在引入的ModbusTcpClient\Packet\ModbusApplicationHeader、ReadHoldingRegistersRequest这些都是Modbus TCP的类,在RTU透传场景下完全用不上,可以直接删掉,减少代码复杂度。同时注意修正require 'vendor/autoload.php'的换行语法错误。

先做「前置排查」,排除硬件/配置问题

在调整代码之前,先用工具验证网关和电表的通信是否正常:

  1. 用「Modbus Poll」这类工具,设置为RTU over TCP模式,连接到你的服务器监听的端口(或者直接连网关的Serial Over IP端口,以网关配置为准)。
  2. 发送和你代码中相同的Modbus请求,如果能拿到数据,说明网关、电表、接线都是正常的,问题确实在代码;如果拿不到,先排查:
    • 网关的RS485参数(波特率、数据位、停止位、奇偶校验)是否和电表完全一致;
    • 网关的Serial Over IP配置是否正确(客户端模式、目标IP/端口和你的服务器一致);
    • 电表的Modbus地址是否正确,RS485接线是否接对(A/A、B/B,不要反接)。

最后再检查你的代码逻辑

  • 确保你的PHP CLI脚本有足够的权限,并且依赖包(比如React Socket)已经正确安装;
  • 连接建立后,不要只发Server ready:{}\n,要马上发送RTU请求——电表不会主动推送数据,必须你主动请求;
  • 开启调试日志,打印所有收到的原始数据(用unpack('H*', $data)转成十六进制),方便排查是没收到数据,还是收到了但解析错误。

备注:内容来源于stack exchange,提问作者user27972404

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 08:38:00