-
Notifications
You must be signed in to change notification settings - Fork 57
/
Packet.php
95 lines (85 loc) · 3.98 KB
/
Packet.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?php
declare(strict_types=1);
namespace ModbusTcpClient\Utils;
use ModbusTcpClient\Network\IOException;
use ModbusTcpClient\Packet\ErrorResponse;
use ModbusTcpClient\Packet\ModbusPacket;
final class Packet
{
private function __construct()
{
// no access, this is an utility class
}
/**
* isCompleteLength checks if binary string is complete modbus TCP packet
* NB: this function works only for MODBUS TCP packets (request/response)
*
* @param string|null $binaryData binary string to be checked
* @return bool true if data is actual modbus TCP packet
*/
public static function isCompleteLength(string|null $binaryData): bool
{
// minimal amount is 8 bytes (header + function code)
$length = strlen($binaryData);
if ($length < 8) {
return false;
}
// modbus header 6 bytes are = transaction id + protocol id + length of PDU part.
// so adding these number is what complete packet would be
$expectedLength = 6 + unpack('n', ($binaryData[4] . $binaryData[5]))[1];
if ($length > $expectedLength) {
return false;
}
return $length === $expectedLength;
}
/**
* isCompleteLengthRTU checks if binary string is complete modbus RTU response packet
* NB: this function works only for MODBUS RTU response packets
*
* @param string|null $binaryData binary string to be checked
* @return bool true if data is actual error packet
*/
public static function isCompleteLengthRTU(string|null $binaryData): bool
{
// minimal RTU packet length is 5 bytes (1 byte unit id + 1 byte function code + 1 byte of error code or byte length + 2 bytes for CRC)
$length = strlen($binaryData);
if ($length < 5) {
return false;
}
$functionCode = ord($binaryData[1]);
if (($functionCode & ErrorResponse::EXCEPTION_BITMASK) > 0) { // seems to be error response
return true;
}
switch ($functionCode) {
case ModbusPacket::READ_COILS: //
case ModbusPacket::READ_INPUT_DISCRETES: //
case ModbusPacket::READ_HOLDING_REGISTERS: //
case ModbusPacket::READ_INPUT_REGISTERS: //
case ModbusPacket::READ_WRITE_MULTIPLE_REGISTERS: //
// if it is not error response then 3rd byte contains data length in bytes
// trailing 3 bytes are = unit id (1) + function code (1) + data length in bytes (1) + (N)
// next is N bytes of data that should match 3rd byte value
$responseBytesLen = 3 + ord($binaryData[2]);
break;
case ModbusPacket::WRITE_SINGLE_COIL: // unit id (1) + function code (1) + start address (2) + coil data (2)
case ModbusPacket::WRITE_SINGLE_REGISTER: // unit id (1) + function code (1) + start address (2) + register data (2)
case ModbusPacket::WRITE_MULTIPLE_COILS: // unit id (1) + function code (1) + start address (2) + count of coils written (2)
case ModbusPacket::WRITE_MULTIPLE_REGISTERS: // unit id (1) + function code (1) + start address (2) + count of registers written (2)
$responseBytesLen = 6;
break;
case ModbusPacket::MASK_WRITE_REGISTER:
$responseBytesLen = 8; // unit id (1) + function code (1) + start address (2) + AND mask (2) + OR mask (2)
break;
case ModbusPacket::GET_COMM_EVENT_COUNTER: // unit id (1) + function code (1) + status (2) + count (2)
$responseBytesLen = 6;
break;
default:
throw new IOException('can not determine complete length for unsupported modbus function code');
}
$expectedLength = $responseBytesLen + 2; // and 2 bytes for CRC
if ($length > $expectedLength) {
throw new IOException('packet length more bytes than expected');
}
return $length === $expectedLength;
}
}