Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement #38 - add read coils api with request splitter #49

Merged
merged 2 commits into from
Apr 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]


## [2.0.0] - 2020-04-07

### Added

* Added high level API to compose Coil (fc1/2) requests. See:
* `ModbusTcpClient\Composer\Read\ReadCoilsBuilder`
* `ModbusTcpClient\Composer\Write\WriteCoilsBuilder`
* Added 'serial' protocol support to `BinaryStreamConnection`. See [examples/rtu_usb_to_serial_stream.php](examples/rtu_usb_to_serial_stream.php) how to use it.
* Added example to read modbus RTU from Usb to rs-485 adapter and SHT20 sensor (#50)
* Started changelog
* Adds Function 23 (0x17) Read/Write Multiple registers support (#47)
* Started changelog

### Changed

* Adds request time on example request page
* (BREAKING change) abstract class `ModbusTcpClient\Composer\Address` changed to interface
4 changes: 4 additions & 0 deletions examples/fc1.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
echo 'Data parsed from packet (bytes):' . PHP_EOL;
print_r($response->getCoils());

// set internal index to match start address to simplify array access
$responseWithStartAddress = $response->withStartAddress($startAddress);
print_r($responseWithStartAddress[12288]); // coil value at 12288

} catch (Exception $exception) {
echo 'An exception occurred' . PHP_EOL;
echo $exception->getMessage() . PHP_EOL;
Expand Down
4 changes: 4 additions & 0 deletions examples/fc2.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
echo 'Data parsed from packet (bytes):' . PHP_EOL;
print_r($response->getCoils());

// set internal index to match start address to simplify array access
$responseWithStartAddress = $response->withStartAddress($startAddress);
print_r($responseWithStartAddress[12288]); // coil value at 12288

} catch (Exception $exception) {
echo 'An exception occurred' . PHP_EOL;
echo $exception->getMessage() . PHP_EOL;
Expand Down
51 changes: 4 additions & 47 deletions src/Composer/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
namespace ModbusTcpClient\Composer;


use ModbusTcpClient\Exception\InvalidArgumentException;

abstract class Address
interface Address
{
const TYPE_BIT = 'bit';
const TYPE_BYTE = 'byte';
Expand All @@ -31,48 +29,7 @@ abstract class Address
Address::TYPE_STRING,
];

/** @var int */
protected $address;

/** @var string */
protected $type;

public function __construct(int $address, string $type)
{
$this->address = $address;
$this->type = $type;

if (!in_array($type, $this->getAllowedTypes(), true)) {
throw new InvalidArgumentException("Invalid address type given! type: '{$type}', address: {$address}");
}
}

abstract protected function getAllowedTypes(): array;

public function getSize(): int
{
$size = 1;
switch ($this->type) {
case self::TYPE_INT32:
case self::TYPE_UINT32:
case self::TYPE_FLOAT:
$size = 2;
break;
case self::TYPE_INT64:
case self::TYPE_UINT64:
$size = 4;
break;
}
return $size;
}

public function getAddress(): int
{
return $this->address;
}
public function getSize(): int;

public function getType(): string
{
return $this->type;
}
}
public function getAddress(): int;
}
1 change: 1 addition & 0 deletions src/Composer/AddressSplitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protected function getMaxAddressesPerModbusRequest(): int
abstract protected function createRequest(string $uri, array $addressesChunk, int $startAddress, int $quantity, int $unitId);

/**
* @param Address[] $addresses
* @return array
*/
public function split(array $addresses): array
Expand Down
73 changes: 73 additions & 0 deletions src/Composer/Read/Coil/ReadCoilAddress.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace ModbusTcpClient\Composer\Read\Coil;


use ModbusTcpClient\Composer\Address;
use ModbusTcpClient\Packet\ModbusResponse;

class ReadCoilAddress implements Address
{
/** @var int */
protected $address;

/** @var string */
private $name;

/** @var callable */
protected $callback;

/** @var callable */
private $errorCallback;

public function __construct(int $address, string $name = null, callable $callback = null, callable $errorCallback = null)
{
$this->address = $address;
$this->name = $name ?: (string)($address);
$this->callback = $callback;
$this->errorCallback = $errorCallback;
}

/**
* @param ModbusResponse $response
* @return null
* @throws \Exception
*/
public function extract(ModbusResponse $response)
{
try {
$result = $response[$this->address];

if ($this->callback !== null) {
// callback has access to extracted value, extractor instance that extracted it and whole response
return ($this->callback)($result, $this, $response);
}
return $result;
} catch (\Exception $exception) {
if ($this->errorCallback !== null) {
return ($this->errorCallback)($exception, $this, $response);
}
throw $exception;
}
}

public function getName(): string
{
return $this->name;
}

public function getSize(): int
{
return 1;
}

public function getAddress(): int
{
return $this->address;
}

public function getType(): string
{
return Address::TYPE_BIT;
}
}
27 changes: 27 additions & 0 deletions src/Composer/Read/Coil/ReadCoilAddressSplitter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace ModbusTcpClient\Composer\Read\Coil;


use ModbusTcpClient\Composer\AddressSplitter;

class ReadCoilAddressSplitter extends AddressSplitter
{
/** @var string */
private $requestClass;

public function __construct(string $requestClass)
{
$this->requestClass = $requestClass;
}

protected function getMaxAddressesPerModbusRequest(): int
{
return static::MAX_COILS_PER_MODBUS_REQUEST;
}

protected function createRequest(string $uri, array $addressesChunk, int $startAddress, int $quantity, int $unitId = 0)
{
return new ReadCoilRequest($uri, $addressesChunk, new $this->requestClass($startAddress, $quantity, $unitId));
}
}
78 changes: 78 additions & 0 deletions src/Composer/Read/Coil/ReadCoilRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace ModbusTcpClient\Composer\Read\Coil;


use ModbusTcpClient\Composer\Request;
use ModbusTcpClient\Exception\ModbusException;
use ModbusTcpClient\Packet\ErrorResponse;
use ModbusTcpClient\Packet\ModbusFunction\ReadCoilsRequest;
use ModbusTcpClient\Packet\ResponseFactory;

class ReadCoilRequest implements Request
{
/**
* @var string uri to modbus server. Example: 'tcp://192.168.100.1:502'
*/
private $uri;

/** @var ReadCoilAddress[] */
private $addresses;

/** @var ReadCoilsRequest */
private $request;


public function __construct(string $uri, array $addresses, $request)
{
$this->addresses = $addresses;
$this->request = $request;
$this->uri = $uri;
}

/**
* @return ReadCoilsRequest
*/
public function getRequest(): ReadCoilsRequest
{
return $this->request;
}

public function getUri(): string
{
return $this->uri;
}

/**
* @return ReadCoilAddress[]
*/
public function getAddresses(): array
{
return $this->addresses;
}

public function __toString()
{
return $this->request->__toString();
}

/**
* @param string $binaryData
* @return array|ErrorResponse
* @throws ModbusException
* @throws \Exception
*/
public function parse(string $binaryData)
{
$response = ResponseFactory::parseResponse($binaryData)->withStartAddress($this->request->getStartAddress());
if ($response instanceof ErrorResponse) {
return $response;
}

$result = [];
foreach ($this->addresses as $address) {
$result[$address->getName()] = $address->extract($response);
}
return $result;
}
}
Loading