Skip to content

Commit

Permalink
update documentation and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
aldas committed Oct 4, 2020
1 parent f428875 commit d62f769
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 65 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.1.0] - 2020-10-04

### Added

* Added functions to check is received data is complete/error Modbus TCP packet (PR #63)
* [Packet::isCompleteLength](src/Utils/Packet.php)
* [ErrorResponse::is](src/Packet/ErrorResponse.php)

### Changed

* Changed `StreamHandler` to read data until complete packet is received. Previously we read only once from stream
and naively assumed that Modbus TCP packet can not be fragmented over multiple TCP packets/ multiple stream reads.
This fixed #61.

## [2.0.1] - 2020-04-12

### Security
Expand Down
58 changes: 8 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ composer require aldas/modbus-tcp-client
* FC16 - Write Multiple Registers ([WriteMultipleRegistersRequest](src/Packet/ModbusFunction/WriteMultipleRegistersRequest.php) / [WriteMultipleRegistersResponse](src/Packet/ModbusFunction/WriteMultipleRegistersResponse.php))
* FC23 - Read / Write Multiple Registers ([ReadWriteMultipleRegistersRequest](src/Packet/ModbusFunction/ReadWriteMultipleRegistersRequest.php) / [ReadWriteMultipleRegistersResponse](src/Packet/ModbusFunction/ReadWriteMultipleRegistersResponse.php))

### Utility functions

* [Packet::isCompleteLength](src/Utils/Packet.php) - checks if data is complete Modbus TCP packet
* [ErrorResponse::is](src/Packet/ErrorResponse.php) - checks if data is Modbus TCP error packet

## Requirements

* PHP 7.0+
Expand Down Expand Up @@ -152,57 +157,10 @@ $responseAsTcpPacket = RtuConverter::fromRtu($binaryData);
See Linux example in 'examples/[rtu_usb_to_serial.php](examples/rtu_usb_to_serial.php)'


## Example of non-blocking socket IO (i.e. modbus request are run in 'parallel')

Example of non-blocking socket IO with https://github.com/amphp/socket

```php
/**
* Install dependency with 'composer require amphp/socket'
*
* This will do 'parallel' socket request with help of Amp socket library
*/

require __DIR__ . '/../vendor/autoload.php';

use ModbusTcpClient\Packet\ModbusFunction\ReadHoldingRegistersRequest;
use ModbusTcpClient\Packet\ResponseFactory;
use function Amp\Socket\connect;


$uri = 'tcp://127.0.0.1:502';
$packets = [
new ReadHoldingRegistersRequest(256, 10),
new ReadHoldingRegistersRequest(266, 10),
];

$promises = [];
foreach ($packets as $packet) {
$promises[] = Amp\call(function () use ($packet, $uri) {
/** @var \Amp\Socket\ClientSocket $socket */
$socket = yield connect($uri);
try {
yield $socket->write($packet);

$chunk = yield $socket->read(); // modbus packet is so small that one read is enough
if ($chunk === null) {
return null;
}
return ResponseFactory::parseResponse($chunk);
} finally {
$socket->close();
}
});
}
## Example of non-blocking socket IO with ReactPHP/Amp (i.e. modbus request are run in 'parallel')

try {
// will run multiple request in parallel using non-blocking php stream io
$responses = Amp\Promise\wait(Amp\Promise\all($promises));
print_r($responses);
} catch (Throwable $e) {
print_r($e);
}
```
* 'examples/[example_parallel_requests_reactphp.php](examples/example_parallel_requests_reactphp.php) - example of non-blocking socket IO with ReactPHP socket library (https://github.com/reactphp/socket)
* 'examples/[example_parallel_requests_amp.php](examples/example_parallel_requests_amp.php) - example of non-blocking socket IO with Amp socket library https://github.com/amphp/socket

## Try communication with PLCs quickly using php built-in web server

Expand Down
20 changes: 17 additions & 3 deletions examples/example_cli_poller.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require __DIR__ . '/../vendor/autoload.php';

use ModbusTcpClient\Composer\Read\ReadRegistersBuilder;
use ModbusTcpClient\Utils\Packet;

$requests = ReadRegistersBuilder::newReadHoldingRegisters('tcp://127.0.0.1:5022')
->bit(278, 5, 'dirchange1_status')
Expand All @@ -22,6 +23,8 @@
->build(); // returns array of 3 requests

// Install: 'composer require react/socket:^0.8.11'
// NB: install PHP extension ('ev', 'event' or 'uv') if the concurrent socket connections are more than 1024.

$loop = React\EventLoop\Factory::create();

$n = 60;
Expand All @@ -36,13 +39,24 @@

$connector->connect($request->getUri())->then(
function (React\Socket\ConnectionInterface $connection) use ($request) {
$receivedData = '';

echo microtime(true) . ": connected to {$request->getUri()}" . PHP_EOL;
$connection->write($request);

// wait for response event
$connection->on('data', function ($data) use ($connection, $request) {
echo microtime(true) . ": uri: {$request->getUri()}, response: " . print_r($request->parse($data), true) . PHP_EOL;
$connection->end();
$connection->on('data', function ($data) use ($connection, $request, &$receivedData) {
// there are rare cases when MODBUS packet is received by multiple fragmented TCP packets and it could
// take PHP multiple reads from stream to get full packet. So we concatenate data and check if all that
// we have received makes a complete modbus packet.
// NB: `Packet::isCompleteLength` is suitable only for modbus TCP packets
$receivedData .= $data;
if (Packet::isCompleteLength($receivedData)) {
echo microtime(true) . ": uri: {$request->getUri()}, complete response: " . print_r($request->parse($receivedData), true) . PHP_EOL;
$connection->end();
} else {
echo microtime(true) . ": uri: {$request->getUri()}, partial response: " . print_r($data, true) . PHP_EOL;
}
});
$connection->on('error', function ($data) use ($connection, $request) {
echo microtime(true) . ": uri: {$request->getUri()}, Error during connection! error: " . print_r($data, true) . PHP_EOL;
Expand Down
6 changes: 4 additions & 2 deletions examples/example_parallel_requests_amp.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use ModbusTcpClient\Composer\Address;
use ModbusTcpClient\Composer\Read\ReadRegistersBuilder;
use ModbusTcpClient\Composer\Read\ReadRequest;
use ModbusTcpClient\Composer\Read\Register\ReadRegisterRequest;
use function Amp\Socket\connect;

// Install dependency with 'composer require amphp/socket'
Expand Down Expand Up @@ -46,8 +46,10 @@
* This will do 'parallel' socket request with help of Amp socket library (https://amphp.org/socket/)
* Install dependency with 'composer require amphp/socket'
*
* NB: install PHP extension ('ev', 'event' or 'uv') if the concurrent socket connections are more than 1024.
*
* @param ReadRequest[] $requests
*
* @param ReadRegisterRequest[] $requests
*/
function requestWithAmp(array $requests)
{
Expand Down
21 changes: 16 additions & 5 deletions examples/example_parallel_requests_reactphp.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
require __DIR__ . '/../vendor/autoload.php';

use ModbusTcpClient\Composer\Read\ReadRegistersBuilder;
use ModbusTcpClient\Composer\Read\ReadRequest;
use ModbusTcpClient\Composer\Read\Register\ReadRegisterRequest;
use ModbusTcpClient\Utils\Packet;

$fc3 = ReadRegistersBuilder::newReadHoldingRegisters('tcp://127.0.0.1:5022')
->bit(256, 15, 'pump2_feedbackalarm_do')
Expand All @@ -30,8 +31,9 @@
* This will do 'parallel' socket request with help of ReactPHP socket library (https://github.com/reactphp/socket)
* Install dependency with 'composer require react/socket:^0.8.11'
*
* NB: install PHP extension ('ev', 'event' or 'uv') if the concurrent socket connections are more than 1024.
*
* @param ReadRequest[] $requests
* @param ReadRegisterRequest[] $requests
*/
function requestWithReactPhp(array $requests)
{
Expand All @@ -49,12 +51,21 @@ function requestWithReactPhp(array $requests)

$connector->connect($request->getUri())->then(
function (React\Socket\ConnectionInterface $connection) use ($request, $promise) {
$receivedData = '';

$connection->write($request);

// wait for response event
$connection->on('data', function ($data) use ($connection, $promise, $request) {
$promise->resolve($request->parse($data));
$connection->end();
$connection->on('data', function ($data) use ($connection, $promise, $request, &$receivedData) {
// there are rare cases when MODBUS packet is received by multiple fragmented TCP packets and it could
// take PHP multiple reads from stream to get full packet. So we concatenate data and check if all that
// we have received makes a complete modbus packet.
// NB: `Packet::isCompleteLength` is suitable only for modbus TCP packets
$receivedData .= $data;
if (Packet::isCompleteLength($receivedData)) {
$promise->resolve($request->parse($data));
$connection->end();
}
});
$connection->on('error', function ($data) use ($connection, $promise) {
$promise->reject('Request failed: ' . print_r($data, true));
Expand Down
3 changes: 0 additions & 3 deletions src/Network/StreamHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

namespace ModbusTcpClient\Network;


use ModbusTcpClient\Utils\Packet;

trait StreamHandler
{
/**
Expand Down
5 changes: 3 additions & 2 deletions src/Packet/ErrorResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ public function withStartAddress(int $startAddress)
}

/**
* is checks if given binary string is complete modbus error packet
* is checks if given binary string is complete MODBUS TCP error packet
* NB: do not use for RTU packets
*
* @param $binaryData string|null binary string to be checked
* @return bool true if data is actual error packet
Expand All @@ -129,7 +130,7 @@ public static function is($binaryData): bool
{
// a) data is too short. can not determine packet.
// b) data is too long. can not be an error packet
// Actual packet is at least 9 bytes. 7 bytes for Modbus header and at least 2 bytes for PDU
// Actual packet is at least 9 bytes. 7 bytes for Modbus TCP header and at least 2 bytes for PDU
if (strlen($binaryData) !== 9) {
return false;
}
Expand Down
1 change: 1 addition & 0 deletions src/Utils/Packet.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ private function __construct()

/**
* isCompleteLength checks if binary string is complete modbus packet
* NB: this function works only for MODBUS TCP packets
*
* @param $binaryData string|null binary string to be checked
* @return bool true if data is actual error packet
Expand Down

0 comments on commit d62f769

Please sign in to comment.