-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from aldas/rtu_support
add support to Modbus RTU
- Loading branch information
Showing
13 changed files
with
274 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
use ModbusTcpClient\Network\BinaryStreamConnection; | ||
use ModbusTcpClient\Packet\ModbusFunction\ReadHoldingRegistersRequest; | ||
use ModbusTcpClient\Packet\RtuConverter; | ||
|
||
require __DIR__ . '/../vendor/autoload.php'; | ||
|
||
$connection = BinaryStreamConnection::getBuilder() | ||
->setPort(502) | ||
->setHost('127.0.0.1') | ||
->setReadTimeoutSec(3) // increase read timeout to 3 seconds | ||
->build(); | ||
|
||
$startAddress = 256; | ||
$quantity = 6; | ||
$slaveId = 1; // RTU packet slave id equivalent is Modbus TCP unitId | ||
|
||
$tcpPacket = new ReadHoldingRegistersRequest($startAddress, $quantity, $slaveId); | ||
$rtuPacket = RtuConverter::toRtu($tcpPacket); | ||
|
||
try { | ||
$binaryData = $connection->connect()->sendAndReceive($rtuPacket); | ||
echo 'RTU Binary received (in hex): ' . unpack('H*', $binaryData)[1] . PHP_EOL; | ||
|
||
$response = RtuConverter::fromRtu($binaryData); | ||
echo 'Parsed packet (in hex): ' . $response->toHex() . PHP_EOL; | ||
echo 'Data parsed from packet (bytes):' . PHP_EOL; | ||
print_r($response->getData()); | ||
|
||
} catch (Exception $exception) { | ||
echo 'An exception occurred' . PHP_EOL; | ||
echo $exception->getMessage() . PHP_EOL; | ||
echo $exception->getTraceAsString() . PHP_EOL; | ||
} finally { | ||
$connection->close(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php | ||
|
||
namespace ModbusTcpClient\Packet; | ||
|
||
|
||
use ModbusTcpClient\Exception\ParseException; | ||
use ModbusTcpClient\Utils\Types; | ||
|
||
/** | ||
* Converts Modbus TCP/IP packet to/from Modbus RTU packet. | ||
* | ||
* Read differences between Modbus RTU and Modbus TCP/IP packet http://www.simplymodbus.ca/TCP.htm | ||
* | ||
* Difference is: | ||
* 1. RTU header contains only slave id. TCP/IP header contains of transaction id, protocol id, length, unitid | ||
* 2. RTU packed has CRC16 appended | ||
* | ||
* NB: RTU slave id equivalent is TCP/IP packet unit id | ||
*/ | ||
final class RtuConverter | ||
{ | ||
private function __construct() | ||
{ | ||
// utility class | ||
} | ||
|
||
/** | ||
* Convert Modbus TCP request instance to Modbus RTU binary packet | ||
* | ||
* @param ModbusRequest $request request to be converted | ||
* @return string Modbus RTU request in binary form | ||
*/ | ||
public static function toRtu(ModbusRequest $request): string | ||
{ | ||
// trim 6 bytes: 2 bytes for transaction id + 2 bytes for protocol id + 2 bytes for data length field | ||
$packet = substr((string)$request, 6); | ||
return $packet . self::crc16($packet); | ||
} | ||
|
||
/** | ||
* Converts binary string containing RTU response packet to Modbus TCP response instance | ||
* | ||
* @param string $binaryData rtu binary response | ||
* @param array $options option to use during conversion | ||
* @return ModbusResponse converted Modbus TCP packet | ||
* @throws \ModbusTcpClient\Exception\ParseException | ||
* @throws \Exception if it was not possible to gather sufficient entropy | ||
*/ | ||
public static function fromRtu(string $binaryData, array $options = []): ModbusResponse | ||
{ | ||
$data = substr($binaryData, 0, -2); // remove and crc | ||
|
||
if ((bool)($options['no_crc_check'] ?? false) === false) { | ||
$originalCrc = substr($binaryData, -2); | ||
$calculatedCrc = self::crc16($data); | ||
if ($originalCrc !== $calculatedCrc) { | ||
throw new ParseException( | ||
sprintf('Packet crc (\x%s) does not match calculated crc (\x%s)!', | ||
bin2hex($originalCrc), | ||
bin2hex($calculatedCrc) | ||
) | ||
); | ||
} | ||
} | ||
|
||
$packet = b'' | ||
. Types::toRegister(random_int(0, Types::MAX_VALUE_UINT16)) // 2 bytes for transaction id | ||
. "\x00\x00" // 2 bytes for protocol id | ||
. Types::toRegister(strlen($data)) // 2 bytes for data length field | ||
. $data; | ||
|
||
return ResponseFactory::parseResponse($packet); | ||
} | ||
|
||
private static function crc16(string $string): string | ||
{ | ||
$crc = 0xFFFF; | ||
for ($x = 0, $xMax = \strlen($string); $x < $xMax; $x++) { | ||
$crc ^= \ord($string[$x]); | ||
for ($y = 0; $y < 8; $y++) { | ||
if (($crc & 0x0001) === 0x0001) { | ||
$crc = (($crc >> 1) ^ 0xA001); | ||
} else { | ||
$crc >>= 1; | ||
} | ||
} | ||
} | ||
|
||
return \chr($crc & 0xFF) . \chr($crc >> 8); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.