-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add write coils api with request splitter
- Loading branch information
Showing
9 changed files
with
575 additions
and
0 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,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; | ||
} | ||
} |
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,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; | ||
} | ||
} |
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,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); | ||
} | ||
} |
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,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); | ||
} | ||
} |
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,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
31
tests/unit/Composer/Write/WriteCoilAddressSplitterTest.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,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); | ||
} | ||
} |
Oops, something went wrong.