Skip to content

Commit

Permalink
add write coils api with request splitter
Browse files Browse the repository at this point in the history
  • Loading branch information
aldas committed Mar 31, 2020
1 parent 16cffdd commit 506fde5
Show file tree
Hide file tree
Showing 9 changed files with 575 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/Composer/Read/ReadCoilAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,9 @@ public function getAddress(): int
{
return $this->address;
}

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

namespace ModbusTcpClient\Composer\Write;


use ModbusTcpClient\Composer\Address;

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

/** @var bool */
private $value;

public function __construct(int $address, bool $value)
{
$this->address = $address;
$this->value = $value;
}

public function getValue(): bool
{
return $this->value;
}

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

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

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

namespace ModbusTcpClient\Composer\Write;


use ModbusTcpClient\Composer\Address;
use ModbusTcpClient\Composer\AddressSplitter;
use ModbusTcpClient\Exception\InvalidArgumentException;

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

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

/**
* @param string $uri
* @param WriteCoilAddress[] $addressesChunk
* @param int $startAddress
* @param int $quantity
* @param int $unitId
* @return WriteCoilRequest
*/
protected function createRequest(string $uri, array $addressesChunk, int $startAddress, int $quantity, int $unitId = 0)
{
$values = [];
foreach ($addressesChunk as $address) {
$values[] = $address->getValue();
}

return new WriteCoilRequest($uri, $addressesChunk, new $this->requestClass($startAddress, $values, $unitId));
}

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

protected function shouldSplit(Address $currentAddress, int $currentQuantity, Address $previousAddress = null, int $previousQuantity = null): bool
{
$isOverAddressLimit = $currentQuantity >= $this->getMaxAddressesPerModbusRequest();
if ($isOverAddressLimit) {
return $isOverAddressLimit;
}
if ($previousAddress === null) {
return false;
}

$currentStartAddress = $currentAddress->getAddress();
$previousStartAddress = $previousAddress->getAddress();
$previousAddressEndStartAddress = ($previousStartAddress + $previousAddress->getSize());

if (($previousStartAddress <= $currentStartAddress) && ($currentStartAddress < $previousAddressEndStartAddress)) {
// situation when current address overlaps previous memory range does not make sense

$info = "{$previousStartAddress} with {$currentStartAddress}";
throw new InvalidArgumentException('Trying to write addresses that seem share their memory range! ' . $info);
}

// current and previous need to be adjacent as WriteMultipleCoilsRequest needs to have all registers in packet to be adjacent
// or another packet should be build (split)
return $currentStartAddress - $previousAddressEndStartAddress > 0;
}
}
68 changes: 68 additions & 0 deletions src/Composer/Write/WriteCoilRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace ModbusTcpClient\Composer\Write;


use ModbusTcpClient\Composer\Request;
use ModbusTcpClient\Exception\ModbusException;
use ModbusTcpClient\Packet\ModbusRequest;
use ModbusTcpClient\Packet\ModbusResponse;
use ModbusTcpClient\Packet\ResponseFactory;

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

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

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


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

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

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

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

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

/**
* @param string $binaryData
* @return ModbusResponse
* @throws ModbusException
*/
public function parse(string $binaryData): ModbusResponse
{
return ResponseFactory::parseResponse($binaryData);
}
}
112 changes: 112 additions & 0 deletions src/Composer/Write/WriteCoilsBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

namespace ModbusTcpClient\Composer\Write;


use ModbusTcpClient\Composer\AddressSplitter;
use ModbusTcpClient\Exception\InvalidArgumentException;
use ModbusTcpClient\Packet\ModbusFunction\WriteMultipleCoilsRequest;

class WriteCoilsBuilder
{
/** @var WriteCoilAddressSplitter */
private $addressSplitter;

/** @var WriteCoilAddress[] */
private $addresses = [];

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

/** @var int */
private $unitId;

public function __construct(string $requestClass, string $uri = null, int $unitId = 0)
{
$this->addressSplitter = new WriteCoilAddressSplitter($requestClass);

if ($uri !== null) {
$this->useUri($uri);
}
$this->unitId = $unitId;
}

public static function newWriteMultipleCoils(string $uri = null, int $unitId = 0): WriteCoilsBuilder
{
return new WriteCoilsBuilder(WriteMultipleCoilsRequest::class, $uri, $unitId);
}

public function useUri(string $uri, int $unitId = 0): WriteCoilsBuilder
{
if (empty($uri)) {
throw new InvalidArgumentException('uri can not be empty value');
}
$this->currentUri = $uri;
$this->unitId = $unitId;

return $this;
}

protected function addAddress(WriteCoilAddress $address): WriteCoilsBuilder
{
if (empty($this->currentUri)) {
throw new InvalidArgumentException('uri not set');
}
$unitIdPrefix = AddressSplitter::UNIT_ID_PREFIX;
$modbusPath = "{$this->currentUri}{$unitIdPrefix}{$this->unitId}";
$this->addresses[$modbusPath][$address->getAddress()] = $address;
return $this;
}

public function allFromArray(array $coils): WriteCoilsBuilder
{
foreach ($coils as $coil) {
if (\is_array($coil)) {
$this->fromArray($coil);
} elseif ($coil instanceof WriteCoilAddress) {
$this->addAddress($coil);
}
}
return $this;
}

public function fromArray(array $coil): WriteCoilsBuilder
{
$uri = $coil['uri'] ?? null;
$unitId = $coil['unitId'] ?? 0;
if ($uri !== null) {
$this->useUri($uri, $unitId);
}

$address = $coil['address'] ?? null;
if ($address === null) {
throw new InvalidArgumentException('empty address given');
}

if (!array_key_exists('value', $coil)) {
throw new InvalidArgumentException('value missing');
}

$this->coil($address, (bool)$coil['value']);

return $this;
}

public function coil(int $address, bool $value): WriteCoilsBuilder
{
return $this->addAddress(new WriteCoilAddress($address, $value));
}

/**
* @return WriteCoilRequest[]
*/
public function build(): array
{
return $this->addressSplitter->split($this->addresses);
}

public function isNotEmpty()
{
return !empty($this->addresses);
}
}
28 changes: 28 additions & 0 deletions tests/unit/Composer/Read/ReadCoilAddressSplitterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Tests\unit\Composer\Write;


use ModbusTcpClient\Composer\AddressSplitter;
use ModbusTcpClient\Composer\Read\ReadCoilAddress;
use ModbusTcpClient\Composer\Read\ReadCoilAddressSplitter;
use ModbusTcpClient\Packet\ModbusFunction\ReadCoilsRequest;
use PHPUnit\Framework\TestCase;

class ReadCoilAddressSplitterTest extends TestCase
{
public function testSplitSameAddress()
{
$splitter = new ReadCoilAddressSplitter(ReadCoilsRequest::class);

$requests = $splitter->split([
'tcp://127.0.0.1' . AddressSplitter::UNIT_ID_PREFIX . '1' => [
new ReadCoilAddress(256),
new ReadCoilAddress(256),
]
]);

$this->assertCount(1, $requests);
$this->assertCount(2, $requests[0]->getAddresses());
}
}
31 changes: 31 additions & 0 deletions tests/unit/Composer/Write/WriteCoilAddressSplitterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Tests\unit\Composer\Write;


use ModbusTcpClient\Composer\AddressSplitter;
use ModbusTcpClient\Composer\Write\WriteCoilAddress;
use ModbusTcpClient\Composer\Write\WriteCoilAddressSplitter;
use ModbusTcpClient\Packet\ModbusFunction\WriteMultipleCoilsRequest;
use PHPUnit\Framework\TestCase;

class WriteCoilAddressSplitterTest extends TestCase
{
/**
* @expectedException \ModbusTcpClient\Exception\InvalidArgumentException
* @expectedExceptionMessage Trying to write addresses that seem share their memory range! 256 with 256
*/
public function testSplitSameAddress()
{
$splitter = new WriteCoilAddressSplitter(WriteMultipleCoilsRequest::class);

$requests = $splitter->split([
'tcp://127.0.0.1' . AddressSplitter::UNIT_ID_PREFIX . '1' => [
new WriteCoilAddress(256, true),
new WriteCoilAddress(256, false),
]
]);

$this->assertEquals(1, $requests);
}
}
Loading

0 comments on commit 506fde5

Please sign in to comment.