-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for FC17 Report server ID
- Loading branch information
Showing
11 changed files
with
515 additions
and
9 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
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,63 @@ | ||
<?php | ||
|
||
namespace ModbusTcpClient\Packet\ModbusFunction; | ||
|
||
use ModbusTcpClient\Packet\ErrorResponse; | ||
use ModbusTcpClient\Packet\ModbusPacket; | ||
use ModbusTcpClient\Packet\ModbusRequest; | ||
use ModbusTcpClient\Packet\ProtocolDataUnit; | ||
use ModbusTcpClient\Utils\Types; | ||
|
||
/** | ||
* Request for Report Server ID (FC=17, 0x11) | ||
* | ||
* Example packet: \x81\x80\x00\x00\x00\x02\x10\x11 | ||
* \x81\x80 - transaction id | ||
* \x00\x00 - protocol id | ||
* \x00\x08 - number of bytes in the message (PDU = ProtocolDataUnit) to follow | ||
* \x10 - unit id | ||
* \x11 - function code | ||
* | ||
*/ | ||
class ReportServerIDRequest extends ProtocolDataUnit implements ModbusRequest | ||
{ | ||
public function __construct(int $unitId = 0, int $transactionId = null) | ||
{ | ||
parent::__construct($unitId, $transactionId); | ||
} | ||
|
||
public function getFunctionCode(): int | ||
{ | ||
return ModbusPacket::REPORT_SERVER_ID; // 17 (0x11) | ||
} | ||
|
||
protected function getLengthInternal(): int | ||
{ | ||
return 1; // size of function code (1 byte) | ||
} | ||
|
||
public function __toString(): string | ||
{ | ||
return b'' | ||
. $this->getHeader()->__toString() | ||
. Types::toByte($this->getFunctionCode()); | ||
} | ||
|
||
/** | ||
* Parses binary string to ReportServerIDRequest or return ErrorResponse on failure | ||
* | ||
* @param string $binaryString | ||
* @return ReportServerIDRequest|ErrorResponse | ||
*/ | ||
public static function parse(string $binaryString): ReportServerIDRequest|ErrorResponse | ||
{ | ||
return self::parsePacket( | ||
$binaryString, | ||
8, | ||
ModbusPacket::REPORT_SERVER_ID, | ||
function (int $transactionId, int $unitId) { | ||
return new self($unitId, $transactionId); | ||
} | ||
); | ||
} | ||
} |
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,138 @@ | ||
<?php | ||
|
||
namespace ModbusTcpClient\Packet\ModbusFunction; | ||
|
||
use ModbusTcpClient\Exception\InvalidArgumentException; | ||
use ModbusTcpClient\Packet\ModbusPacket; | ||
use ModbusTcpClient\Packet\ModbusResponse; | ||
use ModbusTcpClient\Packet\ProtocolDataUnit; | ||
use ModbusTcpClient\Utils\Types; | ||
|
||
/** | ||
* Response for Report Server ID (FC=17, 0x11) | ||
* | ||
* Example packet: \x81\x80\x00\x00\x00\x08\x10\x11\x02\x01\x02\x00\x01\x02 | ||
* \x81\x80 - transaction id | ||
* \x00\x00 - protocol id | ||
* \x00\x08 - number of bytes in the message (PDU = ProtocolDataUnit) to follow | ||
* \x10 - unit id | ||
* \x11 - function code | ||
* \x02 - byte count for server id | ||
* \x01\x02 - N bytes for server id (device specific, variable length) | ||
* \x00 - run status | ||
* \x01\x02 - optional N bytes for additional data (device specific, variable length) | ||
* | ||
*/ | ||
class ReportServerIDResponse extends ProtocolDataUnit implements ModbusResponse | ||
{ | ||
/** | ||
* @var string server ID bytes as binary string | ||
*/ | ||
private string $serverID; | ||
|
||
/** | ||
* @var int run status | ||
*/ | ||
private int $status; | ||
|
||
/** | ||
* @var string|null additional data (optional) | ||
*/ | ||
private ?string $additionalData = null; | ||
|
||
public function __construct(string $rawData, int $unitId = 0, int $transactionId = null) | ||
{ | ||
$serverIDLength = Types::parseByte($rawData[0]); | ||
if (strlen($rawData) < ($serverIDLength + 2)) { | ||
throw new InvalidArgumentException("too few bytes to be a complete report server id packet"); | ||
} | ||
$this->serverID = substr($rawData, 1, $serverIDLength); | ||
$this->status = Types::parseByte($rawData[$serverIDLength + 1]); | ||
if (strlen($rawData) > ($serverIDLength + 2)) { | ||
$this->additionalData = substr($rawData, $serverIDLength + 2); | ||
} | ||
|
||
parent::__construct($unitId, $transactionId); | ||
|
||
} | ||
|
||
public function getStatus(): int | ||
{ | ||
return $this->status; | ||
} | ||
|
||
/** | ||
* Server ID value as binary string | ||
* @return string | ||
*/ | ||
public function getServerID(): string | ||
{ | ||
return $this->serverID; | ||
} | ||
|
||
/** | ||
* @return int[] | ||
*/ | ||
public function getServerIDBytes(): array | ||
{ | ||
return Types::parseByteArray($this->serverID); | ||
} | ||
|
||
public function getAdditionalData(): ?string | ||
{ | ||
return $this->additionalData; | ||
} | ||
|
||
/** | ||
* @return int[] | ||
*/ | ||
public function getAdditionalDataBytes(): array | ||
{ | ||
if ($this->additionalData) { | ||
return Types::parseByteArray($this->additionalData); | ||
} | ||
return []; | ||
} | ||
|
||
public function getFunctionCode(): int | ||
{ | ||
return ModbusPacket::REPORT_SERVER_ID; // 17 (0x11) | ||
} | ||
|
||
public function __toString(): string | ||
{ | ||
$serverID = $this->getServerID(); | ||
$additionalData = $this->getAdditionalData(); | ||
|
||
$result = b'' | ||
. $this->getHeader()->__toString() | ||
. Types::toByte($this->getFunctionCode()) | ||
. Types::toByte(strlen($serverID)) | ||
. $serverID | ||
. Types::toByte($this->getStatus()); | ||
if ($additionalData !== null) { | ||
$result .= $additionalData; | ||
} | ||
return $result; | ||
} | ||
|
||
protected function getLengthInternal(): int | ||
{ | ||
$serverIDLength = strlen($this->getServerID()); | ||
$additionalDataLength = strlen($this->getAdditionalData()); | ||
// size of function code (1 byte) + | ||
// server id byte count (1 byte) + | ||
// server id value bytes (N bytes) + | ||
// status (1 byte) + | ||
// additional data bytes (N bytes) | ||
return 3 + $serverIDLength + $additionalDataLength; | ||
} | ||
|
||
public function withStartAddress(int $startAddress): static | ||
{ | ||
// Note: I am being stupid and stubborn here. Somehow `ModbusResponse` interface ended up having this method | ||
// and want this response to work with ResponseFactory::parseResponse method. | ||
// TODO: change ModbusResponse interface or ResponseFactory::parseResponse signature | ||
return clone $this; | ||
} | ||
} |
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
111 changes: 111 additions & 0 deletions
111
tests/unit/Packet/ModbusFunction/ReportServerIDRequestTest.php
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,111 @@ | ||
<?php | ||
|
||
namespace Tests\unit\Packet\ModbusFunction; | ||
|
||
use ModbusTcpClient\Packet\ErrorResponse; | ||
use ModbusTcpClient\Packet\ModbusFunction\ReportServerIDRequest; | ||
use ModbusTcpClient\Packet\ModbusPacket; | ||
use ModbusTcpClient\Utils\Endian; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class ReportServerIDRequestTest extends TestCase | ||
{ | ||
protected function setUp(): void | ||
{ | ||
Endian::$defaultEndian = Endian::LITTLE_ENDIAN; // packets are big endian. setting to default to little should not change output | ||
} | ||
|
||
protected function tearDown(): void | ||
{ | ||
Endian::$defaultEndian = Endian::BIG_ENDIAN_LOW_WORD_FIRST; | ||
} | ||
|
||
public function testOnPacketToString() | ||
{ | ||
// Field: Size in packet | ||
$payload = "\x01\x38" . // transaction id: 0138 (2 bytes) | ||
"\x00\x00" . // protocol id: 0000 (2 bytes) | ||
"\x00\x02" . // length: 0002 (2 bytes) (2 bytes after this field) | ||
"\x11" . // unit id: 11 (1 byte) | ||
"\x11" . // function code: 11 (1 byte) | ||
''; | ||
$this->assertEquals( | ||
$payload, | ||
(new ReportServerIDRequest( | ||
0x11, | ||
0x0138, | ||
))->__toString() | ||
); | ||
} | ||
|
||
public function testOnPacketProperties() | ||
{ | ||
$packet = new ReportServerIDRequest( | ||
0x11, | ||
0x0138 | ||
); | ||
$this->assertEquals(ModbusPacket::REPORT_SERVER_ID, $packet->getFunctionCode()); | ||
|
||
$header = $packet->getHeader(); | ||
$this->assertEquals(0x0138, $header->getTransactionId()); | ||
$this->assertEquals(0, $header->getProtocolId()); | ||
$this->assertEquals(2, $header->getLength()); | ||
$this->assertEquals(0x11, $header->getUnitId()); | ||
} | ||
|
||
public function testParse() | ||
{ | ||
// Field: Size in packet | ||
$payload = "\x01\x38" . // transaction id: 0138 (2 bytes) | ||
"\x00\x00" . // protocol id: 0000 (2 bytes) | ||
"\x00\x02" . // length: 0002 (2 bytes) (2 bytes after this field) | ||
"\x11" . // unit id: 11 (1 byte) | ||
"\x11" . // function code: 11 (1 byte) | ||
''; | ||
$packet = ReportServerIDRequest::parse($payload); | ||
$this->assertEquals($packet, (new ReportServerIDRequest( | ||
0x11, | ||
0x0138 | ||
))->__toString()); | ||
$this->assertEquals(ModbusPacket::REPORT_SERVER_ID, $packet->getFunctionCode()); | ||
|
||
$header = $packet->getHeader(); | ||
$this->assertEquals(0x0138, $header->getTransactionId()); | ||
$this->assertEquals(0, $header->getProtocolId()); | ||
$this->assertEquals(2, $header->getLength()); | ||
$this->assertEquals(0x11, $header->getUnitId()); | ||
} | ||
|
||
public function testParseShouldReturnErrorResponseForTooShortPacket() | ||
{ | ||
// Field: Size in packet | ||
$payload = "\x01\x38" . // transaction id: 0138 (2 bytes) | ||
"\x00\x00" . // protocol id: 0000 (2 bytes) | ||
"\x00\x01" . // length: 0001 (2 bytes) (2 bytes after this field) | ||
"\x11" . // unit id: 11 (1 byte) | ||
"\x11" . // function code: 11 (1 byte) | ||
''; | ||
$packet = ReportServerIDRequest::parse($payload); | ||
self::assertInstanceOf(ErrorResponse::class, $packet); | ||
$toString = $packet->__toString(); | ||
// transaction id is random | ||
$toString[0] = "\x00"; | ||
$toString[1] = "\x00"; | ||
self::assertEquals("\x00\x00\x00\x00\x00\x03\x11\x91\x03", $toString); | ||
} | ||
|
||
public function testParseShouldReturnErrorResponseForInvalidFunction() | ||
{ | ||
// Field: Size in packet | ||
$payload = "\x01\x38" . // transaction id: 0138 (2 bytes) | ||
"\x00\x00" . // protocol id: 0000 (2 bytes) | ||
"\x00\x02" . // length: 0002 (2 bytes) (2 bytes after this field) | ||
"\x11" . // unit id: 11 (1 byte) | ||
"\x01" . // function code: 01 (1 byte) <-- should be 0x11 | ||
''; | ||
$packet = ReportServerIDRequest::parse($payload); | ||
self::assertInstanceOf(ErrorResponse::class, $packet); | ||
$toString = $packet->__toString(); | ||
self::assertEquals("\x01\x38\x00\x00\x00\x03\x11\x91\x01", $toString); | ||
} | ||
} |
Oops, something went wrong.