From 613732627702a84a41488348febaa77a1859bf0d Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sat, 26 Nov 2016 21:34:54 +0200 Subject: [PATCH 01/17] refactor/reimplement old (batch script) tests to use phpunit. --- .gitignore | 1 + composer.json | 11 + examples/README.md | 4 +- examples/example_750841_Mmemory.php | 107 ++++---- examples/example_datatype.php | 33 ++- examples/example_fc1.php | 23 +- examples/example_fc15.php | 36 ++- examples/example_fc16.php | 30 +- examples/example_fc2.php | 35 +-- examples/example_fc22.php | 18 +- examples/example_fc23.php | 32 ++- examples/example_fc3.php | 31 ++- examples/example_fc4.php | 18 +- examples/example_fc5.php | 33 ++- examples/example_fc6.php | 32 ++- phpunit.xml | 34 +++ tests/Codesys/TEST.EXP | 256 ------------------ tests/Codesys/_make_exp.cmd | 15 - tests/Codesys/test.pro | Bin 34020 -> 0 bytes tests/IecType/IecByteTest.php | 18 ++ tests/IecType/IecDIntTest.php | 31 +++ tests/IecType/IecIntTest.php | 22 ++ tests/IecType/IecRealTest.php | 31 +++ tests/IecType/_test.bat | 11 - tests/IecType/ref/test.iecByte.php.html | 1 - tests/IecType/ref/test.iecDInt.php.html | 1 - tests/IecType/ref/test.iecInt.php.html | 1 - tests/IecType/ref/test.iecReal.php.html | 12 - tests/IecType/test.iecByte.php | 33 --- tests/IecType/test.iecDInt.php | 50 ---- tests/IecType/test.iecInt.php | 50 ---- tests/IecType/test.iecReal.php | 50 ---- .../BindClientToLocalIpAndPortTest.php | 25 ++ .../Fc15WriteMultipleCoilsTest.php | 21 ++ .../Fc16WriteMultipleRegistersTest.php | 21 ++ tests/ModbusMaster/Fc1ReadCoilsTest.php | 35 +++ .../Fc22MaskWriteRegisterTest.php | 26 ++ .../Fc23ReadWriteRegistersTest.php | 24 ++ .../Fc2ReadInputDiscretesTest.php | 35 +++ .../Fc3ReadMultipleRegistersTest.php | 35 +++ .../Fc4ReadMultipleInputRegistersTest.php | 35 +++ tests/ModbusMaster/Fc5WriteSingleCoilTest.php | 35 +++ .../Fc6WriteSingleRegisterTest.php | 35 +++ tests/ModbusMaster/MockResponseServer.php | 86 ++++++ tests/ModbusMaster/MockServerTestCase.php | 32 +++ tests/ModbusMaster/ModbusExceptionTest.php | 63 +++++ tests/ModbusMaster/UdpFc1ReadCoilsTest.php | 23 ++ tests/ModbusMaster/_test.bat | 11 - .../ref/test.tcp.fc16fc3.php.html | 72 ----- tests/ModbusMaster/ref/test.tcp.fc26.php.html | 1 - ...test.tcp.socket_protocol_mismatch.php.html | 1 - .../ref/test.udp.fc16fc3.php.html | 72 ----- tests/ModbusMaster/ref/test.udp.fc26.php.html | 1 - tests/ModbusMaster/test.tcp.fc16fc3.php | 44 --- tests/ModbusMaster/test.tcp.fc26.php | 24 -- .../test.tcp.socket_protocol_mismatch.php | 22 -- tests/ModbusMaster/test.udp.fc16fc3.php | 44 --- tests/ModbusMaster/test.udp.fc26.php | 24 -- tests/ModbusMasterTcp/_test.bat | 11 - .../ModbusMasterTcp/ref/test.fc16fc3.php.html | 72 ----- tests/ModbusMasterTcp/ref/test.fc26.php.html | 1 - tests/ModbusMasterTcp/test.fc16fc3.php | 44 --- tests/ModbusMasterTcp/test.fc26.php | 24 -- tests/ModbusMasterUdp/_test.bat | 11 - .../ModbusMasterUdp/ref/test.fc15fc1.php.html | 66 ----- .../ModbusMasterUdp/ref/test.fc16fc3.php.html | 72 ----- .../ref/test.fc16fc3bind.php.html | 72 ----- tests/ModbusMasterUdp/ref/test.fc2.php.html | 7 - tests/ModbusMasterUdp/ref/test.fc26.php.html | 1 - .../ref/test.fc26bind.php.html | 1 - tests/ModbusMasterUdp/ref/test.fc4.php.html | 11 - tests/ModbusMasterUdp/ref/test.fc5.php.html | 5 - .../ModbusMasterUdp/ref/test.fc6fc3.php.html | 5 - tests/ModbusMasterUdp/test.fc15fc1.php | 22 -- tests/ModbusMasterUdp/test.fc16fc3.php | 44 --- tests/ModbusMasterUdp/test.fc16fc3bind.php | 45 --- tests/ModbusMasterUdp/test.fc2.php | 14 - tests/ModbusMasterUdp/test.fc26.php | 24 -- tests/ModbusMasterUdp/test.fc26bind.php | 25 -- tests/ModbusMasterUdp/test.fc4.php | 14 - tests/ModbusMasterUdp/test.fc5.php | 23 -- tests/ModbusMasterUdp/test.fc6fc3.php | 15 - tests/PhpType/Bytes2MixedTest.php | 45 +++ tests/PhpType/Bytes2RealTest.php | 31 +++ tests/PhpType/Bytes2SignedIntTest.php | 42 +++ tests/PhpType/Bytes2StringTest.php | 32 +++ tests/PhpType/Bytes2UnSignedIntTest.php | 42 +++ ...PhpTypeArrayExceptionWithTextArrayTest.php | 27 ++ .../PhpTypeArraySizeExceptionsTest.php | 46 ++++ tests/PhpType/_test.bat | 11 - tests/PhpType/ref/test.bytes2mixed.php.html | 1 - tests/PhpType/ref/test.bytes2real.php.html | 1 - .../PhpType/ref/test.bytes2signedint.php.html | 1 - tests/PhpType/ref/test.bytes2string.php.html | 1 - .../ref/test.bytes2unsignedint.php.html | 6 - .../ref/test.strangearray.size.php.html | 1 - .../ref/test.strangearray.textarray.php.html | 1 - tests/PhpType/test.bytes2mixed.php | 37 --- tests/PhpType/test.bytes2real.php | 27 -- tests/PhpType/test.bytes2signedint.php | 35 --- tests/PhpType/test.bytes2string.php | 28 -- tests/PhpType/test.bytes2unsignedint.php | 36 --- tests/PhpType/test.strangearray.size.php | 44 --- tests/PhpType/test.strangearray.textarray.php | 22 -- tests/README.md | 3 - tests/config.bat | 5 - tests/config.php | 3 - 107 files changed, 1186 insertions(+), 1878 deletions(-) create mode 100644 phpunit.xml delete mode 100644 tests/Codesys/TEST.EXP delete mode 100644 tests/Codesys/_make_exp.cmd delete mode 100644 tests/Codesys/test.pro create mode 100644 tests/IecType/IecByteTest.php create mode 100644 tests/IecType/IecDIntTest.php create mode 100644 tests/IecType/IecIntTest.php create mode 100644 tests/IecType/IecRealTest.php delete mode 100644 tests/IecType/_test.bat delete mode 100644 tests/IecType/ref/test.iecByte.php.html delete mode 100644 tests/IecType/ref/test.iecDInt.php.html delete mode 100644 tests/IecType/ref/test.iecInt.php.html delete mode 100644 tests/IecType/ref/test.iecReal.php.html delete mode 100644 tests/IecType/test.iecByte.php delete mode 100644 tests/IecType/test.iecDInt.php delete mode 100644 tests/IecType/test.iecInt.php delete mode 100644 tests/IecType/test.iecReal.php create mode 100644 tests/ModbusMaster/BindClientToLocalIpAndPortTest.php create mode 100644 tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php create mode 100644 tests/ModbusMaster/Fc16WriteMultipleRegistersTest.php create mode 100644 tests/ModbusMaster/Fc1ReadCoilsTest.php create mode 100644 tests/ModbusMaster/Fc22MaskWriteRegisterTest.php create mode 100644 tests/ModbusMaster/Fc23ReadWriteRegistersTest.php create mode 100644 tests/ModbusMaster/Fc2ReadInputDiscretesTest.php create mode 100644 tests/ModbusMaster/Fc3ReadMultipleRegistersTest.php create mode 100644 tests/ModbusMaster/Fc4ReadMultipleInputRegistersTest.php create mode 100644 tests/ModbusMaster/Fc5WriteSingleCoilTest.php create mode 100644 tests/ModbusMaster/Fc6WriteSingleRegisterTest.php create mode 100644 tests/ModbusMaster/MockResponseServer.php create mode 100644 tests/ModbusMaster/MockServerTestCase.php create mode 100644 tests/ModbusMaster/ModbusExceptionTest.php create mode 100644 tests/ModbusMaster/UdpFc1ReadCoilsTest.php delete mode 100644 tests/ModbusMaster/_test.bat delete mode 100644 tests/ModbusMaster/ref/test.tcp.fc16fc3.php.html delete mode 100644 tests/ModbusMaster/ref/test.tcp.fc26.php.html delete mode 100644 tests/ModbusMaster/ref/test.tcp.socket_protocol_mismatch.php.html delete mode 100644 tests/ModbusMaster/ref/test.udp.fc16fc3.php.html delete mode 100644 tests/ModbusMaster/ref/test.udp.fc26.php.html delete mode 100644 tests/ModbusMaster/test.tcp.fc16fc3.php delete mode 100644 tests/ModbusMaster/test.tcp.fc26.php delete mode 100644 tests/ModbusMaster/test.tcp.socket_protocol_mismatch.php delete mode 100644 tests/ModbusMaster/test.udp.fc16fc3.php delete mode 100644 tests/ModbusMaster/test.udp.fc26.php delete mode 100644 tests/ModbusMasterTcp/_test.bat delete mode 100644 tests/ModbusMasterTcp/ref/test.fc16fc3.php.html delete mode 100644 tests/ModbusMasterTcp/ref/test.fc26.php.html delete mode 100644 tests/ModbusMasterTcp/test.fc16fc3.php delete mode 100644 tests/ModbusMasterTcp/test.fc26.php delete mode 100644 tests/ModbusMasterUdp/_test.bat delete mode 100644 tests/ModbusMasterUdp/ref/test.fc15fc1.php.html delete mode 100644 tests/ModbusMasterUdp/ref/test.fc16fc3.php.html delete mode 100644 tests/ModbusMasterUdp/ref/test.fc16fc3bind.php.html delete mode 100644 tests/ModbusMasterUdp/ref/test.fc2.php.html delete mode 100644 tests/ModbusMasterUdp/ref/test.fc26.php.html delete mode 100644 tests/ModbusMasterUdp/ref/test.fc26bind.php.html delete mode 100644 tests/ModbusMasterUdp/ref/test.fc4.php.html delete mode 100644 tests/ModbusMasterUdp/ref/test.fc5.php.html delete mode 100644 tests/ModbusMasterUdp/ref/test.fc6fc3.php.html delete mode 100644 tests/ModbusMasterUdp/test.fc15fc1.php delete mode 100644 tests/ModbusMasterUdp/test.fc16fc3.php delete mode 100644 tests/ModbusMasterUdp/test.fc16fc3bind.php delete mode 100644 tests/ModbusMasterUdp/test.fc2.php delete mode 100644 tests/ModbusMasterUdp/test.fc26.php delete mode 100644 tests/ModbusMasterUdp/test.fc26bind.php delete mode 100644 tests/ModbusMasterUdp/test.fc4.php delete mode 100644 tests/ModbusMasterUdp/test.fc5.php delete mode 100644 tests/ModbusMasterUdp/test.fc6fc3.php create mode 100644 tests/PhpType/Bytes2MixedTest.php create mode 100644 tests/PhpType/Bytes2RealTest.php create mode 100644 tests/PhpType/Bytes2SignedIntTest.php create mode 100644 tests/PhpType/Bytes2StringTest.php create mode 100644 tests/PhpType/Bytes2UnSignedIntTest.php create mode 100644 tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php create mode 100644 tests/PhpType/PhpTypeArraySizeExceptionsTest.php delete mode 100644 tests/PhpType/_test.bat delete mode 100644 tests/PhpType/ref/test.bytes2mixed.php.html delete mode 100644 tests/PhpType/ref/test.bytes2real.php.html delete mode 100644 tests/PhpType/ref/test.bytes2signedint.php.html delete mode 100644 tests/PhpType/ref/test.bytes2string.php.html delete mode 100644 tests/PhpType/ref/test.bytes2unsignedint.php.html delete mode 100644 tests/PhpType/ref/test.strangearray.size.php.html delete mode 100644 tests/PhpType/ref/test.strangearray.textarray.php.html delete mode 100644 tests/PhpType/test.bytes2mixed.php delete mode 100644 tests/PhpType/test.bytes2real.php delete mode 100644 tests/PhpType/test.bytes2signedint.php delete mode 100644 tests/PhpType/test.bytes2string.php delete mode 100644 tests/PhpType/test.bytes2unsignedint.php delete mode 100644 tests/PhpType/test.strangearray.size.php delete mode 100644 tests/PhpType/test.strangearray.textarray.php delete mode 100644 tests/README.md delete mode 100644 tests/config.bat delete mode 100644 tests/config.php diff --git a/.gitignore b/.gitignore index 88fd331..bc96eec 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ composer.lock .idea/ *~ +/report \ No newline at end of file diff --git a/composer.json b/composer.json index b44d7a1..d52d076 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,12 @@ "php": "^5.3.2 || ^7.0", "ext-sockets": "*" }, + "require-dev": { + "react/socket": "~0.4.0", + "react/child-process": "^0.4.1", + "react/datagram": "^1.1", + "phpunit/phpunit": "^5.6" + }, "authors": [ { "name": "Honza Krakora", @@ -21,5 +27,10 @@ "psr-4": { "PHPModbus\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } } } diff --git a/examples/README.md b/examples/README.md index f3e0c3b..b4b8c0c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1 @@ -Those examples have not been updated to use namespaces. - -They may not work. Just for reference. +Do not expose this folder publicly as you read/write modbus data with query parameters diff --git a/examples/example_750841_Mmemory.php b/examples/example_750841_Mmemory.php index 452b801..6fa8f90 100644 --- a/examples/example_750841_Mmemory.php +++ b/examples/example_750841_Mmemory.php @@ -1,65 +1,74 @@ readMultipleRegisters($moduleId, $reference, $quantity); + // FC 3 + $recData = $modbus->readMultipleRegisters($unitId, $reference, $quantity); } catch (Exception $e) { - echo $modbus; - echo $e; - exit; + echo $modbus; + echo $e; + exit; } ?> - - - - WAGO 750-841 M-memory dump - - -

Dump of M-memory from WAGO 750-84x series coupler.

- - -

M-memory dump

+ + + + WAGO 750-841 M-memory dump + + +

Dump of M-memory from WAGO 750-84x series coupler.

+ - - - - - - - - - - - - - -
Modbus addressMWxvalue
MW
+

M-memory dump

-

Modbus class status

+ + + + + + + + + + + + + + + + + + + + + +
WORD addressInt16UInt16high bytelow bytehigh bitslow bits
-
+

Modbus class status

- +
+

Data

+
+ diff --git a/examples/example_datatype.php b/examples/example_datatype.php index 86fa4dc..aeb0a7b 100644 --- a/examples/example_datatype.php +++ b/examples/example_datatype.php @@ -1,20 +1,23 @@ readMultipleRegisters(0, 12288, 10); + // FC 3 + // read 10 words (20 bytes) from device ID=0, address=12288 + $recData = $modbus->readMultipleRegisters($unitId, $reference, $quantity); } catch (Exception $e) { - // Print error information if any - echo $modbus; - echo $e; - exit; + // Print error information if any + echo $modbus; + echo $e; + exit; } // Received data @@ -29,17 +32,17 @@ $values = array_chunk($recData, 4); // Get float from REAL interpretation echo "

REAL to Float

\n"; foreach ($values as $bytes) - echo PhpType::bytes2float($bytes) . "
"; + echo PhpType::bytes2float($bytes) . "
"; // Get integer from DINT interpretation echo "

DINT to integer

\n"; foreach ($values as $bytes) - echo PhpType::bytes2signedInt($bytes) . "
"; + echo PhpType::bytes2signedInt($bytes) . "
"; // Get integer of float from DINT interpretation echo "

DWORD to integer (or float)

\n"; foreach ($values as $bytes) - echo PhpType::bytes2unsignedInt($bytes) . "
"; + echo PhpType::bytes2unsignedInt($bytes) . "
"; echo "

16 bit types

\n"; // Chunk the data array to set of 4 bytes @@ -48,12 +51,12 @@ $values = array_chunk($recData, 2); // Get signed integer from INT interpretation echo "

INT to integer

\n"; foreach ($values as $bytes) - echo PhpType::bytes2signedInt($bytes) . "
"; + echo PhpType::bytes2signedInt($bytes) . "
"; // Get unsigned integer from WORD interpretation echo "

WORD to integer

\n"; foreach ($values as $bytes) - echo PhpType::bytes2unsignedInt($bytes) . "
"; + echo PhpType::bytes2unsignedInt($bytes) . "
"; // Get string from STRING interpretation echo "

STRING to string

\n"; diff --git a/examples/example_fc1.php b/examples/example_fc1.php index d6c7497..34d7e12 100644 --- a/examples/example_fc1.php +++ b/examples/example_fc1.php @@ -2,12 +2,16 @@ use PHPModbus\ModbusMaster; -// Create Modbus object -$modbus = new ModbusMaster("192.192.15.51", "UDP"); +$ip = filter_var($_GET['ip'], FILTER_VALIDATE_IP) ? $_GET['ip'] : '192.192.15.51'; +$unitId = ((int)$_GET['unitid']) ?: 0; +$reference = ((int)$_GET['reference']) ?: 12288; +$quantity = ((int)$_GET['quantity']) ?: 12; + +$modbus = new ModbusMaster($ip, 'UDP'); try { // FC 1 - $recData = $modbus->readCoils(0, 12288, 12); + $recData = $modbus->readCoils($unitId, $reference, $quantity); } catch (Exception $e) { // Print error information if any echo $modbus; @@ -15,10 +19,9 @@ try { exit; } -// Print status information -echo "
Status:
" . $modbus; - -// Print read data -echo "
Data:
"; -var_dump($recData); -echo "
"; +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
+print_r($recData);
+echo '
'; diff --git a/examples/example_fc15.php b/examples/example_fc15.php index 23de38b..ced94a6 100644 --- a/examples/example_fc15.php +++ b/examples/example_fc15.php @@ -1,27 +1,33 @@ writeMultipleCoils(0, 12288, $data); + // FC15 + $recData = $modbus->writeMultipleCoils($unitId, $reference, $data); } catch (Exception $e) { - // Print error information if any - echo $modbus; - echo $e; - exit; + // Print error information if any + echo $modbus; + echo $e; + exit; } -// Print status information -echo $modbus; +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
+print_r($recData);
+echo '
'; \ No newline at end of file diff --git a/examples/example_fc16.php b/examples/example_fc16.php index 1bff6f2..1cd8e49 100644 --- a/examples/example_fc16.php +++ b/examples/example_fc16.php @@ -1,25 +1,29 @@ writeMultipleRegister(0, 12288, $data, $dataTypes); + // FC16 + $recData = $modbus->writeMultipleRegister($unitId, $reference, $data, $dataTypes); } catch (Exception $e) { - // Print error information if any - echo $modbus; - echo $e; - exit; + // Print error information if any + echo $modbus; + echo $e; + exit; } -// Print status information -echo $modbus; - -?> +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
+print_r($recData);
+echo '
'; \ No newline at end of file diff --git a/examples/example_fc2.php b/examples/example_fc2.php index 1563938..d91c550 100644 --- a/examples/example_fc2.php +++ b/examples/example_fc2.php @@ -2,24 +2,27 @@ use PHPModbus\ModbusMaster; -// Create Modbus object -$modbus = new ModbusMaster("192.192.15.51", "UDP"); +$ip = filter_var($_GET['ip'], FILTER_VALIDATE_IP) ? $_GET['ip'] : '192.192.15.51'; +$unitId = ((int)$_GET['unitid']) ?: 0; +$reference = ((int)$_GET['reference']) ?: 0; +$quantity = ((int)$_GET['quantity']) ?: 2; + +$modbus = new ModbusMaster($ip, 'UDP'); try { - // FC 2 - // read 2 input bits from address 0x0 (Wago input image) - $recData = $modbus->readInputDiscretes(0, 0, 2); + // FC 2 + // read 2 input bits from address 0x0 (Wago input image) + $recData = $modbus->readInputDiscretes($unitId, $reference, $quantity); } catch (Exception $e) { - // Print error information if any - echo $modbus; - echo $e; - exit; + // Print error information if any + echo $modbus; + echo $e; + exit; } -// Print status information -echo "
Status:
" . $modbus; - -// Print read data -echo "
Data:
"; -var_dump($recData); -echo "
"; +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
+print_r($recData);
+echo '
'; \ No newline at end of file diff --git a/examples/example_fc22.php b/examples/example_fc22.php index 34ccd2e..28ca792 100644 --- a/examples/example_fc22.php +++ b/examples/example_fc22.php @@ -1,9 +1,11 @@ maskWriteRegister(0, 12288, $andMask, $orMask); + $recData = $modbus->maskWriteRegister($unitId, $reference, $andMask, $orMask); } catch (Exception $e) { // Print error information if any echo $modbus; @@ -21,5 +23,9 @@ try { exit; } -// Print status information -echo $modbus; +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
+print_r($recData);
+echo '
'; \ No newline at end of file diff --git a/examples/example_fc23.php b/examples/example_fc23.php index 0fa0ebe..a21aea4 100644 --- a/examples/example_fc23.php +++ b/examples/example_fc23.php @@ -1,28 +1,30 @@ readWriteRegisters(0, 12288, 6, 12288, $data, $dataTypes); + // FC23 + $recData = $modbus->readWriteRegisters($unitId, $reference, $quantity, $reference, $data, $dataTypes); } catch (Exception $e) { - // Print error information if any - echo $modbus; - echo $e; - exit; + // Print error information if any + echo $modbus; + echo $e; + exit; } -// Print status information -echo "
Status:
" . $modbus; - -// Print read data -echo "
Data:
"; +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
 print_r($recData);
-echo "
"; +echo '
'; \ No newline at end of file diff --git a/examples/example_fc3.php b/examples/example_fc3.php index 859e1c0..d3d12bd 100644 --- a/examples/example_fc3.php +++ b/examples/example_fc3.php @@ -2,23 +2,26 @@ use PHPModbus\ModbusMaster; -// Create Modbus object -$modbus = new ModbusMaster("192.192.15.51", "UDP"); +$ip = filter_var($_GET['ip'], FILTER_VALIDATE_IP) ? $_GET['ip'] : '192.192.15.51'; +$unitId = ((int)$_GET['unitid']) ?: 0; +$reference = ((int)$_GET['reference']) ?: 12288; +$quantity = ((int)$_GET['quantity']) ?: 6; + +$modbus = new ModbusMaster($ip, 'UDP'); try { - // FC 3 - $recData = $modbus->readMultipleRegisters(0, 12288, 6); + // FC 3 + $recData = $modbus->readMultipleRegisters($unitId, $reference, $quantity); } catch (Exception $e) { - // Print error information if any - echo $modbus; - echo $e; - exit; + // Print error information if any + echo $modbus; + echo $e; + exit; } -// Print status information -echo "
Status:
" . $modbus; - -// Print read data -echo "
Data:
"; +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
 print_r($recData);
-echo "
"; +echo '
'; \ No newline at end of file diff --git a/examples/example_fc4.php b/examples/example_fc4.php index 94478c6..eefa528 100644 --- a/examples/example_fc4.php +++ b/examples/example_fc4.php @@ -1,13 +1,16 @@ readMultipleInputRegisters(0, 0, 2); + $recData = $modbus->readMultipleInputRegisters($unitId, $reference, $quantity); } catch (Exception $e) { // Print error information if any echo $modbus; @@ -15,4 +18,9 @@ try { exit; } -var_dump($recData); +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
+print_r($recData);
+echo '
'; \ No newline at end of file diff --git a/examples/example_fc5.php b/examples/example_fc5.php index 7f47680..151aa4e 100644 --- a/examples/example_fc5.php +++ b/examples/example_fc5.php @@ -1,23 +1,26 @@ writeSingleCoil(0, 12288, $data_true); - $modbus->writeSingleCoil(0, 12289, $data_false); - $modbus->writeSingleCoil(0, 12290, $data_true); - $modbus->writeSingleCoil(0, 12291, $data_false); + // Write single coil - FC5 + $recData = $modbus->writeSingleCoil($unitId, $reference, [$value]); } catch (Exception $e) { - // Print error information if any - echo $modbus; - echo $e; - exit; + // Print error information if any + echo $modbus; + echo $e; + exit; } + +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
+print_r($recData);
+echo '
'; diff --git a/examples/example_fc6.php b/examples/example_fc6.php index 1d7c733..50e5985 100644 --- a/examples/example_fc6.php +++ b/examples/example_fc6.php @@ -1,22 +1,26 @@ writeSingleRegister(0, 12288, $data); + // FC6 + $recData = $modbus->writeSingleRegister($unitId, $reference, [$value]); } catch (Exception $e) { - // Print error information if any - echo $modbus; - echo $e; - exit; + // Print error information if any + echo $modbus; + echo $e; + exit; } -// Print status information -echo $modbus; +echo '

Status

';
+print_r($modbus);
+echo '
'; +echo '

Data

';
+print_r($recData);
+echo '
'; \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..fb3d442 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,34 @@ + + + + ./tests/* + + + + + + + + + + src + + + diff --git a/tests/Codesys/TEST.EXP b/tests/Codesys/TEST.EXP deleted file mode 100644 index 3e6f226..0000000 --- a/tests/Codesys/TEST.EXP +++ /dev/null @@ -1,256 +0,0 @@ - - -(* @NESTEDCOMMENTS := 'Yes' *) -(* @PATH := '' *) -(* @OBJECTFLAGS := '0, 8' *) -(* @SYMFILEFLAGS := '2048' *) -PROGRAM PLC_PRG -VAR - (* BOOL, COIL *) - COIL1 AT %MX0.0 : BOOL := 0; - COIL2 AT %MX0.1 : BOOL := 0; - COIL3 AT %MX0.2 : BOOL := 0; - COIL4 AT %MX0.3 : BOOL := 0; - COIL5 AT %MX0.4 : BOOL := 0; - COIL6 AT %MX0.5 : BOOL := 0; - COIL7 AT %MX0.6 : BOOL := 0; - COIL8 AT %MX0.7 : BOOL := 0; - - (* BYTE *) - BYTE1 AT %MB0 : BYTE := 0; - BYTE2 AT %MB1 : BYTE := 0; - BYTE3 AT %MB2 : BYTE := 0; - BYTE4 AT %MB3 : BYTE := 0; - BYTE5 AT %MB4 : BYTE := 0; - - (* INT *) - INT1 AT %MW0 : INT := 0; - INT2 AT %MW1 : INT := 0; - INT3 AT %MW2 : INT := 0; - INT4 AT %MW3 : INT := 0; - INT5 AT %MW4 : INT := 0; - - (* WORD *) - WORD1 AT %MW0 : WORD := 0; - WORD2 AT %MW1 : WORD := 0; - WORD3 AT %MW2 : WORD := 0; - WORD4 AT %MW3 : WORD := 0; - WORD5 AT %MW4 : WORD := 0; - - (* DINT *) - DINT1 AT %MD0 : DINT := 0; - DINT2 AT %MD1 : DINT := 0; - DINT3 AT %MD2 : DINT := 0; - DINT4 AT %MD3 : DINT := 0; - DINT5 AT %MD4 : DINT := 0; - - (* DWORD *) - DWORD1 AT %MD0 : DWORD := 0; - DWORD2 AT %MD1 : DWORD := 0; - DWORD3 AT %MD2 : DWORD := 0; - DWORD4 AT %MD3 : DWORD := 0; - DWORD5 AT %MD4 : DWORD := 0; - - (* REAL *) - REAL1 AT %MD0 : REAL := 0; - REAL2 AT %MD1 : REAL := 0; - REAL3 AT %MD2 : REAL := 0; - REAL4 AT %MD3 : REAL := 0; - REAL5 AT %MD4 : REAL := 0; - - (* String *) - STRING1 AT %MW0 : STRING := 'Hello word!!!'; -END_VAR - -(* @END_DECLARATION := '0' *) -(* Something to do *) -; -END_PROGRAM - -(* @NESTEDCOMMENTS := 'Yes' *) -(* @GLOBAL_VARIABLE_LIST := 'Global_Variables' *) -(* @PATH := '' *) -(* @OBJECTFLAGS := '0, 8' *) -(* @SYMFILEFLAGS := '2048' *) -VAR_GLOBAL -END_VAR - -(* @OBJECT_END := 'Global_Variables' *) -(* @CONNECTIONS := Global_Variables -FILENAME : '' -FILETIME : 0 -EXPORT : 0 -NUMOFCONNECTIONS : 0 -*) - -(* @NESTEDCOMMENTS := 'Yes' *) -(* @GLOBAL_VARIABLE_LIST := 'Variable_Configuration' *) -(* @PATH := '' *) -(* @OBJECTFLAGS := '0, 8' *) -(* @SYMFILEFLAGS := '2048' *) -VAR_CONFIG -END_VAR - -(* @OBJECT_END := 'Variable_Configuration' *) -(* @CONNECTIONS := Variable_Configuration -FILENAME : '' -FILETIME : 0 -EXPORT : 0 -NUMOFCONNECTIONS : 0 -*) - - -_ALARMCONFIG -_ALARMCONFIGNEXTTEXTID : 10002 -_ALARMCONFIGFORMATS : 'HH$':$'mm$':$'ss','dd$'-$'MM$'-$'yyyy' -_ALARMCLASSLIST : 1 -_ALARMCLASSID : 0 -_ALARMCLASSACKTYPE : 0 -_ALARMCLASSNAME : 'DEFAULT' -_ALARMCLASSDESCRIPTION : '' -_ALARMCLASSBGCOLORS : 16777215,16777215,16777215 -_ALARMCLASSTEXTCOLORS : 3394560,255,16711680 -_ALARMCLASSBITMAPS : '','','' -_ALARMACTIONLIST : 0 -(* @ALARMCLASSRESETCOLORS := '_ALARMCLASSRESETCOLORS: 33023,16777215' *) -(* @ALARMCLASSRESETBITMAP := '_ALARMCLASSRESETBITMAP: $'$'' *) -_ALARMGROUPLISTNAME : 'System' -_ALARMGROUPPATH : 'System' -_ALARMGROUPLIST : 0 -_VISUALSETTINGSFLAGS : 0,0,0,0 -_VISUALSETTINGSFLAGS : '','','' -_VISUALSETTINGSDYNTEXTFILECOUNT : 0 - -(* @ALARMCONFIGFLAGS := '_ALARMCONFIGFLAGS: 0' *) -(* @ALARMCONFIGGLOBALDB_STR := '_ALARMCONFIGGLOBALDB_STRINGS: $'$',$'$',$'$',$'$'' *) -(* @ALARMCONFIGGLOBALDB_NUM := '_ALARMCONFIGGLOBALDB_NUMBERS: 0,0' *) -_END_ALARMCONFIG - - -LIBRARY -Standard.lib 2.12.10 14:48:34 -(* @LIBRARYSYMFILEINFO := '0' *) -NumOfPOUs: 26 -ASCIIBYTE_TO_STRING: 2048 -CONCAT: 0 -CTD: 0 -CTU: 0 -CTUD: 0 -DELETE: 0 -F_TRIG: 0 -FIND: 0 -INSERT: 0 -LEFT: 0 -LEN: 0 -MID: 0 -R_TRIG: 0 -REAL_STATE: 2048 -REPLACE: 0 -RIGHT: 0 -RS: 0 -RTC: 0 -SEMA: 0 -SR: 0 -STANDARD_VERSION: 2048 -STRING_COMPARE: 2048 -STRING_TO_ASCIIBYTE: 2048 -TOF: 0 -TON: 0 -TP: 0 -NumOfGVLs: 1 -'Global Variables 0': 0 -END_LIBRARY - -LIBRARY -SYSLIBCALLBACK.LIB 2.12.10 14:48:32 -(* @LIBRARYSYMFILEINFO := '0' *) -NumOfPOUs: 2 -SysCallbackRegister: 0 -SysCallbackUnregister: 0 -NumOfGVLs: 2 -Globale_Variablen: 0 -Version: 0 -END_LIBRARY - -PLC_CONFIGURATION -_GLOBAL -_VERSION: 3 -_AUTOADR: 0 -_CHECKADR: 0 -_SAVECONFIGFILESINPROJECT: 0 -_END_GLOBAL - -_MODULE: '3S' -_SECTION_NAME: 'Root' -_INDEX_IN_PARENT: '-1' -_MODULE_NAME: 'Hardware configuration' -_NODE_ID: -1 -_IECIN: %IB0 -_IECOUT: %QB0 -_IECDIAG: %MB0 -_DOWNLOAD: 1 -_EXCLUDEFROMAUTOADR: 0 -_COMMENT: '' - -_MODULE: '3S' -_SECTION_NAME: 'K_Bus' -_INDEX_IN_PARENT: '1' -_MODULE_NAME: 'K-Bus' -_NODE_ID: 0 -_IECIN: %IB0 -_IECOUT: %QB0 -_IECDIAG: %MB0 -_DOWNLOAD: 1 -_EXCLUDEFROMAUTOADR: 0 -_COMMENT: '' -_END_MODULE - -_MODULE: '3S' -_SECTION_NAME: 'FB_VARS' -_INDEX_IN_PARENT: '2' -_MODULE_NAME: 'Fieldbus variables' -_NODE_ID: 1 -_IECIN: %IB0 -_IECOUT: %QB0 -_IECDIAG: %MB0 -_DOWNLOAD: 1 -_EXCLUDEFROMAUTOADR: 0 -_COMMENT: '' -_END_MODULE -_END_MODULE -PLC_END - - -RESOURCE -{event_task : 'start','Called when program starts','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,1,11986} -{event_task : 'stop','Called when program stops','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,2,11986} -{event_task : 'before_reset','Called before reset takes place','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,3,11986} -{event_task : 'after_reset','Called after reset took place','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,4,11986} -{event_task : 'shutdown','Called before shutdown is performed (Firmware update over ethernet)','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,5,11986} -{event_task : 'excpt_watchdog','Software watchdog of IEC-task expired','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,7,11986} -{event_task : 'excpt_fieldbus','Fieldbus error','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,9,11986} -{event_task : 'excpt_ioupdate','KBus error','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,10,11986} -{event_task : 'excpt_dividebyzero','Division by zero. Only integer operations!','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,18,11986} -{event_task : 'excpt_noncontinuable','Exception handler','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,20,11986} -{event_task : 'after_reading_inputs','Called after reading of inputs','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,28,11986} -{event_task : 'before_writing_outputs','Called before writing of outputs','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,29,11986} -{event_task : 'debug_loop','Debug loop at breakpoint','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,31,11986} -{event_task : 'online_change','Is called after CodeInit() at Online-Change','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,33,11986} -{event_task : 'before_download','Is called before the Download starts','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,34,11986} -{event_task : 'event_login','Is called before the login service is performed','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,501,11986} -{event_task : 'eth_overload','Ethernet Overload','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,750,11986} -{event_task : 'eth_network_ready','Is called directly after the Network and the PLC are initialised','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,751,11986} -{event_task : 'blink_code','New blink code / Blink code cleared ( Call STATUS_GET_LAST_ERROR for details )','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,752,11986} -{event_task : 'interrupt_0','Interrupt Real Time Clock (every second)','','FUNCTION systemevent: DWORD VAR_INPUT dwEvent: DWORD; dwFilter: DWORD; dwOwner: DWORD; END_VAR '}{event_task_info : 0,1000,11986} - -END_RESOURCE - - -_WORKSPACE -_GLOBALVISUALSETTINGS -_VISUALSETTINGSFLAGS : 0,0,0,0 -_VISUALSETTINGSFLAGS : '','','' -_VISUALSETTINGSDYNTEXTFILECOUNT : 0 -_VISUALBITMAPLISTCOUNT : 0 -_END_GLOBALVISUALSETTINGS -_END_WORKSPACE diff --git a/tests/Codesys/_make_exp.cmd b/tests/Codesys/_make_exp.cmd deleted file mode 100644 index 415af58..0000000 --- a/tests/Codesys/_make_exp.cmd +++ /dev/null @@ -1,15 +0,0 @@ -rem Create Codesys EXP file - -rem Build cmd file -set CODESYS="c:\Program Files (x86)\WAGO Software\CoDeSys V2.3\codesys.exe" -set PROJECT=test -del %PROJECT%.EXP -echo file open %PROJECT%.pro >> codesys_cmd_file.cmd -echo project export %PROJECT%.EXP >> codesys_cmd_file.cmd -rem echo file saveas %PROJECT%.lib internallib >> codesys_cmd_file.cmd -echo file close >> codesys_cmd_file.cmd -echo file quit >> codesys_cmd_file.cmd -%CODESYS% /noinfo /cmd codesys_cmd_file.cmd - -rem Clean all when finished -del codesys_cmd_file.cmd \ No newline at end of file diff --git a/tests/Codesys/test.pro b/tests/Codesys/test.pro deleted file mode 100644 index 6904c0c412cd9814cce6b332086a34bc044fff9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34020 zcmeHQ3yfRGb$u(zwq#q5tvIghIPu7_EXk7gC&`lKCMB2TO0>93isb5JsA0Lwx4X2s zd@Mg&tzo!w`bBFXO!*}^zN!D&nLBsxeBa|t$P2rvh02b;BaY+z%)@ls*yrHSag=kd zvxcB|I?m0Plivy8_7>a%ee3By+`qg3uKwZuFM~ROfRI<5&d)^7RNZ1F?-lqJ;QE~Ch=v;PTl$bUejkblWI4olmxvB*s%!9^FMpJHql;vT|v65^vPA;jm}2=V2%LTrM*8Vi>6 z`SXO;76E|W8t44}KKk^n3AF>P0hklHe>Sc;9u8dpH;QudrnF1cjeoGktgYXBWTy?soD+S)p|J?VJpv z#5IPS?-rM?GoD-Msg7pL`RruDRnuNsg~BS5n{idxt-4cHYKIUZLoastzMFt9lvB)?mG!^cPOSbZohe;CgR5OXSAdn!;7X?0LULb1aDwF9)cg!tgtvQqh(Vz!{DA=zHT0c_{4-X@924P}R}zPSu+YBo1^l!KVsxo*0Tj;A6? zF+_n{S3+*Y+s3?XqXV TcAy3BK2FUdT!;GAc5Nf`Kp{>y5P_qS2a&-W%~X4QO$x zdGKYExi~^YJgTj}F(KNnGB?X(V(`Vju*!*O#-hTif#@p8$}~Dgj*FvRlyCA4-{PQ6 zNJfG&n*RjT5#4qYiYrma9vfx~CkAU6IGwiRb|@Ikkz_K?y^1y)+E745GeDepixYXR(B$^4D`xqfINMC<*#3d6w zcxa{Dqk1|QM{F2CGUXc?{M-z*&Ie=zz%>n%2A=E^4>?^DvhM>15grj#@GmaO;vhub zb?Sv7QZgOVav};u-7;lG7jGS;4d0U#C|)vxCA@54w2!~Lnw{aIq^BPN)ln7 zBB6%a%u1D5saggg(l(Kgkc~*8Oja=R;b)M$lRJPu-YNr&hFk0t8188nVirX zV0}T*&yH19rCJ6k*Yn_SzZlpiMi6Tey9;`zgPSCnUe1d!7ju$2Uh$f|-Rvx=%xLs(Yx`Rc5i&K3%G?QJKmbkWjIL%3X; zLjnnI_;jQ?0b9X%aS$Fwg*78Y0H%)Z;xL3H;?ZC%71^m0 zX=0o5in)r)PGc4y9apoo@|SH8o1@izl$Hz0_zfw{Qn`g}sY25{mX8p1*Qp)SB|82F zEKyNn?Fv*DIBRZYOLUMljS@{KC%P7>ka|VR6E=}f_?=|e%4P=-7hKskBiHG2erAS@ z7VG4Ct=gMb7>Sz&i|*BFD@Q@p1-Cdu71i(_%FoR1G7;p9YG`n-vV65d@21ieSE*L0 z=*9BR_2qU;JY`n0OS4&GYLh+@)?K@LG)bWs{cX{S6ul=31!J+1VCXR389|M3+*zDR z*`Q?PU^JDEB>m0;gedlWB0do<=G-%Aig4Mc(vAhW^oU3?6>>gBt>k2O>Xe$U75Ozs zKQG5dPSZ+1V$j0}9UqcNrc;^7(a1O&%WM6?7uypvp^YrNGx-WyHSn?Y3zd*bC~3kF zq@B&C^*27h@rAqYZZ{Tq89yV#DqQ?M7Yq@Ru zmZ^n%w@;zDQ4E&5*%(^5_cU`OpO#w>E^W$=)55*CnHzn)<(30P#L>ciXEQfygyp^r zEW)*L?`!79gv4@NQ?wDZaPM#CM$NR`*4`Sp5v9UwX0FiyxYV_V6$K9Pz_!mB8v$S2 zI1qr0ftyQRa-%Z=HzZq@n@e4CqfG)gaLaOYsY`CWxCJ*#d|7TTb;*s@b#SA^+j1k% zG$uUUQb4RVTSI3qabywaq=@>yoqXB)6i#<#iW^$w2}DtUKu1aTHwx z&b#PNafIp7T>@~k?j-kZh8rc?Reo@@?j-jX!`;>DtmLda$$h)wURHNVn{_9-w;FEX z+DZ^lv6SN+x5S%H#$mtpU! zz&bl?P_p+M_Kq3^tHOFbTOR<{AJC*Vf1a)5gK-Y>i`&iKDAhZ5*uA);RW>I67(;0}PrZm9Z27JJoA6tljm_5klZw zfLw61L58b)Hf2e@l14h zTxk{k*_S@>&`-a9-KP#Y*WP>2lUOdQyYy6PAX-w}Vn{;`tE5+}x>UrZnhdEXGng6L z=j=`?L)So7R$*WBuu^C~Xw>ejTG8sjtXHWPvvV%lov-%6eu^7!7?4$H-ElCSdtx1C zwEqKba?wR|`ZEYZI{WA%uQNuXefL}8(=zb#Ln{0DM_*(^hn}BZ?@H+&2i-38;fXK;h75Hre{;YsMC*VI9 z@Lvh|uMLd6{Dr`OS-@Wr@K*)>7X+o9VUo1=vU$ZdL{IY-_6!2LAe?Y*$E8yP~@b3%w5dnWtz#kIu9|-uv0)9xq?-TH^ z2>4e8{A&XKbpii|g#(WBn*#nV0sppu9~SWY1^l>$N#~Cl7>`d_Ji#Ba@L6gE4Geo2 zw|Ii%0!|3{7X^J*;Hv^&5b*s1o-{DhniBZqdMHF#YM2F$E8vWvKO*3h1S3AUq`)5* z@Pwc_F5r6w{O^MP83UtAKWkvL1J4Qk^9DxRKQJ&l#UBd%%L0DUz(nG_B;Z#JjK`}6 z#yIOog678pe$Bw!i2iy#EJu{i4FbN=z>xnYfxlV6?-VpVPUl#ty)t;@YqOw#mw;~* zG>;0|-Yei~10$Xpfu9v{UcmPW_>_Ukgq=~5?jZvs_#Oje7#tHcF!Vq@ zXkh5)tbw7=Ga}CW4GdfQq=@rR3=Da;7#g5HEolB!z@HKH0aKU3`)!fdGXnmmp@%%T z3;at0-XP-HB-)2(1{IS44Dd0a7@TUa)X#symz)uMHF9rO0 z0Y4?+9|-tA1$;T>3v$6-C-NH=_Sd+CevkmDU8YL z;~3=(*2gYiS?kotF3NG)$i* zaq1G5@4_GZ-kY@xe?fFR|-D3UYGCx!rXTI-|Thm zY#XqVUf0g5=ymN{4d^}2cZHzWwQJq=x^}IHUe~VmAZO<*(Y0S8X3SR$xYy-3cKKiJ zb@yzLmt1#0*kL9=C}#5X_NF(J=iKk( z(M4}D{}Nox4>-zS%)i24%nyhm0KvT>0Jkx{Aponv!79b2@NT|01kmNt8vv#bI>%2*wc#}0@i`3~ChQ-l8@^n?HIDC+(FY&^-?iXVeK_PF z-;VK*WSIg~ps@S?*=vsEUx5#U!DU`DoRTfZEC8ICN>m-AKqoj805+QlgH zaAxezjCJmIdf<&dnF1OD1u_G;&Tjl;`Qo|JWzQ^2hf=|{mlA~Vy&}^(NH3JM+1kQ? zkd$)`T`&Op`4rF-=82zE;jQKb00hJ=0ss&xivR!we(8xPeoX~d{xKC0_%RL;F#s@8 z{Yw@CxOjXcL4AXf;I|TBWPy!8>`rHE1#P^Y=wMjZkh;(YzU z_yg_@-QRN|GJ2lVLXAX`p<9&h6>7G+kFLS>KDsjD+T7ei0L%f@ z)j*$y5iLW6)9zz01}w-=@&_rg&Pz5Xsav`<`cXm3U{d@GY?$_-dhuH*eSfJKI``X{Wa253CyQ{{$T&>{t zCq1vaX4lZhOs42nGt;!QcP^6w*$s!X<=lL>>?+zUj7~+Vm9sdD09{SvIVMe~mylQQ z>Y>xxf^2yVUu}{+oEfQA`i7(g2*2#`F2VykulT{B3hM1y~k-L1NC zJ4vEbgV+t}Vi16FHSb`DU%-ZFrzG0YZGP4*D*83?OgTGewzmTx%wlw6Jd~z=8C7M0 zwpp5^pYRr|2h0Wt$ZK>wF_BiedESu81l&#j(R_h+WfF+^d{Ov~Y7|!htns6%s8qcY zHvVWYl~-b0Sq-6J!Vn763cdYahBx)Q#TU-(%ExF`&7N{As#M5MxkRxldPDg`M2t&# zTR$x!b}cdL)0_Pn0X^^3>dJ@mgnSqYRAy_{9Gy}U@`W?%vmw~ruH=?!lealC$8Dqe z@*KBBwNfsNMFslpy>iX(^|wcgx-#gbkA)$AHl<{uOZjDo(o|DC~oPEeJibQ0@{d zn~8W;$`!IgW;EPd=Au`m?zx&T)^L0f=75niQ*H@8HSOwIq=mdyD_{s^g|kgiHbX$t*S{{Z#h-+$UG@%RU||I2g$IyKpTSOi*9C$ zjBduo*g9I_9UiIu);&*%c0-94^VMzJAuQD-BHtBaa-v=pc~MUx?_20;${qbsfeya9 zjZnVCrp&RgC=T)g!77h~ZZ)E!Kj%Rf75YrRIM7K<4yBEK%ct{GZbSD;9IK+-AJbqM z%Ecgw`sNHeX(*1{B-2-`xIwO#P(P%h8-#)o5Q_Hqp{fMx zYjZ`}k85g0?I`atOM6->Oo;Mi#`$a^k3DtBt;P`aMUxRU0+UpAPGzR3K8=%a+?`j9 zp?O-)RRd}S!P+xba7kVoK`At53I|?Hq%w4FU?vt!(J_%Ytb+IUq2nmB`9ejl+F zlONbOZ!A=<(ST}j8+oJH#2a2Z6pgm>#>U+$RPd%wsclpf$_r-u>QxgJTLr;}Zu{@9 zs|9^_HMY|v3`1YqvbdO|d9}lbL2mF*gzUsbJdKX$ofu7bUO2Jy&QN@OGygLKf?*xzV8NTDZME#{yNa`(XVbWKXSTUoh1oP`!rmJ4he2%T{9!y{GnUau zj+#{yk<0E;*j%nc*>P6<8{XwTF!;cr6X5YfAW8lpol0470wBWB^U$HK2e!`5(Tz4a z<8`!a2%F1o-L-XWjBX3$up4+IMoG3GQ62gny=+2+jSgsB)HFDKI|+hk2^W?LpWdyX zgkeA7(HJ^`rL&rXmp}r9!R#j-84XUv(gYdT$L|e!(L!VX|MQC$OzK7}8kuh6Fy3p_ zt0o8OCbH4Bx=rAflXZR073(on*hxd&8=QyMUbO~ivC2Q_$ZXN8*oEpoz43W zngQEWY7OtkD|ta{!?rurNIV|fsY0~Fv$|vZ#w#}f8dAZu+A?;0uzye;Ub| z6FgiG9%>i7yB<8;E_hEpcz3(tz4hQd?Sk*D2k&hcyssX7XS?A2_27L?!IU*91A8B{ z8hG<#O+CU&K!{ZW?`>=XSq063Mg=3RfaXA>fDz7qbD+uo2xnddYWe7myB3cCQ%J`k z9q?8-f=vM(gKSMfMzUj&tSQLIbqsPf1sSQ1K`IfXh2mXQS!sZzSR7*If=3G05-3G$ z36#RM1WNH_pq39`GH9&N_eE0ErC+V zmOv?DOP~}`25R}tuBog`vu&zYCf5>;K}Ffxuzl)QYS`+F{F~5XQ&W)g=4j@DJy>y= zrpLL)u>uW_{B)#2x3^(ycn@Yfe(W#gCl&1^O8w(YFpOsCtm|gPgpWWcje~!E z4C2vevNq!MVWvI~oH!cP$AD9xap~hgg>x!>3}m81AR7^M7^9CsJo+F*ABVvy3_%|Y zXz-M4EN+;?_k3bZTj=1`Ln2j-UXv-G1X9qEYCZyjrsz;H9|M+D()VNh*n9w%2_;gj zuW8G(M^O1;cuiu$bBl>D?bLMREsq!T^?#vw3aJz!UMTua-*`ZQ}Oe3Gt| zf0~3nWJABF)k-kub`T+dzE?lUBSUB1(`h2D_*8r%8HxZe9fp=7CI8%tMuD(Py%yT7 zPcw=>3Ci=RTgJk!kCe$cH?CoCrzsf}Gwi%d4_w-TgQ{8w+!&xh04Wn(fo~i$O;*e~?};(assertEquals(125, ord(IecType::iecBYTE(125))); + $this->assertEquals(98, ord(IecType::iecBYTE(98))); + $this->assertEquals(0, ord(IecType::iecBYTE(0))); + $this->assertEquals(255, ord(IecType::iecBYTE(255))); + $this->assertEquals(88, ord(IecType::iecBYTE(88))); + } +} \ No newline at end of file diff --git a/tests/IecType/IecDIntTest.php b/tests/IecType/IecDIntTest.php new file mode 100644 index 0000000..b1eaaef --- /dev/null +++ b/tests/IecType/IecDIntTest.php @@ -0,0 +1,31 @@ +assertEquals('00000000', self::unPackDInt2HexString(0)); + $this->assertEquals('00010000', self::unPackDInt2HexString(1)); + $this->assertEquals('ffffffff', self::unPackDInt2HexString(-1)); + $this->assertEquals('ffff7fff', self::unPackDInt2HexString(pow(2, 31) - 1)); + $this->assertEquals('00008000', self::unPackDInt2HexString(-pow(2, 31))); + } + + public function testIecDintEndianingOn() + { + $this->assertEquals('00000000', self::unPackDInt2HexString(0, 1)); + $this->assertEquals('00000001', self::unPackDInt2HexString(1, 1)); + $this->assertEquals('ffffffff', self::unPackDInt2HexString(-1, 1)); + $this->assertEquals('7fffffff', self::unPackDInt2HexString(pow(2, 31) - 1, 1)); + $this->assertEquals('80000000', self::unPackDInt2HexString(-pow(2, 31), 1)); + } +} diff --git a/tests/IecType/IecIntTest.php b/tests/IecType/IecIntTest.php new file mode 100644 index 0000000..3abc828 --- /dev/null +++ b/tests/IecType/IecIntTest.php @@ -0,0 +1,22 @@ +assertEquals('0000', self::unPackInt2HexString(0)); + $this->assertEquals('0001', self::unPackInt2HexString(1)); + $this->assertEquals('ffff', self::unPackInt2HexString(-1)); + $this->assertEquals('7fff', self::unPackInt2HexString(pow(2, 15) - 1)); + $this->assertEquals('8000', self::unPackInt2HexString(-pow(2, 15))); + } +} \ No newline at end of file diff --git a/tests/IecType/IecRealTest.php b/tests/IecType/IecRealTest.php new file mode 100644 index 0000000..070568d --- /dev/null +++ b/tests/IecType/IecRealTest.php @@ -0,0 +1,31 @@ +assertEquals('00000000', self::unPackReal2HexString(0)); + $this->assertEquals('00003f80', self::unPackReal2HexString(1)); + $this->assertEquals('0000c000', self::unPackReal2HexString(-2)); + $this->assertEquals('aaab3eaa', self::unPackReal2HexString(0.333333333333)); + $this->assertEquals('000041c8', self::unPackReal2HexString(25)); + } + + public function testIecRealEndianingOn() + { + $this->assertEquals('00000000', self::unPackReal2HexString(0, 1)); + $this->assertEquals('3f800000', self::unPackReal2HexString(1, 1)); + $this->assertEquals('c0000000', self::unPackReal2HexString(-2, 1)); + $this->assertEquals('3eaaaaab', self::unPackReal2HexString(0.333333333333, 1)); + $this->assertEquals('41c80000', self::unPackReal2HexString(25, 1)); + } +} diff --git a/tests/IecType/_test.bat b/tests/IecType/_test.bat deleted file mode 100644 index 8122004..0000000 --- a/tests/IecType/_test.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -call ../config.bat - -for %%f in (test.*.php) do %php% -q "%%f" > "output/%%f.html" - -cd output -for %%f in (*.html) do %diff% "%%f" ../ref/"%%f" -cd .. -pause - -@echo on \ No newline at end of file diff --git a/tests/IecType/ref/test.iecByte.php.html b/tests/IecType/ref/test.iecByte.php.html deleted file mode 100644 index dc6e8a7..0000000 --- a/tests/IecType/ref/test.iecByte.php.html +++ /dev/null @@ -1 +0,0 @@ -125
98
0
0
0
0
0
0
0
0
0
0
255
255
255
255
158
88
97
168
\ No newline at end of file diff --git a/tests/IecType/ref/test.iecDInt.php.html b/tests/IecType/ref/test.iecDInt.php.html deleted file mode 100644 index 1abf31b..0000000 --- a/tests/IecType/ref/test.iecDInt.php.html +++ /dev/null @@ -1 +0,0 @@ -Endianing off
0 --> Packet: 0000_0000_
1 --> Packet: 0001_0000_
-1 --> Packet: ffff_ffff_
2147483647 --> Packet: ffff_7fff_
-2147483648 --> Packet: 0000_8000_
Endianing on
0 --> Packet: 0000_0000_
1 --> Packet: 0000_0001_
-1 --> Packet: ffff_ffff_
2147483647 --> Packet: 7fff_ffff_
-2147483648 --> Packet: 8000_0000_
\ No newline at end of file diff --git a/tests/IecType/ref/test.iecInt.php.html b/tests/IecType/ref/test.iecInt.php.html deleted file mode 100644 index 583ad9b..0000000 --- a/tests/IecType/ref/test.iecInt.php.html +++ /dev/null @@ -1 +0,0 @@ -Endianing off
0 --> Packet: 0000_
1 --> Packet: 0001_
-1 --> Packet: ffff_
32767 --> Packet: 7fff_
-32768 --> Packet: 8000_
Endianing on
0 --> Packet: 0000_
1 --> Packet: 0001_
-1 --> Packet: ffff_
32767 --> Packet: 7fff_
-32768 --> Packet: 8000_
\ No newline at end of file diff --git a/tests/IecType/ref/test.iecReal.php.html b/tests/IecType/ref/test.iecReal.php.html deleted file mode 100644 index a195b00..0000000 --- a/tests/IecType/ref/test.iecReal.php.html +++ /dev/null @@ -1,12 +0,0 @@ -Endianing off
-0 --> Packet: 0000_0000_
-1 --> Packet: 0000_3f80_
--2 --> Packet: 0000_c000_
-0.333333333333 --> Packet: aaab_3eaa_
-25 --> Packet: 0000_41c8_
-Endianing on
-0 --> Packet: 0000_0000_
-1 --> Packet: 3f80_0000_
--2 --> Packet: c000_0000_
-0.333333333333 --> Packet: 3eaa_aaab_
-25 --> Packet: 41c8_0000_
diff --git a/tests/IecType/test.iecByte.php b/tests/IecType/test.iecByte.php deleted file mode 100644 index 8af17c8..0000000 --- a/tests/IecType/test.iecByte.php +++ /dev/null @@ -1,33 +0,0 @@ - 125, // 32098 (DINT) - "1" => 98, - "2" => 0, - "3" => 0, - "4" => 0, // 0 (DINT) - "5" => 0, - "6" => 0, - "7" => 0, - "8" => 0, // 0 (DINT) - "9" => 0, - "10" => 0, - "11" => 0, - "12" => 255, // -1 (DINT) - "13" => 255, - "14" => 255, - "15" => 255, - "16" => 158, // -25000 (INT) - "17" => 88, - "18" => 97, // 25000 (INT) - "19" => 168 -); - -// Print mixed values -foreach($data as $d) - echo ord(IecType::iecBYTE($d)) . "
"; - -?> \ No newline at end of file diff --git a/tests/IecType/test.iecDInt.php b/tests/IecType/test.iecDInt.php deleted file mode 100644 index 24a4820..0000000 --- a/tests/IecType/test.iecDInt.php +++ /dev/null @@ -1,50 +0,0 @@ - 0, - "1" => 1, - "2" => -1, - "3" => pow(2,31)-1, - "4" => -pow(2,31) -); - -function byte2hex($value){ - $h = dechex(($value >> 4) & 0x0F); - $l = dechex($value & 0x0F); - return "$h$l"; -} - -function printPacket($packet){ - $str = ""; - $str .= "Packet: "; - for($i=0;$i"; -// Print mixed values -for($i=0;$i "; - $v = IecType::iecDINT($data[$i], 0); - echo printPacket($v); - "
"; -} - -echo "Endianing on
"; -// Print mixed values -for($i=0;$i "; - $v = IecType::iecDINT($data[$i], 1); - echo printPacket($v); - "
"; -} - -?> \ No newline at end of file diff --git a/tests/IecType/test.iecInt.php b/tests/IecType/test.iecInt.php deleted file mode 100644 index dec812e..0000000 --- a/tests/IecType/test.iecInt.php +++ /dev/null @@ -1,50 +0,0 @@ - 0, - "1" => 1, - "2" => -1, - "3" => pow(2,15)-1, - "4" => -pow(2,15) -); - -function byte2hex($value){ - $h = dechex(($value >> 4) & 0x0F); - $l = dechex($value & 0x0F); - return "$h$l"; -} - -function printPacket($packet){ - $str = ""; - $str .= "Packet: "; - for($i=0;$i"; -// Print mixed values -for($i=0;$i "; - $v = IecType::iecINT($data[$i], 0); - echo printPacket($v); - "
"; -} - -echo "Endianing on
"; -// Print mixed values -for($i=0;$i "; - $v = IecType::iecINT($data[$i], 1); - echo printPacket($v); - "
"; -} - -?> \ No newline at end of file diff --git a/tests/IecType/test.iecReal.php b/tests/IecType/test.iecReal.php deleted file mode 100644 index 30495fd..0000000 --- a/tests/IecType/test.iecReal.php +++ /dev/null @@ -1,50 +0,0 @@ - 0, // -> 0000 0000 - "1" => 1, // -> 3f80 0000 - "2" => -2, // -> c000 0000 - "3" => 0.333333333333, //1/3 -> 3eaa aaab - "4" => 25 // -> 41c8 0000 -); - -function byte2hex($value){ - $h = dechex(($value >> 4) & 0x0F); - $l = dechex($value & 0x0F); - return "$h$l"; -} - -function printPacket($packet){ - $str = ""; - $str .= "Packet: "; - for($i=0;$i\n"; -// Print mixed values -for($i=0;$i "; - $v = IecType::iecREAL($data[$i], 0); - echo printPacket($v); - "
\n"; -} - -echo "Endianing on
\n"; -// Print mixed values -for($i=0;$i "; - $v = IecType::iecREAL($data[$i], 1); - echo printPacket($v); - "
\n"; -} - -?> \ No newline at end of file diff --git a/tests/ModbusMaster/BindClientToLocalIpAndPortTest.php b/tests/ModbusMaster/BindClientToLocalIpAndPortTest.php new file mode 100644 index 0000000..b171356 --- /dev/null +++ b/tests/ModbusMaster/BindClientToLocalIpAndPortTest.php @@ -0,0 +1,25 @@ +port = $port; + // use wildcard as multiple IPs on same machine ala VirtualBox network adapter installed may result ip that you cannot bind to + // good enough for test that no thing throws exception. + $modbus->client = '0.0.0.0'; + $modbus->client_port = mt_rand(30000, 40000); + + $this->assertEquals([1], $modbus->readCoils(0, 256, 1)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000101000001', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php b/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php new file mode 100644 index 0000000..bc59828 --- /dev/null +++ b/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php @@ -0,0 +1,21 @@ +port = $port; + + $this->assertTrue($modbus->fc15(0, 12288, [1, 0, 1])); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000008000f300000030105', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc16WriteMultipleRegistersTest.php b/tests/ModbusMaster/Fc16WriteMultipleRegistersTest.php new file mode 100644 index 0000000..ca1dcff --- /dev/null +++ b/tests/ModbusMaster/Fc16WriteMultipleRegistersTest.php @@ -0,0 +1,21 @@ +port = $port; + + $this->assertTrue($modbus->fc16(0, 12288, [-1,100001,1.3], ['INT', 'DINT', 'REAL'])); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('000000110010300000050affff86a1000166663fa6', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc1ReadCoilsTest.php b/tests/ModbusMaster/Fc1ReadCoilsTest.php new file mode 100644 index 0000000..e4d0b82 --- /dev/null +++ b/tests/ModbusMaster/Fc1ReadCoilsTest.php @@ -0,0 +1,35 @@ +port = $port; + + $this->assertEquals([1], $modbus->readCoils(0, 256, 1)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000101000001', $packetWithoutTransactionId); + } + + public function testFc1Read3Coils() + { + $mockResponse = '31be0000000400010103'; // respond with 1 byte (00000011 bits set) [1, 1, 0] + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + $this->assertEquals([1, 1, 0], $modbus->fc1(0, 256, 3)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000101000003', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc22MaskWriteRegisterTest.php b/tests/ModbusMaster/Fc22MaskWriteRegisterTest.php new file mode 100644 index 0000000..6450828 --- /dev/null +++ b/tests/ModbusMaster/Fc22MaskWriteRegisterTest.php @@ -0,0 +1,26 @@ +port = $port; + + $bitValue = true; + $bitNumber = 2; + $andMask = 0xFFFF ^ pow(2, $bitNumber) ; + $orMask = 0x0000 ^ (pow(2, $bitNumber) * $bitValue ) ; + + $this->assertTrue($modbus->fc22(0, 12288, $andMask, $orMask)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('0000000800163000fffb0004', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc23ReadWriteRegistersTest.php b/tests/ModbusMaster/Fc23ReadWriteRegistersTest.php new file mode 100644 index 0000000..83c821c --- /dev/null +++ b/tests/ModbusMaster/Fc23ReadWriteRegistersTest.php @@ -0,0 +1,24 @@ +port = $port; + + $data = array(10, -1000, 2000, 3.0); + $dataTypes = array("INT", "INT", "DINT", "REAL"); + + $this->assertEquals([0, 10, 252, 24, 7, 208, 0, 0, 0, 0, 64, 64], $modbus->fc23(0, 12288, 6, 12288, $data, $dataTypes)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000017001730000006300000060c000afc1807d0000000004040', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc2ReadInputDiscretesTest.php b/tests/ModbusMaster/Fc2ReadInputDiscretesTest.php new file mode 100644 index 0000000..00c95b1 --- /dev/null +++ b/tests/ModbusMaster/Fc2ReadInputDiscretesTest.php @@ -0,0 +1,35 @@ +port = $port; + + $this->assertEquals([1], $modbus->readInputDiscretes(0, 256, 1)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000201000001', $packetWithoutTransactionId); + } + + public function testFc1Read3InputDiscretes() + { + $mockResponse = 'b5110000000400020103'; + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + $this->assertEquals([1, 1, 0], $modbus->fc2(0, 256, 3)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000201000003', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc3ReadMultipleRegistersTest.php b/tests/ModbusMaster/Fc3ReadMultipleRegistersTest.php new file mode 100644 index 0000000..18b77db --- /dev/null +++ b/tests/ModbusMaster/Fc3ReadMultipleRegistersTest.php @@ -0,0 +1,35 @@ +port = $port; + + $this->assertEquals([0, 3], $modbus->readMultipleRegisters(0, 256, 1)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000301000001', $packetWithoutTransactionId); + } + + public function testFc3Read3Words() + { + $mockResponse = 'e4710000000900030693e000040000'; // respond with 3 WORD (3*2 bytes) [147, 224, 0, 4, 0, 0] + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + $this->assertEquals([147, 224, 0, 4, 0, 0], $modbus->fc3(0, 268, 3)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('000000060003010c0003', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc4ReadMultipleInputRegistersTest.php b/tests/ModbusMaster/Fc4ReadMultipleInputRegistersTest.php new file mode 100644 index 0000000..01cc9ef --- /dev/null +++ b/tests/ModbusMaster/Fc4ReadMultipleInputRegistersTest.php @@ -0,0 +1,35 @@ +port = $port; + + $this->assertEquals([0, 3], $modbus->readMultipleInputRegisters(0, 256, 1)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000401000001', $packetWithoutTransactionId); + } + + public function testFc4Read3Words() + { + $mockResponse = 'e4710000000900030693e000040000'; // respond with 3 WORD (3*2 bytes) [147, 224, 0, 4, 0, 0] + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + $this->assertEquals([147, 224, 0, 4, 0, 0], $modbus->fc4(0, 268, 3)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('000000060004010c0003', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc5WriteSingleCoilTest.php b/tests/ModbusMaster/Fc5WriteSingleCoilTest.php new file mode 100644 index 0000000..064f153 --- /dev/null +++ b/tests/ModbusMaster/Fc5WriteSingleCoilTest.php @@ -0,0 +1,35 @@ +port = $port; + + $this->assertTrue($modbus->writeSingleCoil(0, 4096, [1])); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('0000000600051000ff00', $packetWithoutTransactionId); + } + + public function testFc5WriteSingleCoilWith0() + { + $mockResponse = '489c00000006000510000000'; + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + $this->assertTrue($modbus->fc5(0, 4096, [0])); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000510000000', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/Fc6WriteSingleRegisterTest.php b/tests/ModbusMaster/Fc6WriteSingleRegisterTest.php new file mode 100644 index 0000000..212c847 --- /dev/null +++ b/tests/ModbusMaster/Fc6WriteSingleRegisterTest.php @@ -0,0 +1,35 @@ +port = $port; + + $this->assertTrue($modbus->writeSingleRegister(0, 4096, [15])); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('0000000600061000000f', $packetWithoutTransactionId); + } + + public function testFc6WriteSingleRegisterWith0() + { + $mockResponse = '489c00000006000510000000'; + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + $this->assertTrue($modbus->fc6(0, 4096, [0])); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000610000000', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/MockResponseServer.php b/tests/ModbusMaster/MockResponseServer.php new file mode 100644 index 0000000..081d415 --- /dev/null +++ b/tests/ModbusMaster/MockResponseServer.php @@ -0,0 +1,86 @@ +port = $port; + + $loop = Factory::create(); + + if ('TCP' === $protocol) { + $this->startTcpServer($loop, $answerTimeout, $responsePacket); + } else { + $this->startUdpServer($loop, $answerTimeout, $responsePacket); + } + + $loop->addPeriodicTimer(self::MAX_WAIT_TIMEOUT, function () use ($loop) { + $loop->stop(); + }); + $loop->run(); + } + + private function startTcpServer($loop, $answerTimeout, $responsePacket) + { + $socket = new Server($loop); + + $socket->on('connection', function ($conn) use ($socket, $loop, $answerTimeout, $responsePacket) { + $conn->on('data', function ($data) use ($conn, $answerTimeout, $responsePacket) { + if ($answerTimeout) { + sleep($answerTimeout); + } + $conn->write(pack('H*', $responsePacket)); + + echo unpack('H*', $data)[1]; + }); + + $conn->on('close', function () use ($socket, $loop) { + $socket->shutdown(); + $loop->stop(); + }); + }); + + $socket->listen($this->port); + } + + private function startUdpServer($loop, $answerTimeout, $responsePacket) + { + $factory = new \React\Datagram\Factory($loop); + + $factory->createServer('127.0.0.1:' . $this->port)->then(function (Socket $server) use ($loop, $answerTimeout, $responsePacket) { + $server->on('message', function ($message, $address, Socket $server) use ($loop, $answerTimeout, $responsePacket) { + if ($answerTimeout > 0) { + sleep($answerTimeout); + } + $server->send(pack('H*', $responsePacket), $address); + + echo unpack('H*', $message)[1]; + + $loop->addTimer(0.001, function () use ($server, $loop) { + $server->emit('shutdown', [$server]); + }); + }); + + //silly but otherwise client will not receive packets from server. probably server is closed before stream is flushed etc + $server->on('shutdown', function () use ($server, $loop) { + $loop->addTimer(0.002, function () use ($server, $loop) { + $server->close(); + $loop->stop(); + }); + }); + }); + } +} + +new MockResponseServer($argv[1], $argv[2], $argv[3], $argv[4]); \ No newline at end of file diff --git a/tests/ModbusMaster/MockServerTestCase.php b/tests/ModbusMaster/MockServerTestCase.php new file mode 100644 index 0000000..b6e93fd --- /dev/null +++ b/tests/ModbusMaster/MockServerTestCase.php @@ -0,0 +1,32 @@ +addTimer(0.001, function (Timer $timer) use ($process, $closure, $port, &$clientData) { + $process->start($timer->getLoop()); + + $process->stdout->on('data', function ($output) use (&$clientData) { + $clientData[] = $output; + }); + + $closure($port); + }); + + $loop->run(); + return $clientData; + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/ModbusExceptionTest.php b/tests/ModbusMaster/ModbusExceptionTest.php new file mode 100644 index 0000000..0d31651 --- /dev/null +++ b/tests/ModbusMaster/ModbusExceptionTest.php @@ -0,0 +1,63 @@ +expectException(\Exception::class); + $this->expectExceptionMessage("Unknown socket protocol, should be 'TCP' or 'UDP'"); + + $modbus = new ModbusMaster('127.0.0.1', 'Mismatch'); + $modbus->readCoils(0, 256, 1); + } + + public function testPortClosedException() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('socket_connect() failed. Reason: ()No connection could be made because the target machine actively refused it.'); + + $modbus = new ModbusMasterTcp('127.0.0.1'); + $modbus->setSocketTimeout(0.2, 0.2); + $modbus->readCoils(0, 256, 1); + } + + public function testTimeoutException() + { + $this->expectException(\Exception::class); + + $mockResponse = '89130000000400010101'; // respond with 1 byte (00000001 bits set) [1] + static::executeWithMockServer($mockResponse, function ($port) { + $this->expectExceptionMessage("Watchdog time expired [ 0.5 sec ]!!! Connection to 127.0.0.1:{$port} is not established."); + + $modbus = new ModbusMaster('127.0.0.1', 'UDP'); + $modbus->port = $port; + $modbus->setTimeout(0.5); + $modbus->setSocketTimeout(0.2, 0.2); + + $modbus->readCoils(0, 256, 1); + }, 'UDP', 1); + } + + + public function testThrowIllegalDataValueException() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Modbus response error code: 3 (ILLEGAL DATA VALUE)'); + + $mockResponse = 'da8700000003008303'; // respond with 1 WORD (2 bytes) [0, 3] + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + //can not query more than 124 WORDs. Wago response is ILLEGAL DATA VALUE + $this->assertEquals([0, 3], $modbus->readMultipleRegisters(0, 256, 140)); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('0000000600030100008c', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/UdpFc1ReadCoilsTest.php b/tests/ModbusMaster/UdpFc1ReadCoilsTest.php new file mode 100644 index 0000000..78a6e34 --- /dev/null +++ b/tests/ModbusMaster/UdpFc1ReadCoilsTest.php @@ -0,0 +1,23 @@ +port = $port; + + usleep(50000); // no idea how to fix this. wait for server to "warm" up or modbus UDP socket will timeout. does not occur with TCP + $this->assertEquals([1], $modbus->readCoils(0, 256, 1)); + }, 'UDP'); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('00000006000101000001', $packetWithoutTransactionId); + } +} \ No newline at end of file diff --git a/tests/ModbusMaster/_test.bat b/tests/ModbusMaster/_test.bat deleted file mode 100644 index 8122004..0000000 --- a/tests/ModbusMaster/_test.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -call ../config.bat - -for %%f in (test.*.php) do %php% -q "%%f" > "output/%%f.html" - -cd output -for %%f in (*.html) do %diff% "%%f" ../ref/"%%f" -cd .. -pause - -@echo on \ No newline at end of file diff --git a/tests/ModbusMaster/ref/test.tcp.fc16fc3.php.html b/tests/ModbusMaster/ref/test.tcp.fc16fc3.php.html deleted file mode 100644 index 00dd046..0000000 --- a/tests/ModbusMaster/ref/test.tcp.fc16fc3.php.html +++ /dev/null @@ -1,72 +0,0 @@ -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 255 - [8] => 0 - [9] => 255 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 255 - [5] => 255 - [6] => 127 - [7] => 255 - [8] => 128 - [9] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 0 - [8] => 255 - [9] => 255 - [10] => 255 - [11] => 255 - [12] => 255 - [13] => 255 - [14] => 127 - [15] => 255 - [16] => 0 - [17] => 0 - [18] => 128 - [19] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 0 - [6] => 63 - [7] => 128 - [8] => 0 - [9] => 0 - [10] => 192 - [11] => 0 - [12] => 170 - [13] => 171 - [14] => 62 - [15] => 170 - [16] => 0 - [17] => 0 - [18] => 65 - [19] => 200 -) diff --git a/tests/ModbusMaster/ref/test.tcp.fc26.php.html b/tests/ModbusMaster/ref/test.tcp.fc26.php.html deleted file mode 100644 index 7991aa8..0000000 --- a/tests/ModbusMaster/ref/test.tcp.fc26.php.html +++ /dev/null @@ -1 +0,0 @@ -writeMultipleRegister (FC26): DONE \ No newline at end of file diff --git a/tests/ModbusMaster/ref/test.tcp.socket_protocol_mismatch.php.html b/tests/ModbusMaster/ref/test.tcp.socket_protocol_mismatch.php.html deleted file mode 100644 index b25cfe4..0000000 --- a/tests/ModbusMaster/ref/test.tcp.socket_protocol_mismatch.php.html +++ /dev/null @@ -1 +0,0 @@ -Caught exception: Unknown socket protocol, should be 'TCP' or 'UDP' diff --git a/tests/ModbusMaster/ref/test.udp.fc16fc3.php.html b/tests/ModbusMaster/ref/test.udp.fc16fc3.php.html deleted file mode 100644 index 00dd046..0000000 --- a/tests/ModbusMaster/ref/test.udp.fc16fc3.php.html +++ /dev/null @@ -1,72 +0,0 @@ -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 255 - [8] => 0 - [9] => 255 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 255 - [5] => 255 - [6] => 127 - [7] => 255 - [8] => 128 - [9] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 0 - [8] => 255 - [9] => 255 - [10] => 255 - [11] => 255 - [12] => 255 - [13] => 255 - [14] => 127 - [15] => 255 - [16] => 0 - [17] => 0 - [18] => 128 - [19] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 0 - [6] => 63 - [7] => 128 - [8] => 0 - [9] => 0 - [10] => 192 - [11] => 0 - [12] => 170 - [13] => 171 - [14] => 62 - [15] => 170 - [16] => 0 - [17] => 0 - [18] => 65 - [19] => 200 -) diff --git a/tests/ModbusMaster/ref/test.udp.fc26.php.html b/tests/ModbusMaster/ref/test.udp.fc26.php.html deleted file mode 100644 index 7991aa8..0000000 --- a/tests/ModbusMaster/ref/test.udp.fc26.php.html +++ /dev/null @@ -1 +0,0 @@ -writeMultipleRegister (FC26): DONE \ No newline at end of file diff --git a/tests/ModbusMaster/test.tcp.fc16fc3.php b/tests/ModbusMaster/test.tcp.fc16fc3.php deleted file mode 100644 index bd0b765..0000000 --- a/tests/ModbusMaster/test.tcp.fc16fc3.php +++ /dev/null @@ -1,44 +0,0 @@ -writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - INT -$data = array(0, 1, -1, pow(2,15)-1, -pow(2,15)); -$dataTypes = array("INT", "INT", "INT", "INT", "INT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - DINT -$data = array(0, 1, -1, pow(2,31)-1, -pow(2,31)); -$dataTypes = array("DINT", "DINT", "DINT", "DINT", "DINT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -// Data to be writen - REAL -$data = array(0, 1, -2, 1/3, 25); -$dataTypes = array("REAL", "REAL", "REAL", "REAL", "REAL"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -?> \ No newline at end of file diff --git a/tests/ModbusMaster/test.tcp.fc26.php b/tests/ModbusMaster/test.tcp.fc26.php deleted file mode 100644 index 2c76b7f..0000000 --- a/tests/ModbusMaster/test.tcp.fc26.php +++ /dev/null @@ -1,24 +0,0 @@ -readWriteRegisters(0, 12288, 6, 12288, $data, $dataTypes); - -if(!$recData) { - // Print error information if any - echo "
Error:
" . $modbus->errstr . "
"; - // - exit(); -} - -// Print status information -echo "writeMultipleRegister (FC26): DONE"; -?> \ No newline at end of file diff --git a/tests/ModbusMaster/test.tcp.socket_protocol_mismatch.php b/tests/ModbusMaster/test.tcp.socket_protocol_mismatch.php deleted file mode 100644 index aa5f691..0000000 --- a/tests/ModbusMaster/test.tcp.socket_protocol_mismatch.php +++ /dev/null @@ -1,22 +0,0 @@ -readWriteRegisters(0, 12288, 6, 12288, $data, $dataTypes); -} catch (Exception $e) { - echo 'Caught exception: ', $e->getMessage(), "\n"; - exit(); -} -// Should through an Exception - -// Print status information -echo "Should never reach this line!"; \ No newline at end of file diff --git a/tests/ModbusMaster/test.udp.fc16fc3.php b/tests/ModbusMaster/test.udp.fc16fc3.php deleted file mode 100644 index 20dee62..0000000 --- a/tests/ModbusMaster/test.udp.fc16fc3.php +++ /dev/null @@ -1,44 +0,0 @@ -writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - INT -$data = array(0, 1, -1, pow(2,15)-1, -pow(2,15)); -$dataTypes = array("INT", "INT", "INT", "INT", "INT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - DINT -$data = array(0, 1, -1, pow(2,31)-1, -pow(2,31)); -$dataTypes = array("DINT", "DINT", "DINT", "DINT", "DINT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -// Data to be writen - REAL -$data = array(0, 1, -2, 1/3, 25); -$dataTypes = array("REAL", "REAL", "REAL", "REAL", "REAL"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -?> \ No newline at end of file diff --git a/tests/ModbusMaster/test.udp.fc26.php b/tests/ModbusMaster/test.udp.fc26.php deleted file mode 100644 index 02352ac..0000000 --- a/tests/ModbusMaster/test.udp.fc26.php +++ /dev/null @@ -1,24 +0,0 @@ -readWriteRegisters(0, 12288, 6, 12288, $data, $dataTypes); - -if(!$recData) { - // Print error information if any - echo "
Error:
" . $modbus->errstr . "
"; - // - exit(); -} - -// Print status information -echo "writeMultipleRegister (FC26): DONE"; -?> \ No newline at end of file diff --git a/tests/ModbusMasterTcp/_test.bat b/tests/ModbusMasterTcp/_test.bat deleted file mode 100644 index 8122004..0000000 --- a/tests/ModbusMasterTcp/_test.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -call ../config.bat - -for %%f in (test.*.php) do %php% -q "%%f" > "output/%%f.html" - -cd output -for %%f in (*.html) do %diff% "%%f" ../ref/"%%f" -cd .. -pause - -@echo on \ No newline at end of file diff --git a/tests/ModbusMasterTcp/ref/test.fc16fc3.php.html b/tests/ModbusMasterTcp/ref/test.fc16fc3.php.html deleted file mode 100644 index 00dd046..0000000 --- a/tests/ModbusMasterTcp/ref/test.fc16fc3.php.html +++ /dev/null @@ -1,72 +0,0 @@ -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 255 - [8] => 0 - [9] => 255 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 255 - [5] => 255 - [6] => 127 - [7] => 255 - [8] => 128 - [9] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 0 - [8] => 255 - [9] => 255 - [10] => 255 - [11] => 255 - [12] => 255 - [13] => 255 - [14] => 127 - [15] => 255 - [16] => 0 - [17] => 0 - [18] => 128 - [19] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 0 - [6] => 63 - [7] => 128 - [8] => 0 - [9] => 0 - [10] => 192 - [11] => 0 - [12] => 170 - [13] => 171 - [14] => 62 - [15] => 170 - [16] => 0 - [17] => 0 - [18] => 65 - [19] => 200 -) diff --git a/tests/ModbusMasterTcp/ref/test.fc26.php.html b/tests/ModbusMasterTcp/ref/test.fc26.php.html deleted file mode 100644 index 7991aa8..0000000 --- a/tests/ModbusMasterTcp/ref/test.fc26.php.html +++ /dev/null @@ -1 +0,0 @@ -writeMultipleRegister (FC26): DONE \ No newline at end of file diff --git a/tests/ModbusMasterTcp/test.fc16fc3.php b/tests/ModbusMasterTcp/test.fc16fc3.php deleted file mode 100644 index 2fc3ee2..0000000 --- a/tests/ModbusMasterTcp/test.fc16fc3.php +++ /dev/null @@ -1,44 +0,0 @@ -writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - INT -$data = array(0, 1, -1, pow(2,15)-1, -pow(2,15)); -$dataTypes = array("INT", "INT", "INT", "INT", "INT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - DINT -$data = array(0, 1, -1, pow(2,31)-1, -pow(2,31)); -$dataTypes = array("DINT", "DINT", "DINT", "DINT", "DINT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -// Data to be writen - REAL -$data = array(0, 1, -2, 1/3, 25); -$dataTypes = array("REAL", "REAL", "REAL", "REAL", "REAL"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -?> \ No newline at end of file diff --git a/tests/ModbusMasterTcp/test.fc26.php b/tests/ModbusMasterTcp/test.fc26.php deleted file mode 100644 index 076ab22..0000000 --- a/tests/ModbusMasterTcp/test.fc26.php +++ /dev/null @@ -1,24 +0,0 @@ -readWriteRegisters(0, 12288, 6, 12288, $data, $dataTypes); - -if(!$recData) { - // Print error information if any - echo "
Error:
" . $modbus->errstr . "
"; - // - exit(); -} - -// Print status information -echo "writeMultipleRegister (FC26): DONE"; -?> \ No newline at end of file diff --git a/tests/ModbusMasterUdp/_test.bat b/tests/ModbusMasterUdp/_test.bat deleted file mode 100644 index 8122004..0000000 --- a/tests/ModbusMasterUdp/_test.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -call ../config.bat - -for %%f in (test.*.php) do %php% -q "%%f" > "output/%%f.html" - -cd output -for %%f in (*.html) do %diff% "%%f" ../ref/"%%f" -cd .. -pause - -@echo on \ No newline at end of file diff --git a/tests/ModbusMasterUdp/ref/test.fc15fc1.php.html b/tests/ModbusMasterUdp/ref/test.fc15fc1.php.html deleted file mode 100644 index 55a3de0..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc15fc1.php.html +++ /dev/null @@ -1,66 +0,0 @@ -array(32) { - [0]=> - bool(true) - [1]=> - bool(false) - [2]=> - bool(true) - [3]=> - bool(true) - [4]=> - bool(false) - [5]=> - bool(true) - [6]=> - bool(true) - [7]=> - bool(true) - [8]=> - bool(true) - [9]=> - bool(true) - [10]=> - bool(true) - [11]=> - bool(true) - [12]=> - bool(false) - [13]=> - bool(false) - [14]=> - bool(false) - [15]=> - bool(false) - [16]=> - bool(false) - [17]=> - bool(false) - [18]=> - bool(false) - [19]=> - bool(false) - [20]=> - bool(true) - [21]=> - bool(true) - [22]=> - bool(true) - [23]=> - bool(true) - [24]=> - bool(true) - [25]=> - bool(true) - [26]=> - bool(true) - [27]=> - bool(true) - [28]=> - bool(true) - [29]=> - bool(true) - [30]=> - bool(true) - [31]=> - bool(true) -} diff --git a/tests/ModbusMasterUdp/ref/test.fc16fc3.php.html b/tests/ModbusMasterUdp/ref/test.fc16fc3.php.html deleted file mode 100644 index 00dd046..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc16fc3.php.html +++ /dev/null @@ -1,72 +0,0 @@ -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 255 - [8] => 0 - [9] => 255 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 255 - [5] => 255 - [6] => 127 - [7] => 255 - [8] => 128 - [9] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 0 - [8] => 255 - [9] => 255 - [10] => 255 - [11] => 255 - [12] => 255 - [13] => 255 - [14] => 127 - [15] => 255 - [16] => 0 - [17] => 0 - [18] => 128 - [19] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 0 - [6] => 63 - [7] => 128 - [8] => 0 - [9] => 0 - [10] => 192 - [11] => 0 - [12] => 170 - [13] => 171 - [14] => 62 - [15] => 170 - [16] => 0 - [17] => 0 - [18] => 65 - [19] => 200 -) diff --git a/tests/ModbusMasterUdp/ref/test.fc16fc3bind.php.html b/tests/ModbusMasterUdp/ref/test.fc16fc3bind.php.html deleted file mode 100644 index 00dd046..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc16fc3bind.php.html +++ /dev/null @@ -1,72 +0,0 @@ -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 255 - [8] => 0 - [9] => 255 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 1 - [4] => 255 - [5] => 255 - [6] => 127 - [7] => 255 - [8] => 128 - [9] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 1 - [6] => 0 - [7] => 0 - [8] => 255 - [9] => 255 - [10] => 255 - [11] => 255 - [12] => 255 - [13] => 255 - [14] => 127 - [15] => 255 - [16] => 0 - [17] => 0 - [18] => 128 - [19] => 0 -) -Array -( - [0] => 0 - [1] => 0 - [2] => 0 - [3] => 0 - [4] => 0 - [5] => 0 - [6] => 63 - [7] => 128 - [8] => 0 - [9] => 0 - [10] => 192 - [11] => 0 - [12] => 170 - [13] => 171 - [14] => 62 - [15] => 170 - [16] => 0 - [17] => 0 - [18] => 65 - [19] => 200 -) diff --git a/tests/ModbusMasterUdp/ref/test.fc2.php.html b/tests/ModbusMasterUdp/ref/test.fc2.php.html deleted file mode 100644 index 5baae72..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc2.php.html +++ /dev/null @@ -1,7 +0,0 @@ -Test should pass when %IX0.0==FALSE and %IX0.1==TRUE -array(2) { - [0]=> - bool(false) - [1]=> - bool(true) -} diff --git a/tests/ModbusMasterUdp/ref/test.fc26.php.html b/tests/ModbusMasterUdp/ref/test.fc26.php.html deleted file mode 100644 index 7991aa8..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc26.php.html +++ /dev/null @@ -1 +0,0 @@ -writeMultipleRegister (FC26): DONE \ No newline at end of file diff --git a/tests/ModbusMasterUdp/ref/test.fc26bind.php.html b/tests/ModbusMasterUdp/ref/test.fc26bind.php.html deleted file mode 100644 index 7991aa8..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc26bind.php.html +++ /dev/null @@ -1 +0,0 @@ -writeMultipleRegister (FC26): DONE \ No newline at end of file diff --git a/tests/ModbusMasterUdp/ref/test.fc4.php.html b/tests/ModbusMasterUdp/ref/test.fc4.php.html deleted file mode 100644 index 4cbe5ae..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc4.php.html +++ /dev/null @@ -1,11 +0,0 @@ -Test should pass when %IX0.0==FALSE and %IX0.1==TRUE -array(4) { - [0]=> - int(0) - [1]=> - int(2) - [2]=> - int(0) - [3]=> - int(0) -} diff --git a/tests/ModbusMasterUdp/ref/test.fc5.php.html b/tests/ModbusMasterUdp/ref/test.fc5.php.html deleted file mode 100644 index a6a24be..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc5.php.html +++ /dev/null @@ -1,5 +0,0 @@ -Array -( - [0] => 0 - [1] => 5 -) diff --git a/tests/ModbusMasterUdp/ref/test.fc6fc3.php.html b/tests/ModbusMasterUdp/ref/test.fc6fc3.php.html deleted file mode 100644 index 6102dcd..0000000 --- a/tests/ModbusMasterUdp/ref/test.fc6fc3.php.html +++ /dev/null @@ -1,5 +0,0 @@ -Array -( - [0] => 207 - [1] => 199 -) diff --git a/tests/ModbusMasterUdp/test.fc15fc1.php b/tests/ModbusMasterUdp/test.fc15fc1.php deleted file mode 100644 index 5a7d8c4..0000000 --- a/tests/ModbusMasterUdp/test.fc15fc1.php +++ /dev/null @@ -1,22 +0,0 @@ -writeMultipleCoils(0, 12288, $data); -//echo $modbus->status; -//$modbus->status = ""; -//echo "\n\n"; -// Read data - FC 1 -$recData = $modbus->readCoils(0, 12288, 32); -//echo $modbus->status; -//echo "\n\n"; -var_dump($recData); \ No newline at end of file diff --git a/tests/ModbusMasterUdp/test.fc16fc3.php b/tests/ModbusMasterUdp/test.fc16fc3.php deleted file mode 100644 index dff33c7..0000000 --- a/tests/ModbusMasterUdp/test.fc16fc3.php +++ /dev/null @@ -1,44 +0,0 @@ -writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - INT -$data = array(0, 1, -1, pow(2,15)-1, -pow(2,15)); -$dataTypes = array("INT", "INT", "INT", "INT", "INT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - DINT -$data = array(0, 1, -1, pow(2,31)-1, -pow(2,31)); -$dataTypes = array("DINT", "DINT", "DINT", "DINT", "DINT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -// Data to be writen - REAL -$data = array(0, 1, -2, 1/3, 25); -$dataTypes = array("REAL", "REAL", "REAL", "REAL", "REAL"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -?> \ No newline at end of file diff --git a/tests/ModbusMasterUdp/test.fc16fc3bind.php b/tests/ModbusMasterUdp/test.fc16fc3bind.php deleted file mode 100644 index 5d3bed9..0000000 --- a/tests/ModbusMasterUdp/test.fc16fc3bind.php +++ /dev/null @@ -1,45 +0,0 @@ -client = $test_bind_client_ip; - -// Data to be writen - BYTE -$data = array(0, 1, 1, pow(2,8)-1, pow(2,8)-1); -$dataTypes = array("BYTE", "BYTE", "BYTE", "BYTE", "BYTE"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - INT -$data = array(0, 1, -1, pow(2,15)-1, -pow(2,15)); -$dataTypes = array("INT", "INT", "INT", "INT", "INT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 5); -print_r($recData); - -// Data to be writen - DINT -$data = array(0, 1, -1, pow(2,31)-1, -pow(2,31)); -$dataTypes = array("DINT", "DINT", "DINT", "DINT", "DINT"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -// Data to be writen - REAL -$data = array(0, 1, -2, 1/3, 25); -$dataTypes = array("REAL", "REAL", "REAL", "REAL", "REAL"); -// Write data - FC 16 -$modbus->writeMultipleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 10); -print_r($recData); - -?> \ No newline at end of file diff --git a/tests/ModbusMasterUdp/test.fc2.php b/tests/ModbusMasterUdp/test.fc2.php deleted file mode 100644 index 3c73077..0000000 --- a/tests/ModbusMasterUdp/test.fc2.php +++ /dev/null @@ -1,14 +0,0 @@ -readInputDiscretes(0, 0, 2); - -var_dump($recData); \ No newline at end of file diff --git a/tests/ModbusMasterUdp/test.fc26.php b/tests/ModbusMasterUdp/test.fc26.php deleted file mode 100644 index 1a889de..0000000 --- a/tests/ModbusMasterUdp/test.fc26.php +++ /dev/null @@ -1,24 +0,0 @@ -readWriteRegisters(0, 12288, 6, 12288, $data, $dataTypes); - -if(!$recData) { - // Print error information if any - echo "
Error:
" . $modbus->errstr . "
"; - // - exit(); -} - -// Print status information -echo "writeMultipleRegister (FC26): DONE"; -?> \ No newline at end of file diff --git a/tests/ModbusMasterUdp/test.fc26bind.php b/tests/ModbusMasterUdp/test.fc26bind.php deleted file mode 100644 index bf24f9e..0000000 --- a/tests/ModbusMasterUdp/test.fc26bind.php +++ /dev/null @@ -1,25 +0,0 @@ -client = $test_bind_client_ip; - -// Data to be writen -$data = array(1000, 2000, 1.250, 1.250); -$dataTypes = array("REAL", "REAL", "REAL", "REAL"); - -// FC23 -$recData = $modbus->readWriteRegisters(0, 12288, 6, 12288, $data, $dataTypes); - -if(!$recData) { - // Print error information if any - echo "
Error:
" . $modbus->errstr . "
"; - // - exit(); -} - -// Print status information -echo "writeMultipleRegister (FC26): DONE"; -?> \ No newline at end of file diff --git a/tests/ModbusMasterUdp/test.fc4.php b/tests/ModbusMasterUdp/test.fc4.php deleted file mode 100644 index dee4929..0000000 --- a/tests/ModbusMasterUdp/test.fc4.php +++ /dev/null @@ -1,14 +0,0 @@ -readMultipleInputRegisters(0, 0, 2); - -var_dump($recData); \ No newline at end of file diff --git a/tests/ModbusMasterUdp/test.fc5.php b/tests/ModbusMasterUdp/test.fc5.php deleted file mode 100644 index 95e6f92..0000000 --- a/tests/ModbusMasterUdp/test.fc5.php +++ /dev/null @@ -1,23 +0,0 @@ -writeSingleRegister(0, 12288, array(0), array('WORD')); - -// Write single coil - FC5 -$modbus->writeSingleCoil(0, 12288, $data_true); -$modbus->writeSingleCoil(0, 12289, $data_false); -$modbus->writeSingleCoil(0, 12290, $data_true); -$modbus->writeSingleCoil(0, 12291, $data_false); - -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 1); -print_r($recData); diff --git a/tests/ModbusMasterUdp/test.fc6fc3.php b/tests/ModbusMasterUdp/test.fc6fc3.php deleted file mode 100644 index 03c39f5..0000000 --- a/tests/ModbusMasterUdp/test.fc6fc3.php +++ /dev/null @@ -1,15 +0,0 @@ -writeSingleRegister(0, 12288, $data, $dataTypes); -// Read data - FC3 -$recData = $modbus->readMultipleRegisters(0, 12288, 1); -print_r($recData); diff --git a/tests/PhpType/Bytes2MixedTest.php b/tests/PhpType/Bytes2MixedTest.php new file mode 100644 index 0000000..fcaa25b --- /dev/null +++ b/tests/PhpType/Bytes2MixedTest.php @@ -0,0 +1,45 @@ + 125, // 32098 (DINT) + "1" => 98, + "2" => 0, + "3" => 0, + "4" => 0, // 0 (DINT) + "5" => 0, + "6" => 0, + "7" => 0, + "8" => 0, // 0 (DINT) + "9" => 0, + "10" => 0, + "11" => 0, + "12" => 255, // -1 (DINT) + "13" => 255, + "14" => 255, + "15" => 255, + "16" => 158, // -25000 (INT) + "17" => 88, + "18" => 97, // 25000 (INT) + "19" => 168 + ]; + + public function testUnsignedInt() + { + $this->assertEquals(32098, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + } + + public function testSignedInt() + { + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 4, 4))); + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 8, 4))); + $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 12, 4))); + $this->assertEquals(-25000, PhpType::bytes2signedInt(array_slice(self::DATA, 16, 2))); + $this->assertEquals(25000, PhpType::bytes2signedInt(array_slice(self::DATA, 18, 2))); + } +} diff --git a/tests/PhpType/Bytes2RealTest.php b/tests/PhpType/Bytes2RealTest.php new file mode 100644 index 0000000..2fd7b80 --- /dev/null +++ b/tests/PhpType/Bytes2RealTest.php @@ -0,0 +1,31 @@ + 0, + 1 => 0, + 2 => 68, + 3 => 122, + 4 => 0, + 5 => 0, + 6 => 68, + 7 => 250, + 8 => 0, + 9 => 0, + 10 => 63, + 11 => 160, + ]; + + public function testByte2Real() + { + $this->assertEquals(1000, PhpType::bytes2float(array_slice(self::DATA, 0, 4))); + $this->assertEquals(2000, PhpType::bytes2float(array_slice(self::DATA, 4, 4))); + $this->assertEquals(1.25, PhpType::bytes2float(array_slice(self::DATA, 8, 4))); + } +} diff --git a/tests/PhpType/Bytes2SignedIntTest.php b/tests/PhpType/Bytes2SignedIntTest.php new file mode 100644 index 0000000..362a30d --- /dev/null +++ b/tests/PhpType/Bytes2SignedIntTest.php @@ -0,0 +1,42 @@ +assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 0, 2))); + $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 0, 4))); + + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 4, 4))); + $this->assertEquals(1, PhpType::bytes2signedInt(array_slice(self::DATA, 8, 4))); + $this->assertEquals(-2147483648, PhpType::bytes2signedInt(array_slice(self::DATA, 12, 4))); + $this->assertEquals(2147483647, PhpType::bytes2signedInt(array_slice(self::DATA, 16, 4))); + } +} diff --git a/tests/PhpType/Bytes2StringTest.php b/tests/PhpType/Bytes2StringTest.php new file mode 100644 index 0000000..8b5d788 --- /dev/null +++ b/tests/PhpType/Bytes2StringTest.php @@ -0,0 +1,32 @@ +assertEquals('eHll oowlr!da', PhpType::bytes2string(self::DATA)); + $this->assertEquals('Hello world!', PhpType::bytes2string(self::DATA, true)); + } +} diff --git a/tests/PhpType/Bytes2UnSignedIntTest.php b/tests/PhpType/Bytes2UnSignedIntTest.php new file mode 100644 index 0000000..2c424fc --- /dev/null +++ b/tests/PhpType/Bytes2UnSignedIntTest.php @@ -0,0 +1,42 @@ +assertEquals(65535, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2))); + $this->assertEquals(4294967295, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + + $this->assertEquals(0, PhpType::bytes2unsignedInt(array_slice(self::DATA, 4, 4))); + $this->assertEquals(1, PhpType::bytes2unsignedInt(array_slice(self::DATA, 8, 4))); + $this->assertEquals(2147483648, PhpType::bytes2unsignedInt(array_slice(self::DATA, 12, 4))); + $this->assertEquals(2147483647, PhpType::bytes2unsignedInt(array_slice(self::DATA, 16, 4))); + } +} diff --git a/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php b/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php new file mode 100644 index 0000000..0565a13 --- /dev/null +++ b/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php @@ -0,0 +1,27 @@ + 100, // 32098 (DINT) + "1" => "e", + "2" => 0, + "3" => 0 + ]; + + public function testExceptionWhenSize2ContainsString() + { + $this->expectException(\Exception::class); + PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2)); + } + + public function testExceptionWhenSize4ContainsString() + { + $this->expectException(\Exception::class); + PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4)); + } +} diff --git a/tests/PhpType/PhpTypeArraySizeExceptionsTest.php b/tests/PhpType/PhpTypeArraySizeExceptionsTest.php new file mode 100644 index 0000000..6003ad4 --- /dev/null +++ b/tests/PhpType/PhpTypeArraySizeExceptionsTest.php @@ -0,0 +1,46 @@ + 100, // 32098 (DINT) + "1" => 2, + "2" => 0, + "3" => 0, + "4" => 100, // 32098 (DINT) + "5" => 2 + ]; + + public function testExceptionWhenSizeShort() + { + $this->expectException(\Exception::class); + PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 1)); + } + + public function testExceptionWhenSizeShort3() + { + $this->expectException(\Exception::class); + PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 3)); + } + + public function testExceptionWhenSizeLong() + { + $this->expectException(\Exception::class); + PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 5)); + } + + public function testNoExceptionWhenSize2() + { + $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2))); + } + + public function testNoExceptionWhenSize4() + { + $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + } + +} \ No newline at end of file diff --git a/tests/PhpType/_test.bat b/tests/PhpType/_test.bat deleted file mode 100644 index 8122004..0000000 --- a/tests/PhpType/_test.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -call ../config.bat - -for %%f in (test.*.php) do %php% -q "%%f" > "output/%%f.html" - -cd output -for %%f in (*.html) do %diff% "%%f" ../ref/"%%f" -cd .. -pause - -@echo on \ No newline at end of file diff --git a/tests/PhpType/ref/test.bytes2mixed.php.html b/tests/PhpType/ref/test.bytes2mixed.php.html deleted file mode 100644 index 952bc9d..0000000 --- a/tests/PhpType/ref/test.bytes2mixed.php.html +++ /dev/null @@ -1 +0,0 @@ -32098
0
0
-1
-25000
25000
\ No newline at end of file diff --git a/tests/PhpType/ref/test.bytes2real.php.html b/tests/PhpType/ref/test.bytes2real.php.html deleted file mode 100644 index 7d44edb..0000000 --- a/tests/PhpType/ref/test.bytes2real.php.html +++ /dev/null @@ -1 +0,0 @@ -1000
2000
1.25
\ No newline at end of file diff --git a/tests/PhpType/ref/test.bytes2signedint.php.html b/tests/PhpType/ref/test.bytes2signedint.php.html deleted file mode 100644 index a2b10ea..0000000 --- a/tests/PhpType/ref/test.bytes2signedint.php.html +++ /dev/null @@ -1 +0,0 @@ --1
0
1
-2147483648
2147483647
diff --git a/tests/PhpType/ref/test.bytes2string.php.html b/tests/PhpType/ref/test.bytes2string.php.html deleted file mode 100644 index 31bbf09..0000000 --- a/tests/PhpType/ref/test.bytes2string.php.html +++ /dev/null @@ -1 +0,0 @@ -eHll oowlr!da
Hello world!
\ No newline at end of file diff --git a/tests/PhpType/ref/test.bytes2unsignedint.php.html b/tests/PhpType/ref/test.bytes2unsignedint.php.html deleted file mode 100644 index 29be8ee..0000000 --- a/tests/PhpType/ref/test.bytes2unsignedint.php.html +++ /dev/null @@ -1,6 +0,0 @@ -float(4294967295) -
int(0) -
int(1) -
float(2147483648) -
int(2147483647) -
diff --git a/tests/PhpType/ref/test.strangearray.size.php.html b/tests/PhpType/ref/test.strangearray.size.php.html deleted file mode 100644 index d0e619d..0000000 --- a/tests/PhpType/ref/test.strangearray.size.php.html +++ /dev/null @@ -1 +0,0 @@ -Exception 'Data are not in array 2 or 4 bytes'
25602
Exception 'Data are not in array 2 or 4 bytes'
25602
Exception 'Data are not in array 2 or 4 bytes'
\ No newline at end of file diff --git a/tests/PhpType/ref/test.strangearray.textarray.php.html b/tests/PhpType/ref/test.strangearray.textarray.php.html deleted file mode 100644 index 734d099..0000000 --- a/tests/PhpType/ref/test.strangearray.textarray.php.html +++ /dev/null @@ -1 +0,0 @@ -Exception 'Data are not numeric' \ No newline at end of file diff --git a/tests/PhpType/test.bytes2mixed.php b/tests/PhpType/test.bytes2mixed.php deleted file mode 100644 index 6b1b391..0000000 --- a/tests/PhpType/test.bytes2mixed.php +++ /dev/null @@ -1,37 +0,0 @@ - 125, // 32098 (DINT) - "1" => 98, - "2" => 0, - "3" => 0, - "4" => 0, // 0 (DINT) - "5" => 0, - "6" => 0, - "7" => 0, - "8" => 0, // 0 (DINT) - "9" => 0, - "10" => 0, - "11" => 0, - "12" => 255, // -1 (DINT) - "13" => 255, - "14" => 255, - "15" => 255, - "16" => 158, // -25000 (INT) - "17" => 88, - "18" => 97, // 25000 (INT) - "19" => 168 -); - -// Print mixed values -echo PhpType::bytes2unsignedInt(array_slice($data, 0, 4)) . "
"; -echo PhpType::bytes2signedInt(array_slice($data, 4, 4)) . "
"; -echo PhpType::bytes2signedInt(array_slice($data, 8, 4)) . "
"; -echo PhpType::bytes2signedInt(array_slice($data, 12, 4)) . "
"; -echo PhpType::bytes2signedInt(array_slice($data, 16, 2)) . "
"; -echo PhpType::bytes2signedInt(array_slice($data, 18, 2)) . "
"; - -?> \ No newline at end of file diff --git a/tests/PhpType/test.bytes2real.php b/tests/PhpType/test.bytes2real.php deleted file mode 100644 index b6acd56..0000000 --- a/tests/PhpType/test.bytes2real.php +++ /dev/null @@ -1,27 +0,0 @@ - 0, - 1 => 0, - 2 => 68, - 3 => 122, - 4 => 0, - 5 => 0, - 6 => 68, - 7 => 250, - 8 => 0, - 9 => 0, - 10 => 63, - 11 => 160, -); - -$dword = array_chunk($data, 4); - -// Print float interpretation of the real value -echo PhpType::bytes2float($dword[0]) . "
"; -echo PhpType::bytes2float($dword[1]) . "
"; -echo PhpType::bytes2float($dword[2]) . "
"; -?> \ No newline at end of file diff --git a/tests/PhpType/test.bytes2signedint.php b/tests/PhpType/test.bytes2signedint.php deleted file mode 100644 index 567299c..0000000 --- a/tests/PhpType/test.bytes2signedint.php +++ /dev/null @@ -1,35 +0,0 @@ -"; -} -?> diff --git a/tests/PhpType/test.bytes2string.php b/tests/PhpType/test.bytes2string.php deleted file mode 100644 index 547502b..0000000 --- a/tests/PhpType/test.bytes2string.php +++ /dev/null @@ -1,28 +0,0 @@ -"; -echo PhpType::bytes2string($data, true) . "
"; - -?> diff --git a/tests/PhpType/test.bytes2unsignedint.php b/tests/PhpType/test.bytes2unsignedint.php deleted file mode 100644 index 9aa6f37..0000000 --- a/tests/PhpType/test.bytes2unsignedint.php +++ /dev/null @@ -1,36 +0,0 @@ -"; -} -?> diff --git a/tests/PhpType/test.strangearray.size.php b/tests/PhpType/test.strangearray.size.php deleted file mode 100644 index ba817e2..0000000 --- a/tests/PhpType/test.strangearray.size.php +++ /dev/null @@ -1,44 +0,0 @@ - 100, // 32098 (DINT) - "1" => 2, - "2" => 0, - "3" => 0, - "4" => 100, // 32098 (DINT) - "5" => 2 -); - -// Print mixed values -try { - echo PhpType::bytes2unsignedInt(array_slice($data, 0, 1)) . "
"; -} catch(Exception $e) { - echo "Exception 'Data are not in array 2 or 4 bytes'". "
"; -} -try { - echo PhpType::bytes2unsignedInt(array_slice($data, 0, 2)). "
"; -} catch(Exception $e) { - echo "Exception 'Data are not in array 2 or 4 bytes'". "
"; -} -try { - echo PhpType::bytes2unsignedInt(array_slice($data, 0, 3)). "
"; -} catch(Exception $e) { - echo "Exception 'Data are not in array 2 or 4 bytes'". "
"; -} -try { - echo PhpType::bytes2unsignedInt(array_slice($data, 0, 4)). "
"; -} catch(Exception $e) { - echo "Exception 'Data are not in array 2 or 4 bytes'". "
"; -} -try { - echo PhpType::bytes2unsignedInt(array_slice($data, 0, 5)). "
"; -} catch(Exception $e) { - echo "Exception 'Data are not in array 2 or 4 bytes'". "
"; -} -?> \ No newline at end of file diff --git a/tests/PhpType/test.strangearray.textarray.php b/tests/PhpType/test.strangearray.textarray.php deleted file mode 100644 index 10306a0..0000000 --- a/tests/PhpType/test.strangearray.textarray.php +++ /dev/null @@ -1,22 +0,0 @@ - 100, // 32098 (DINT) - "1" => "e", - "2" => 0, - "3" => 0 -); - -// Print mixed values -try { - echo PhpType::bytes2unsignedInt(array_slice($data, 0, 4)); -} catch(Exception $e) { - echo "Exception 'Data are not numeric'"; -} -?> diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 1dbebf5..0000000 --- a/tests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Probably broken and dubious. - -Left from the original repo. diff --git a/tests/config.bat b/tests/config.bat deleted file mode 100644 index ef49dfc..0000000 --- a/tests/config.bat +++ /dev/null @@ -1,5 +0,0 @@ -set php=c:\Program_Files\xampp\php\php.exe -rem set php=C:\PHP\versions\php-5.2.6-Win32\php.exe -d auto_prepend_file=C:\PHP\locale.php -set diff="diff.exe" -rem set testUri=http://localHost/nette/_trunk/tests -rem set wget=wget.exe diff --git a/tests/config.php b/tests/config.php deleted file mode 100644 index 9c71b4a..0000000 --- a/tests/config.php +++ /dev/null @@ -1,3 +0,0 @@ - Date: Sat, 26 Nov 2016 22:25:06 +0200 Subject: [PATCH 02/17] reformat code and run PHP Code Sniffer beautifier --- src/IecType.php | 213 ++- src/ModbusMaster.php | 2874 ++++++++++++++++++++------------------- src/ModbusMasterTcp.php | 50 +- src/ModbusMasterUdp.php | 50 +- src/PhpType.php | 452 +++--- 5 files changed, 1835 insertions(+), 1804 deletions(-) diff --git a/src/IecType.php b/src/IecType.php index ff5da87..c7ff067 100644 --- a/src/IecType.php +++ b/src/IecType.php @@ -22,120 +22,117 @@ namespace PHPModbus; * The class includes set of IEC-1131 data type functions that converts a PHP * data types to a IEC data type. * - * @author Jan Krakora - * @copyright Copyright (c) 2004, 2010 Jan Krakora - * @package Phpmodbus + * @author Jan Krakora + * @copyright Copyright (c) 2004, 2010 Jan Krakora + * @package Phpmodbus */ class IecType { - /** - * iecBYTE - * - * Converts a value to IEC-1131 BYTE data type - * - * @param int $value from 0 to 255 - * @return int IEC BYTE data type - * - */ - public static function iecBYTE($value) - { - return chr($value & 0xFF); - } + /** + * iecINT + * + * Converts a value to IEC-1131 INT data type + * + * @param int $value to be converted + * @return int IEC-1131 INT data type + */ + public static function iecINT($value) + { + return self::iecBYTE(($value >> 8) & 0x00FF) . + self::iecBYTE($value & 0x00FF); + } - /** - * iecINT - * - * Converts a value to IEC-1131 INT data type - * - * @param int $value to be converted - * @return int IEC-1131 INT data type - * - */ - public static function iecINT($value) - { - return self::iecBYTE(($value >> 8) & 0x00FF) . - self::iecBYTE(($value & 0x00FF)); - } + /** + * iecBYTE + * + * Converts a value to IEC-1131 BYTE data type + * + * @param int $value from 0 to 255 + * @return int IEC BYTE data type + */ + public static function iecBYTE($value) + { + return chr($value & 0xFF); + } - /** - * iecDINT - * - * Converts a value to IEC-1131 DINT data type - * - * @param int $value to be converted - * @param int $bigEndian defines endian codding (little endian == 0, big endian == 1) - * @return int IEC-1131 INT data type - * - */ - public static function iecDINT($value, $bigEndian = 0) - { - // result with right endianness - return self::endianness($value, $bigEndian); - } + /** + * iecDINT + * + * Converts a value to IEC-1131 DINT data type + * + * @param int $value to be converted + * @param int $bigEndian defines endian codding (little endian == 0, big endian == 1) + * @return int IEC-1131 INT data type + */ + public static function iecDINT($value, $bigEndian = 0) + { + // result with right endianness + return self::endianness($value, $bigEndian); + } - /** - * iecREAL - * - * Converts a value to IEC-1131 REAL data type. The function uses function @use float2iecReal. - * - * @param int $value to be converted - * @param bool $bigEndian defines endian codding (little endian == 0, big endian == 1) - * @return int IEC-1131 REAL data type - */ - public static function iecREAL($value, $bigEndian = 0) - { - // iecREAL representation - $real = self::float2iecReal($value); - // result with right endianness - return self::endianness($real, $bigEndian); - } + /** + * endianness + * + * Make endianess as required. + * For more see http://en.wikipedia.org/wiki/Endianness + * + * @param int $value + * @param bool $bigEndian + * @return int + */ + private static function endianness($value, $bigEndian = 0) + { + if ($bigEndian == 0) { + return + self::iecBYTE(($value >> 8) & 0x000000FF) . + self::iecBYTE($value & 0x000000FF) . + self::iecBYTE(($value >> 24) & 0x000000FF) . + self::iecBYTE(($value >> 16) & 0x000000FF); + } else { + return + self::iecBYTE(($value >> 24) & 0x000000FF) . + self::iecBYTE(($value >> 16) & 0x000000FF) . + self::iecBYTE(($value >> 8) & 0x000000FF) . + self::iecBYTE($value & 0x000000FF); + } + } - /** - * float2iecReal - * - * This function converts float value to IEC-1131 REAL single precision form. - * - * For more see [{@link http://en.wikipedia.org/wiki/Single_precision Single precision on Wiki}] or - * [{@link http://de.php.net/manual/en/function.base-convert.php PHP base_convert function commentary}, Todd Stokes - * @ Georgia Tech 21-Nov-2007] or - * [{@link http://www.php.net/manual/en/function.pack.php PHP pack/unpack functionality}] - * - * @param float $value to be converted - * @return int IEC REAL data type - */ - private static function float2iecReal($value) - { - // get float binary string - $float = pack("f", $value); - // set 32-bit unsigned integer of the float - $w = unpack("L", $float); - return $w[1]; - } + /** + * iecREAL + * + * Converts a value to IEC-1131 REAL data type. The function uses function @use float2iecReal. + * + * @param int $value to be converted + * @param bool $bigEndian defines endian codding (little endian == 0, big endian == 1) + * @return int IEC-1131 REAL data type + */ + public static function iecREAL($value, $bigEndian = 0) + { + // iecREAL representation + $real = self::float2iecReal($value); + // result with right endianness + return self::endianness($real, $bigEndian); + } - /** - * endianness - * - * Make endianess as required. - * For more see http://en.wikipedia.org/wiki/Endianness - * - * @param int $value - * @param bool $bigEndian - * @return int - */ - private static function endianness($value, $bigEndian = 0) - { - if ($bigEndian == 0) { - return - self::iecBYTE(($value >> 8) & 0x000000FF) . - self::iecBYTE(($value & 0x000000FF)) . - self::iecBYTE(($value >> 24) & 0x000000FF) . - self::iecBYTE(($value >> 16) & 0x000000FF); - } else { - return - self::iecBYTE(($value >> 24) & 0x000000FF) . - self::iecBYTE(($value >> 16) & 0x000000FF) . - self::iecBYTE(($value >> 8) & 0x000000FF) . - self::iecBYTE(($value & 0x000000FF)); - } - } + /** + * float2iecReal + * + * This function converts float value to IEC-1131 REAL single precision form. + * + * For more see [{@link http://en.wikipedia.org/wiki/Single_precision Single precision on Wiki}] or + * [{@link http://de.php.net/manual/en/function.base-convert.php PHP base_convert function commentary}, Todd Stokes + * @ Georgia Tech 21-Nov-2007] or + * [{@link http://www.php.net/manual/en/function.pack.php PHP pack/unpack functionality}] + * + * @param float $value to be converted + * @return int IEC REAL data type + */ + private static function float2iecReal($value) + { + // get float binary string + $float = pack('f', $value); + // set 32-bit unsigned integer of the float + $w = unpack('L', $float); + return $w[1]; + } } diff --git a/src/ModbusMaster.php b/src/ModbusMaster.php index 9c27f53..8e68a21 100644 --- a/src/ModbusMaster.php +++ b/src/ModbusMaster.php @@ -10,14 +10,12 @@ use Exception; * This source file is subject to the "PhpModbus license" that is bundled * with this package in the file license.txt. * - * - * @copyright Copyright (c) 2004, 2013 Jan Krakora - * @license PhpModbus license - * @category Phpmodbus - * @tutorial Phpmodbus.pkg - * @package Phpmodbus - * @version $id$ - * + * @copyright Copyright (c) 2004, 2013 Jan Krakora + * @license PhpModbus license + * @category Phpmodbus + * @tutorial Phpmodbus.pkg + * @package Phpmodbus + * @version $id$ */ @@ -38,1409 +36,1465 @@ use Exception; * - FC 22: mask write register * - FC 23: read write registers * - * @author Jan Krakora - * @copyright Copyright (c) 2004, 2013 Jan Krakora - * @package Phpmodbus - * + * @author Jan Krakora + * @copyright Copyright (c) 2004, 2013 Jan Krakora + * @package Phpmodbus */ class ModbusMaster { - /** @var resource Communication socket */ - private $sock; - - /** @var string Modbus device IP address */ - public $host = "192.168.1.1"; - - /** @var string gateway port */ - public $port = 502; - - /** @var string (optional) client IP address */ - public $client = ""; // TODO explanation? - - /** @var string client port */ - public $client_port = 502; - - /** @var string ModbusMaster status messages (echo for debugging) */ - public $status; - - /** @var float Total response timeout (seconds, decimals allowed) */ - public $timeout_sec = 5; - - /** @var float Socket read timeout (seconds, decimals allowed) */ - public $socket_read_timeout_sec = 0.3; // 300 ms - - /** @var float Socket write timeout (seconds, decimals allowed) */ - public $socket_write_timeout_sec = 1; - - /** @var int Endianness codding (0 = little endian = 0, 1 = big endian) */ - public $endianness = 0; // - - /** @var string Socket protocol (TCP, UDP) */ - public $socket_protocol = "UDP"; - - /** - * ModbusMaster - * - * This is the constructor that defines {@link $host} IP address of the object. - * - * @param String $host An IP address of a Modbus TCP device. E.g. "192.168.1.1" - * @param String $protocol Socket protocol (TCP, UDP) - */ - public function __construct($host, $protocol) - { - $this->socket_protocol = $protocol; - $this->host = $host; - } - - /** - * __toString - * - * Magic method - */ - public function __toString() - { - return "
" . $this->status . "
"; - } - - /** - * Convert float in seconds to array - * - * @param float $secs - * @return array {sec: ..., usec: ...} - */ - private function secsToSecUsecArray($secs) - { - $remainder = $secs - floor($secs); - - return [ - 'sec' => round($secs - $remainder), - 'usec' => round($remainder * 1e6), - ]; - } - - /** - * connect - * - * Connect the socket - * - * @return bool - * @throws Exception - */ - private function connect() - { - // Create a protocol specific socket - if ($this->socket_protocol == "TCP") { - // TCP socket - $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - } elseif ($this->socket_protocol == "UDP") { - // UDP socket - $this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); - } else { - throw new Exception("Unknown socket protocol, should be 'TCP' or 'UDP'"); - } - // Bind the client socket to a specific local port - if (strlen($this->client) > 0) { - $result = socket_bind($this->sock, $this->client, $this->client_port); - if ($result === false) { - throw new Exception("socket_bind() failed. Reason: ($result)" . - socket_strerror(socket_last_error($this->sock))); - } else { - $this->status .= "Bound\n"; - } - } - - // Socket settings (send/write timeout) - $writeTimeout = $this->secsToSecUsecArray($this->socket_write_timeout_sec); - socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, $writeTimeout); - - // Connect the socket - $result = @socket_connect($this->sock, $this->host, $this->port); - if ($result === false) { - throw new Exception("socket_connect() failed. Reason: ($result)" . - socket_strerror(socket_last_error($this->sock))); - } else { - $this->status .= "Connected\n"; - return true; - } - } - - /** - * disconnect - * - * Disconnect the socket - */ - private function disconnect() - { - socket_close($this->sock); - $this->status .= "Disconnected\n"; - } - - /** - * send - * - * Send the packet via Modbus - * - * @param string $packet - */ - private function send($packet) - { - socket_write($this->sock, $packet, strlen($packet)); - $this->status .= "Send\n"; - } - - /** - * rec - * - * Receive data from the socket - * - * @return bool - * @throws Exception - */ - private function rec() - { - socket_set_nonblock($this->sock); - $readsocks[] = $this->sock; - $writesocks = null; - $exceptsocks = null; - $rec = ""; - $totalReadTimeout = $this->timeout_sec; - $lastAccess = microtime(true); - $readTout = $this->secsToSecUsecArray($this->socket_read_timeout_sec); - - while (false !== socket_select($readsocks, $writesocks, $exceptsocks, $readTout['sec'], $readTout['usec'])) { - $this->status .= "Wait data ... \n"; - if (in_array($this->sock, $readsocks)) { - if (@socket_recv($this->sock, $rec, 2000, 0)) { // read max 2000 bytes - $this->status .= "Data received \n"; - return $rec; - } - $lastAccess = microtime(true); - } else { - $timeSpentWaiting = microtime(true) - $lastAccess; - if ($timeSpentWaiting >= $totalReadTimeout) { - throw new Exception( - "Watchdog time expired [ $totalReadTimeout sec ]!!! " . - "Connection to $this->host:$this->port is not established."); - } - } - $readsocks[] = $this->sock; - } - - return null; - } - - /** - * responseCode - * - * Check the Modbus response code - * - * @param string $packet - * @return bool - * @throws Exception - */ - private function responseCode($packet) - { - if ((ord($packet[7]) & 0x80) > 0) { - // failure code - $failure_code = ord($packet[8]); - // failure code strings - $failures = array( - 0x01 => "ILLEGAL FUNCTION", - 0x02 => "ILLEGAL DATA ADDRESS", - 0x03 => "ILLEGAL DATA VALUE", - 0x04 => "SLAVE DEVICE FAILURE", - 0x05 => "ACKNOWLEDGE", - 0x06 => "SLAVE DEVICE BUSY", - 0x08 => "MEMORY PARITY ERROR", - 0x0A => "GATEWAY PATH UNAVAILABLE", - 0x0B => "GATEWAY TARGET DEVICE FAILED TO RESPOND", - ); - // get failure string - if (key_exists($failure_code, $failures)) { - $failure_str = $failures[$failure_code]; - } else { - $failure_str = "UNDEFINED FAILURE CODE"; - } - // exception response - throw new Exception("Modbus response error code: $failure_code ($failure_str)"); - } else { - $this->status .= "Modbus response error code: NOERROR\n"; - return true; - } - } - - /** - * readCoils - * - * Modbus function FC 1(0x01) - Read Coils - * - * Reads {@link $quantity} of Coils (boolean) from reference - * {@link $reference} of a memory of a Modbus device given by - * {@link $unitId}. - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return bool[] - */ - public function readCoils($unitId, $reference, $quantity) - { - $this->status .= "readCoils: START\n"; - // connect - $this->connect(); - // send FC 1 - $packet = $this->readCoilsPacketBuilder($unitId, $reference, $quantity); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readCoilsParser($rpacket, $quantity); - // disconnect - $this->disconnect(); - $this->status .= "readCoils: DONE\n"; - // return - return $receivedData; - } - - /** - * fc1 - * - * Alias to {@link readCoils} method - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return bool[] - */ - public function fc1($unitId, $reference, $quantity) - { - return $this->readCoils($unitId, $reference, $quantity); - } - - /** - * readCoilsPacketBuilder - * - * FC1 packet builder - read coils - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return string - */ - private function readCoilsPacketBuilder($unitId, $reference, $quantity) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(1); // FC 1 = 1(0x01) - // build body - read section - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - $dataLen += 5; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readCoilsParser - * - * FC 1 response parser - * - * @param string $packet - * @param int $quantity - * @return bool[] - */ - private function readCoilsParser($packet, $quantity) - { - $data = array(); - // check Response code - $this->responseCode($packet); - // get data from stream - for ($i = 0; $i < ord($packet[8]); $i++) { - $data[$i] = ord($packet[9 + $i]); - } - // get bool values to array - $data_boolean_array = array(); - $di = 0; - foreach ($data as $value) { - for ($i = 0; $i < 8; $i++) { - if ($di == $quantity) { - continue; - } - // get boolean value - $v = ($value >> $i) & 0x01; - // build boolean array - if ($v == 0) { - $data_boolean_array[] = false; - } else { - $data_boolean_array[] = true; - } - $di++; - } - } - return $data_boolean_array; - } - - /** - * readInputDiscretes - * - * Modbus function FC 2(0x02) - Read Input Discretes - * - * Reads {@link $quantity} of Inputs (boolean) from reference - * {@link $reference} of a memory of a Modbus device given by - * {@link $unitId}. - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return bool[] - */ - public function readInputDiscretes($unitId, $reference, $quantity) - { - $this->status .= "readInputDiscretes: START\n"; - // connect - $this->connect(); - // send FC 2 - $packet = $this->readInputDiscretesPacketBuilder($unitId, $reference, $quantity); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readInputDiscretesParser($rpacket, $quantity); - // disconnect - $this->disconnect(); - $this->status .= "readInputDiscretes: DONE\n"; - // return - return $receivedData; - } - - /** - * fc2 - * - * Alias to {@link readInputDiscretes} method - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return bool[] - */ - public function fc2($unitId, $reference, $quantity) - { - return $this->readInputDiscretes($unitId, $reference, $quantity); - } - - /** - * readInputDiscretesPacketBuilder - * - * FC2 packet builder - read coils - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return string - */ - private function readInputDiscretesPacketBuilder($unitId, $reference, $quantity) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(2); // FC 2 = 2(0x02) - // build body - read section - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - $dataLen += 5; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readInputDiscretesParser - * - * FC 2 response parser, alias to FC 1 parser i.e. readCoilsParser. - * - * @param string $packet - * @param int $quantity - * @return bool[] - */ - private function readInputDiscretesParser($packet, $quantity) - { - return $this->readCoilsParser($packet, $quantity); - } - - /** - * readMultipleRegisters - * - * Modbus function FC 3(0x03) - Read Multiple Registers. - * - * This function reads {@link $quantity} of Words (2 bytes) from reference - * {@link $referenceRead} of a memory of a Modbus device given by - * {@link $unitId}. - * - * - * @param int $unitId usually ID of Modbus device - * @param int $reference Reference in the device memory to read data (e.g. in device WAGO 750-841, memory MW0 - * starts at address 12288). - * @param int $quantity Amounth of the data to be read from device. - * @return false|array Success flag or array of received data. - */ - public function readMultipleRegisters($unitId, $reference, $quantity) - { - $this->status .= "readMultipleRegisters: START\n"; - // connect - $this->connect(); - // send FC 3 - $packet = $this->readMultipleRegistersPacketBuilder($unitId, $reference, $quantity); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readMultipleRegistersParser($rpacket); - // disconnect - $this->disconnect(); - $this->status .= "readMultipleRegisters: DONE\n"; - // return - return $receivedData; - } - - /** - * fc3 - * - * Alias to {@link readMultipleRegisters} method. - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return false|array - */ - public function fc3($unitId, $reference, $quantity) - { - return $this->readMultipleRegisters($unitId, $reference, $quantity); - } - - /** - * readMultipleRegistersPacketBuilder - * - * Packet FC 3 builder - read multiple registers - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return string - */ - private function readMultipleRegistersPacketBuilder($unitId, $reference, $quantity) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(3); // FC 3 = 3(0x03) - // build body - read section - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - $dataLen += 5; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readMultipleRegistersParser - * - * FC 3 response parser - * - * @param string $packet - * @return array - */ - private function readMultipleRegistersParser($packet) - { - $data = array(); - // check Response code - $this->responseCode($packet); - // get data - for ($i = 0; $i < ord($packet[8]); $i++) { - $data[$i] = ord($packet[9 + $i]); - } - return $data; - } - - /** - * readMultipleInputRegisters - * - * Modbus function FC 4(0x04) - Read Multiple Input Registers. - * - * This function reads {@link $quantity} of Words (2 bytes) from reference - * {@link $referenceRead} of a memory of a Modbus device given by - * {@link $unitId}. - * - * - * @param int $unitId usually ID of Modbus device - * @param int $reference Reference in the device memory to read data. - * @param int $quantity Amounth of the data to be read from device. - * @return false|array Success flag or array of received data. - */ - public function readMultipleInputRegisters($unitId, $reference, $quantity) - { - $this->status .= "readMultipleInputRegisters: START\n"; - // connect - $this->connect(); - // send FC 4 - $packet = $this->readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readMultipleInputRegistersParser($rpacket); - // disconnect - $this->disconnect(); - $this->status .= "readMultipleInputRegisters: DONE\n"; - // return - return $receivedData; - } - - /** - * fc4 - * - * Alias to {@link readMultipleInputRegisters} method. - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return false|array - */ - public function fc4($unitId, $reference, $quantity) - { - return $this->readMultipleInputRegisters($unitId, $reference, $quantity); - } - - /** - * readMultipleInputRegistersPacketBuilder - * - * Packet FC 4 builder - read multiple input registers - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return string - */ - private function readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(4); // FC 4 = 4(0x04) - // build body - read section - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - $dataLen += 5; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); // unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readMultipleInputRegistersParser - * - * FC 4 response parser - * - * @param string $packet - * @return array - */ - private function readMultipleInputRegistersParser($packet) - { - $data = array(); - // check Response code - $this->responseCode($packet); - // get data - for ($i = 0; $i < ord($packet[8]); $i++) { - $data[$i] = ord($packet[9 + $i]); - } - return $data; - } - - /** - * writeSingleCoil - * - * Modbus function FC5(0x05) - Write Single Register. - * - * This function writes {@link $data} single coil at {@link $reference} position of - * memory of a Modbus device given by {@link $unitId}. - * - * - * @param int $unitId usually ID of Modbus device - * @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at - * address 12288) - * @param array $data value to be written (TRUE|FALSE). - * @return bool Success flag - */ - public function writeSingleCoil($unitId, $reference, $data) - { - $this->status .= "writeSingleCoil: START\n"; - // connect - $this->connect(); - // send FC5 - $packet = $this->writeSingleCoilPacketBuilder($unitId, $reference, $data); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->writeSingleCoilParser($rpacket); - // disconnect - $this->disconnect(); - $this->status .= "writeSingleCoil: DONE\n"; - return true; - } - - /** - * fc5 - * - * Alias to {@link writeSingleCoil} method - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return bool - */ - public function fc5($unitId, $reference, $data) - { - return $this->writeSingleCoil($unitId, $reference, $data); - } - - /** - * writeSingleCoilPacketBuilder - * - * Packet builder FC5 - WRITE single register - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return string - */ - private function writeSingleCoilPacketBuilder($unitId, $reference, $data) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - foreach ($data as $key => $dataitem) { - if ($dataitem == true) { - $buffer1 = IecType::iecINT(0xFF00); - } else { - $buffer1 = IecType::iecINT(0x0000); - }; - }; - $dataLen += 2; - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(5); // FC5 = 5(0x05) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $dataLen += 3; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * writeSingleCoilParser - * - * FC5 response parser - * - * @param string $packet - * @return bool - */ - private function writeSingleCoilParser($packet) - { - $this->responseCode($packet); - return true; - } - - /** - * writeSingleRegister - * - * Modbus function FC6(0x06) - Write Single Register. - * - * This function writes {@link $data} single word value at {@link $reference} position of - * memory of a Modbus device given by {@link $unitId}. - * - * - * @param int $unitId usually ID of Modbus device - * @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at - * address 12288) - * @param array $data Array of values to be written. - * @return bool Success flag - */ - public function writeSingleRegister($unitId, $reference, $data) - { - $this->status .= "writeSingleRegister: START\n"; - // connect - $this->connect(); - // send FC6 - $packet = $this->writeSingleRegisterPacketBuilder($unitId, $reference, $data); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->writeSingleRegisterParser($rpacket); - // disconnect - $this->disconnect(); - $this->status .= "writeSingleRegister: DONE\n"; - return true; - } - - /** - * fc6 - * - * Alias to {@link writeSingleRegister} method - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return bool - */ - public function fc6($unitId, $reference, $data) - { - return $this->writeSingleRegister($unitId, $reference, $data); - } - - /** - * writeSingleRegisterPacketBuilder - * - * Packet builder FC6 - WRITE single register - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return string - */ - private function writeSingleRegisterPacketBuilder($unitId, $reference, $data) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - foreach ($data as $key => $dataitem) { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - break; - } - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(6); // FC6 = 6(0x06) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $dataLen += 3; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * writeSingleRegisterParser - * - * FC6 response parser - * - * @param string $packet - * @return bool - */ - private function writeSingleRegisterParser($packet) - { - $this->responseCode($packet); - return true; - } - - /** - * writeMultipleCoils - * - * Modbus function FC15(0x0F) - Write Multiple Coils - * - * This function writes {@link $data} array at {@link $reference} position of - * memory of a Modbus device given by {@link $unitId}. - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return bool - */ - public function writeMultipleCoils($unitId, $reference, $data) - { - $this->status .= "writeMultipleCoils: START\n"; - // connect - $this->connect(); - // send FC15 - $packet = $this->writeMultipleCoilsPacketBuilder($unitId, $reference, $data); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->writeMultipleCoilsParser($rpacket); - // disconnect - $this->disconnect(); - $this->status .= "writeMultipleCoils: DONE\n"; - return true; - } - - /** - * fc15 - * - * Alias to {@link writeMultipleCoils} method - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return bool - */ - public function fc15($unitId, $reference, $data) - { - return $this->writeMultipleCoils($unitId, $reference, $data); - } - - /** - * writeMultipleCoilsPacketBuilder - * - * Packet builder FC15 - Write multiple coils - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return string - */ - private function writeMultipleCoilsPacketBuilder($unitId, $reference, $data) - { - $dataLen = 0; - // build bool stream to the WORD array - $data_word_stream = array(); - $data_word = 0; - $shift = 0; - for ($i = 0; $i < count($data); $i++) { - if ((($i % 8) == 0) && ($i > 0)) { - $data_word_stream[] = $data_word; - $shift = 0; - $data_word = 0; - $data_word |= (0x01 && $data[$i]) << $shift; - $shift++; - } else { - $data_word |= (0x01 && $data[$i]) << $shift; - $shift++; - } - } - $data_word_stream[] = $data_word; - // show binary stream to status string - foreach ($data_word_stream as $d) { - $this->status .= sprintf("byte=b%08b\n", $d); - } - // build data section - $buffer1 = ""; - foreach ($data_word_stream as $key => $dataitem) { - $buffer1 .= IecType::iecBYTE($dataitem); // register values x - $dataLen += 1; - } - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(15); // FC 15 = 15(0x0f) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT(count($data)); // bit count - $buffer2 .= IecType::iecBYTE((count($data) + 7) / 8); // byte count - $dataLen += 6; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); // unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * writeMultipleCoilsParser - * - * FC15 response parser - * - * @param string $packet - * @return bool - */ - private function writeMultipleCoilsParser($packet) - { - $this->responseCode($packet); - return true; - } - - /** - * writeMultipleRegister - * - * Modbus function FC16(0x10) - Write Multiple Register. - * - * This function writes {@link $data} array at {@link $reference} position of - * memory of a Modbus device given by {@link $unitId}. - * - * - * @param int $unitId usually ID of Modbus device - * @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at - * address 12288) - * @param array $data Array of values to be written. - * @param array $dataTypes Array of types of values to be written. The array should consists of string "INT", - * "DINT" and "REAL". - * @return bool Success flag - */ - public function writeMultipleRegister($unitId, $reference, $data, $dataTypes) - { - $this->status .= "writeMultipleRegister: START\n"; - // connect - $this->connect(); - // send FC16 - $packet = $this->writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->writeMultipleRegisterParser($rpacket); - // disconnect - $this->disconnect(); - $this->status .= "writeMultipleRegister: DONE\n"; - return true; - } - - /** - * fc16 - * - * Alias to {@link writeMultipleRegister} method - * - * @param int $unitId - * @param int $reference - * @param array $data - * @param array $dataTypes - * @return bool - */ - public function fc16($unitId, $reference, $data, $dataTypes) - { - return $this->writeMultipleRegister($unitId, $reference, $data, $dataTypes); - } - - /** - * writeMultipleRegisterPacketBuilder - * - * Packet builder FC16 - WRITE multiple register - * e.g.: 4dd90000000d0010300000030603e807d00bb8 - * - * @param int $unitId - * @param int $reference - * @param array $data - * @param array $dataTypes - * @return string - */ - private function writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - foreach ($data as $key => $dataitem) { - if ($dataTypes[$key] == "INT") { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - } elseif ($dataTypes[$key] == "DINT") { - $buffer1 .= IecType::iecDINT($dataitem, $this->endianness); // register values x - $dataLen += 4; - } elseif ($dataTypes[$key] == "REAL") { - $buffer1 .= IecType::iecREAL($dataitem, $this->endianness); // register values x - $dataLen += 4; - } else { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - } - } - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(16); // FC 16 = 16(0x10) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($dataLen / 2); // word count - $buffer2 .= IecType::iecBYTE($dataLen); // byte count - $dataLen += 6; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * writeMultipleRegisterParser - * - * FC16 response parser - * - * @param string $packet - * @return bool - */ - private function writeMultipleRegisterParser($packet) - { - $this->responseCode($packet); - return true; - } - - /** - * maskWriteRegister - * - * Modbus function FC22(0x16) - Mask Write Register. - * - * This function alter single bit(s) at {@link $reference} position of - * memory of a Modbus device given by {@link $unitId}. - * - * Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) - * - * @param int $unitId usually ID of Modbus device - * @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at address - * 12288) - * @param int $andMask - * @param int $orMask - * @return bool Success flag - */ - public function maskWriteRegister($unitId, $reference, $andMask, $orMask) - { - $this->status .= "maskWriteRegister: START\n"; - // connect - $this->connect(); - // send FC22 - $packet = $this->maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->maskWriteRegisterParser($rpacket); - // disconnect - $this->disconnect(); - $this->status .= "maskWriteRegister: DONE\n"; - return true; - } - - /** - * fc22 - * - * Alias to {@link maskWriteRegister} method - * - * @param int $unitId - * @param int $reference - * @param int $andMask - * @param int $orMask - * @return bool - */ - public function fc22($unitId, $reference, $andMask, $orMask) - { - return $this->maskWriteRegister($unitId, $reference, $andMask, $orMask); - } - - /** - * maskWriteRegisterPacketBuilder - * - * Packet builder FC22 - MASK WRITE register - * - * @param int $unitId - * @param int $reference - * @param int $andMask - * @param int $orMask - * @return string - */ - private function maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(22); // FC 22 = 22(0x16) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($andMask); // AND mask - $buffer2 .= IecType::iecINT($orMask); // OR mask - $dataLen += 7; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * maskWriteRegisterParser - * - * FC22 response parser - * - * @param string $packet - * @return bool - */ - private function maskWriteRegisterParser($packet) - { - $this->responseCode($packet); - return true; - } - - /** - * readWriteRegisters - * - * Modbus function FC23(0x17) - Read Write Registers. - * - * This function writes {@link $data} array at reference {@link $referenceWrite} - * position of memory of a Modbus device given by {@link $unitId}. Simultanously, - * it returns {@link $quantity} of Words (2 bytes) from reference {@link $referenceRead}. - * - * - * @param int $unitId usually ID of Modbus device - * @param int $referenceRead Reference in the device memory to read data (e.g. in device WAGO 750-841, memory - * MW0 starts at address 12288). - * @param int $quantity Amounth of the data to be read from device. - * @param int $referenceWrite Reference in the device memory to write data. - * @param array $data Array of values to be written. - * @param array $dataTypes Array of types of values to be written. The array should consists of string "INT", - * "DINT" and "REAL". - * @return false|array Success flag or array of data. - */ - public function readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes) - { - $this->status .= "readWriteRegisters: START\n"; - // connect - $this->connect(); - // send FC23 - $packet = $this->readWriteRegistersPacketBuilder($unitId, $referenceRead, $quantity, $referenceWrite, $data, - $dataTypes); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readWriteRegistersParser($rpacket); - // disconnect - $this->disconnect(); - $this->status .= "writeMultipleRegister: DONE\n"; - // return - return $receivedData; - } - - /** - * fc23 - * - * Alias to {@link readWriteRegisters} method. - * - * @param int $unitId - * @param int $referenceRead - * @param int $quantity - * @param int $referenceWrite - * @param array $data - * @param array $dataTypes - * @return false|array - */ - public function fc23($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes) - { - return $this->readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes); - } - - /** - * readWriteRegistersPacketBuilder - * - * Packet FC23 builder - READ WRITE registers - * - * - * @param int $unitId - * @param int $referenceRead - * @param int $quantity - * @param int $referenceWrite - * @param array $data - * @param array $dataTypes - * @return string - */ - private function readWriteRegistersPacketBuilder( - $unitId, - $referenceRead, - $quantity, - $referenceWrite, - $data, - $dataTypes - ) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - foreach ($data as $key => $dataitem) { - if ($dataTypes[$key] == "INT") { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - } elseif ($dataTypes[$key] == "DINT") { - $buffer1 .= IecType::iecDINT($dataitem, $this->endianness); // register values x - $dataLen += 4; - } elseif ($dataTypes[$key] == "REAL") { - $buffer1 .= IecType::iecREAL($dataitem, $this->endianness); // register values x - $dataLen += 4; - } else { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - } - } - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(23); // FC 23 = 23(0x17) - // build body - read section - $buffer2 .= IecType::iecINT($referenceRead); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - // build body - write section - $buffer2 .= IecType::iecINT($referenceWrite); // refnumber = 12288 - $buffer2 .= IecType::iecINT($dataLen / 2); // word count - $buffer2 .= IecType::iecBYTE($dataLen); // byte count - $dataLen += 10; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readWriteRegistersParser - * - * FC23 response parser - * - * @param string $packet - * @return array|false - */ - private function readWriteRegistersParser($packet) - { - $data = array(); - // if not exception - if (!$this->responseCode($packet)) { - return false; - } - // get data - for ($i = 0; $i < ord($packet[8]); $i++) { - $data[$i] = ord($packet[9 + $i]); - } - return $data; - } - - /** - * byte2hex - * - * Parse data and get it to the Hex form - * - * @param int $value - * @return string - */ - private function byte2hex($value) - { - $h = dechex(($value >> 4) & 0x0F); - $l = dechex($value & 0x0F); - return "$h$l"; - } - - /** - * printPacket - * - * Print a packet in the hex form - * - * @param string $packet - * @return string - */ - private function printPacket($packet) - { - $str = ""; - $str .= "Packet: "; - for ($i = 0; $i < strlen($packet); $i++) { - $str .= $this->byte2hex(ord($packet[$i])); - } - $str .= "\n"; - return $str; - } - - /** - * Set data receive timeout. - * Writes property timeout_sec - * - * @param float $seconds seconds - */ - public function setTimeout($seconds) - { - $this->timeout_sec = $seconds; - } - - /** - * Set socket read/write timeout. Null = no change. - * - * @param float|null $read_timeout_sec data read timeout (seconds, default 0.3) - * @param float|null $write_timeout_sec data write timeout (seconds, default 1.0) - * @internal param float $seconds seconds - */ - public function setSocketTimeout($read_timeout_sec, $write_timeout_sec) - { - // Set read timeout if given - if ($read_timeout_sec !== null) { - $this->socket_read_timeout_sec = $read_timeout_sec; - } - - // Set write timeout if given - if ($write_timeout_sec !== null) { - $this->socket_write_timeout_sec = $write_timeout_sec; - } - } + /** +* + * + * @var string Modbus device IP address +*/ + public $host = '192.168.1.1'; + /** +* + * + * @var string gateway port +*/ + public $port = 502; + /** + * @var string (optional) client IP address when binding client +*/ + public $client = ''; + /** +* + * + * @var string client port set when binding client to local ip&port +*/ + public $client_port = 502; + /** +* + * + * @var string ModbusMaster status messages (echo for debugging) +*/ + public $status; + /** +* + * + * @var float Total response timeout (seconds, decimals allowed) +*/ + public $timeout_sec = 5; + /** + * @var float Socket read timeout (seconds, decimals allowed) +*/ + public $socket_read_timeout_sec = 0.3; + /** +* + * + * @var float Socket write timeout (seconds, decimals allowed) +*/ + public $socket_write_timeout_sec = 1; // 300 ms + /** + * @var int Endianness codding (0 = little endian = 0, 1 = big endian) +*/ + public $endianness = 0; + /** +* + * + * @var string Socket protocol (TCP, UDP) +*/ + public $socket_protocol = 'UDP'; // + /** +* + * + * @var resource Communication socket +*/ + private $sock; + + /** + * ModbusMaster + * + * This is the constructor that defines {@link $host} IP address of the object. + * + * @param String $host An IP address of a Modbus TCP device. E.g. "192.168.1.1" + * @param String $protocol Socket protocol (TCP, UDP) + */ + public function __construct($host, $protocol) + { + $this->socket_protocol = $protocol; + $this->host = $host; + } + + /** + * __toString + * + * Magic method + */ + public function __toString() + { + return '
' . $this->status . '
'; + } + + /** + * fc1 + * + * Alias to {@link readCoils} method + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return bool[] + * @throws \Exception + */ + public function fc1($unitId, $reference, $quantity) + { + return $this->readCoils($unitId, $reference, $quantity); + } + + /** + * readCoils + * + * Modbus function FC 1(0x01) - Read Coils + * + * Reads {@link $quantity} of Coils (boolean) from reference + * {@link $reference} of a memory of a Modbus device given by + * {@link $unitId}. + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return bool[] + * @throws \Exception + */ + public function readCoils($unitId, $reference, $quantity) + { + $this->status .= "readCoils: START\n"; + // connect + $this->connect(); + // send FC 1 + $packet = $this->readCoilsPacketBuilder($unitId, $reference, $quantity); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $receivedData = $this->readCoilsParser($rpacket, $quantity); + // disconnect + $this->disconnect(); + $this->status .= "readCoils: DONE\n"; + // return + return $receivedData; + } + + /** + * connect + * + * Connect the socket + * + * @return bool + * @throws Exception + */ + private function connect() + { + // Create a protocol specific socket + if ($this->socket_protocol === 'TCP') { + // TCP socket + $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + } elseif ($this->socket_protocol === 'UDP') { + // UDP socket + $this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + } else { + throw new Exception("Unknown socket protocol, should be 'TCP' or 'UDP'"); + } + // Bind the client socket to a specific local port + if (strlen($this->client) > 0) { + $result = socket_bind($this->sock, $this->client, $this->client_port); + if ($result === false) { + throw new Exception( + "socket_bind() failed. Reason: ($result)" . + socket_strerror(socket_last_error($this->sock)) + ); + } else { + $this->status .= "Bound\n"; + } + } + + // Socket settings (send/write timeout) + $writeTimeout = $this->secsToSecUsecArray($this->socket_write_timeout_sec); + socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, $writeTimeout); + + // Connect the socket + $result = @socket_connect($this->sock, $this->host, $this->port); + if ($result === false) { + throw new Exception( + "socket_connect() failed. Reason: ($result)" . + socket_strerror(socket_last_error($this->sock)) + ); + } else { + $this->status .= "Connected\n"; + return true; + } + } + + /** + * Convert float in seconds to array + * + * @param float $secs + * @return array {sec: ..., usec: ...} + */ + private function secsToSecUsecArray($secs) + { + $remainder = $secs - floor($secs); + + return [ + 'sec' => round($secs - $remainder), + 'usec' => round($remainder * 1e6), + ]; + } + + /** + * readCoilsPacketBuilder + * + * FC1 packet builder - read coils + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return string + */ + private function readCoilsPacketBuilder($unitId, $reference, $quantity) + { + $dataLen = 0; + // build data section + $buffer1 = ''; + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(1); // FC 1 = 1(0x01) + // build body - read section + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT($quantity); // quantity + $dataLen += 5; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * printPacket + * + * Print a packet in the hex form + * + * @param string $packet + * @return string + */ + private function printPacket($packet) + { + $str = 'Packet: '; + for ($i = 0, $len = strlen($packet); $i < $len; $i++) { + $str .= $this->byte2hex(ord($packet[$i])); + } + $str .= "\n"; + return $str; + } + + /** + * byte2hex + * + * Parse data and get it to the Hex form + * + * @param int $value + * @return string + */ + private function byte2hex($value) + { + $h = dechex(($value >> 4) & 0x0F); + $l = dechex($value & 0x0F); + return "$h$l"; + } + + /** + * send + * + * Send the packet via Modbus + * + * @param string $packet + */ + private function send($packet) + { + socket_write($this->sock, $packet, strlen($packet)); + $this->status .= "Send\n"; + } + + /** + * rec + * + * Receive data from the socket + * + * @return bool + * @throws Exception + */ + private function rec() + { + socket_set_nonblock($this->sock); + $readsocks[] = $this->sock; + $writesocks = null; + $exceptsocks = null; + $rec = ""; + $totalReadTimeout = $this->timeout_sec; + $lastAccess = microtime(true); + $readTout = $this->secsToSecUsecArray($this->socket_read_timeout_sec); + + while (false !== socket_select($readsocks, $writesocks, $exceptsocks, $readTout['sec'], $readTout['usec'])) { + $this->status .= "Wait data ... \n"; + if (in_array($this->sock, $readsocks)) { + if (@socket_recv($this->sock, $rec, 2000, 0)) { // read max 2000 bytes + $this->status .= "Data received \n"; + return $rec; + } + $lastAccess = microtime(true); + } else { + $timeSpentWaiting = microtime(true) - $lastAccess; + if ($timeSpentWaiting >= $totalReadTimeout) { + throw new Exception( + "Watchdog time expired [ $totalReadTimeout sec ]!!! " . + "Connection to $this->host:$this->port is not established." + ); + } + } + $readsocks[] = $this->sock; + } + + return null; + } + + /** + * readCoilsParser + * + * FC 1 response parser + * + * @param string $packet + * @param int $quantity + * @return bool[] + * @throws \Exception + */ + private function readCoilsParser($packet, $quantity) + { + $data = array(); + // check Response code + $this->responseCode($packet); + // get data from stream + for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { + $data[$i] = ord($packet[9 + $i]); + } + // get bool values to array + $data_boolean_array = array(); + $di = 0; + foreach ($data as $value) { + for ($i = 0; $i < 8; $i++) { + if ($di == $quantity) { + continue; + } + // get boolean value + $v = ($value >> $i) & 0x01; + // build boolean array + if ($v == 0) { + $data_boolean_array[] = false; + } else { + $data_boolean_array[] = true; + } + $di++; + } + } + return $data_boolean_array; + } + + /** + * responseCode + * + * Check the Modbus response code + * + * @param string $packet + * @return bool + * @throws Exception + */ + private function responseCode($packet) + { + if ((ord($packet[7]) & 0x80) > 0) { + // failure code + $failure_code = ord($packet[8]); + // failure code strings + $failures = array( + 0x01 => 'ILLEGAL FUNCTION', + 0x02 => 'ILLEGAL DATA ADDRESS', + 0x03 => 'ILLEGAL DATA VALUE', + 0x04 => 'SLAVE DEVICE FAILURE', + 0x05 => 'ACKNOWLEDGE', + 0x06 => 'SLAVE DEVICE BUSY', + 0x08 => 'MEMORY PARITY ERROR', + 0x0A => 'GATEWAY PATH UNAVAILABLE', + 0x0B => 'GATEWAY TARGET DEVICE FAILED TO RESPOND', + ); + // get failure string + if (array_key_exists($failure_code, $failures)) { + $failure_str = $failures[$failure_code]; + } else { + $failure_str = 'UNDEFINED FAILURE CODE'; + } + // exception response + throw new Exception("Modbus response error code: $failure_code ($failure_str)"); + } else { + $this->status .= "Modbus response error code: NOERROR\n"; + return true; + } + } + + /** + * disconnect + * + * Disconnect the socket + */ + private function disconnect() + { + socket_close($this->sock); + $this->status .= "Disconnected\n"; + } + + /** + * fc2 + * + * Alias to {@link readInputDiscretes} method + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return bool[] + * @throws \Exception + */ + public function fc2($unitId, $reference, $quantity) + { + return $this->readInputDiscretes($unitId, $reference, $quantity); + } + + /** + * readInputDiscretes + * + * Modbus function FC 2(0x02) - Read Input Discretes + * + * Reads {@link $quantity} of Inputs (boolean) from reference + * {@link $reference} of a memory of a Modbus device given by + * {@link $unitId}. + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return bool[] + * @throws \Exception + */ + public function readInputDiscretes($unitId, $reference, $quantity) + { + $this->status .= "readInputDiscretes: START\n"; + // connect + $this->connect(); + // send FC 2 + $packet = $this->readInputDiscretesPacketBuilder($unitId, $reference, $quantity); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $receivedData = $this->readInputDiscretesParser($rpacket, $quantity); + // disconnect + $this->disconnect(); + $this->status .= "readInputDiscretes: DONE\n"; + // return + return $receivedData; + } + + /** + * readInputDiscretesPacketBuilder + * + * FC2 packet builder - read coils + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return string + */ + private function readInputDiscretesPacketBuilder($unitId, $reference, $quantity) + { + $dataLen = 0; + // build data section + $buffer1 = ''; + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(2); // FC 2 = 2(0x02) + // build body - read section + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT($quantity); // quantity + $dataLen += 5; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // lenght + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * readInputDiscretesParser + * + * FC 2 response parser, alias to FC 1 parser i.e. readCoilsParser. + * + * @param string $packet + * @param int $quantity + * @return bool[] + * @throws \Exception + */ + private function readInputDiscretesParser($packet, $quantity) + { + return $this->readCoilsParser($packet, $quantity); + } + + /** + * fc3 + * + * Alias to {@link readMultipleRegisters} method. + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return false|array + * @throws \Exception + */ + public function fc3($unitId, $reference, $quantity) + { + return $this->readMultipleRegisters($unitId, $reference, $quantity); + } + + /** + * readMultipleRegisters + * + * Modbus function FC 3(0x03) - Read Multiple Registers. + * + * This function reads {@link $quantity} of Words (2 bytes) from reference + * {@link $referenceRead} of a memory of a Modbus device given by + * {@link $unitId}. + * + * @param int $unitId usually ID of Modbus device + * @param int $reference Reference in the device memory to read data (e.g. in device WAGO 750-841, memory MW0 + * starts at address 12288). + * @param int $quantity Amounth of the data to be read from device. + * @return false|array Success flag or array of received data. + * @throws \Exception + */ + public function readMultipleRegisters($unitId, $reference, $quantity) + { + $this->status .= "readMultipleRegisters: START\n"; + // connect + $this->connect(); + // send FC 3 + $packet = $this->readMultipleRegistersPacketBuilder($unitId, $reference, $quantity); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $receivedData = $this->readMultipleRegistersParser($rpacket); + // disconnect + $this->disconnect(); + $this->status .= "readMultipleRegisters: DONE\n"; + // return + return $receivedData; + } + + /** + * readMultipleRegistersPacketBuilder + * + * Packet FC 3 builder - read multiple registers + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return string + */ + private function readMultipleRegistersPacketBuilder($unitId, $reference, $quantity) + { + $dataLen = 0; + // build data section + $buffer1 = ''; + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(3); // FC 3 = 3(0x03) + // build body - read section + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT($quantity); // quantity + $dataLen += 5; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * readMultipleRegistersParser + * + * FC 3 response parser + * + * @param string $packet + * @return array + * @throws \Exception + */ + private function readMultipleRegistersParser($packet) + { + $data = array(); + // check Response code + $this->responseCode($packet); + // get data + for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { + $data[$i] = ord($packet[9 + $i]); + } + return $data; + } + + /** + * fc4 + * + * Alias to {@link readMultipleInputRegisters} method. + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return false|array + * @throws \Exception + */ + public function fc4($unitId, $reference, $quantity) + { + return $this->readMultipleInputRegisters($unitId, $reference, $quantity); + } + + /** + * readMultipleInputRegisters + * + * Modbus function FC 4(0x04) - Read Multiple Input Registers. + * + * This function reads {@link $quantity} of Words (2 bytes) from reference + * {@link $referenceRead} of a memory of a Modbus device given by + * {@link $unitId}. + * + * @param int $unitId usually ID of Modbus device + * @param int $reference Reference in the device memory to read data. + * @param int $quantity Amounth of the data to be read from device. + * @return false|array Success flag or array of received data. + * @throws \Exception + */ + public function readMultipleInputRegisters($unitId, $reference, $quantity) + { + $this->status .= "readMultipleInputRegisters: START\n"; + // connect + $this->connect(); + // send FC 4 + $packet = $this->readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $receivedData = $this->readMultipleInputRegistersParser($rpacket); + // disconnect + $this->disconnect(); + $this->status .= "readMultipleInputRegisters: DONE\n"; + // return + return $receivedData; + } + + /** + * readMultipleInputRegistersPacketBuilder + * + * Packet FC 4 builder - read multiple input registers + * + * @param int $unitId + * @param int $reference + * @param int $quantity + * @return string + */ + private function readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity) + { + $dataLen = 0; + // build data section + $buffer1 = ''; + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(4); // FC 4 = 4(0x04) + // build body - read section + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT($quantity); // quantity + $dataLen += 5; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); // unit ID + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * readMultipleInputRegistersParser + * + * FC 4 response parser + * + * @param string $packet + * @return array + * @throws \Exception + */ + private function readMultipleInputRegistersParser($packet) + { + $data = array(); + // check Response code + $this->responseCode($packet); + // get data + for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { + $data[$i] = ord($packet[9 + $i]); + } + return $data; + } + + /** + * fc5 + * + * Alias to {@link writeSingleCoil} method + * + * @param int $unitId + * @param int $reference + * @param array $data + * @return bool + * @throws \Exception + */ + public function fc5($unitId, $reference, $data) + { + return $this->writeSingleCoil($unitId, $reference, $data); + } + + /** + * writeSingleCoil + * + * Modbus function FC5(0x05) - Write Single Register. + * + * This function writes {@link $data} single coil at {@link $reference} position of + * memory of a Modbus device given by {@link $unitId}. + * + * @param int $unitId usually ID of Modbus device + * @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at address 12288) + * address 12288) + * @param array $data value to be written (TRUE|FALSE). + * @return bool Success flag + * @throws \Exception + */ + public function writeSingleCoil($unitId, $reference, $data) + { + $this->status .= "writeSingleCoil: START\n"; + // connect + $this->connect(); + // send FC5 + $packet = $this->writeSingleCoilPacketBuilder($unitId, $reference, $data); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $this->writeSingleCoilParser($rpacket); + // disconnect + $this->disconnect(); + $this->status .= "writeSingleCoil: DONE\n"; + return true; + } + + /** + * writeSingleCoilPacketBuilder + * + * Packet builder FC5 - WRITE single register + * + * @param int $unitId + * @param int $reference + * @param array $data + * @return string + */ + private function writeSingleCoilPacketBuilder($unitId, $reference, $data) + { + $dataLen = 0; + // build data section + $buffer1 = ''; + foreach ($data as $key => $dataitem) { + if ($dataitem == true) { + $buffer1 = IecType::iecINT(0xFF00); + } else { + $buffer1 = IecType::iecINT(0x0000); + } + } + $dataLen += 2; + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(5); // FC5 = 5(0x05) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $dataLen += 3; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // lenght + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * writeSingleCoilParser + * + * FC5 response parser + * + * @param string $packet + * @return bool + * @throws \Exception + */ + private function writeSingleCoilParser($packet) + { + $this->responseCode($packet); + return true; + } + + /** + * fc6 + * + * Alias to {@link writeSingleRegister} method + * + * @param int $unitId + * @param int $reference + * @param array $data + * @return bool + * @throws \Exception + */ + public function fc6($unitId, $reference, $data) + { + return $this->writeSingleRegister($unitId, $reference, $data); + } + + /** + * writeSingleRegister + * + * Modbus function FC6(0x06) - Write Single Register. + * + * This function writes {@link $data} single word value at {@link $reference} position of + * memory of a Modbus device given by {@link $unitId}. + * + * @param int $unitId usually ID of Modbus device + * @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at address 12288) + * address 12288) + * @param array $data Array of values to be written. + * @return bool Success flag + * @throws \Exception + */ + public function writeSingleRegister($unitId, $reference, $data) + { + $this->status .= "writeSingleRegister: START\n"; + // connect + $this->connect(); + // send FC6 + $packet = $this->writeSingleRegisterPacketBuilder($unitId, $reference, $data); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $this->writeSingleRegisterParser($rpacket); + // disconnect + $this->disconnect(); + $this->status .= "writeSingleRegister: DONE\n"; + return true; + } + + /** + * writeSingleRegisterPacketBuilder + * + * Packet builder FC6 - WRITE single register + * + * @param int $unitId + * @param int $reference + * @param array $data + * @return string + */ + private function writeSingleRegisterPacketBuilder($unitId, $reference, array $data) + { + $dataLen = 0; + // build data section + $buffer1 = ''; + foreach ($data as $key => $dataitem) { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + break; + } + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(6); // FC6 = 6(0x06) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $dataLen += 3; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * writeSingleRegisterParser + * + * FC6 response parser + * + * @param string $packet + * @return bool + * @throws \Exception + */ + private function writeSingleRegisterParser($packet) + { + $this->responseCode($packet); + return true; + } + + /** + * fc15 + * + * Alias to {@link writeMultipleCoils} method + * + * @param int $unitId + * @param int $reference + * @param array $data + * @return bool + * @throws \Exception + */ + public function fc15($unitId, $reference, $data) + { + return $this->writeMultipleCoils($unitId, $reference, $data); + } + + /** + * writeMultipleCoils + * + * Modbus function FC15(0x0F) - Write Multiple Coils + * + * This function writes {@link $data} array at {@link $reference} position of + * memory of a Modbus device given by {@link $unitId}. + * + * @param int $unitId + * @param int $reference + * @param array $data + * @return bool + * @throws \Exception + */ + public function writeMultipleCoils($unitId, $reference, $data) + { + $this->status .= "writeMultipleCoils: START\n"; + // connect + $this->connect(); + // send FC15 + $packet = $this->writeMultipleCoilsPacketBuilder($unitId, $reference, $data); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $this->writeMultipleCoilsParser($rpacket); + // disconnect + $this->disconnect(); + $this->status .= "writeMultipleCoils: DONE\n"; + return true; + } + + /** + * writeMultipleCoilsPacketBuilder + * + * Packet builder FC15 - Write multiple coils + * + * @param int $unitId + * @param int $reference + * @param array $data + * @return string + */ + private function writeMultipleCoilsPacketBuilder($unitId, $reference, array $data) + { + $dataLen = 0; + // build bool stream to the WORD array + $data_word_stream = array(); + $data_word = 0; + $shift = 0; + for ($i = 0, $len = count($data); $i < $len; $i++) { + if ((($i % 8) === 0) && ($i > 0)) { + $data_word_stream[] = $data_word; + $shift = 0; + $data_word = 0; + $data_word |= (0x01 && $data[$i]) << $shift; + $shift++; + } else { + $data_word |= (0x01 && $data[$i]) << $shift; + $shift++; + } + } + $data_word_stream[] = $data_word; + // show binary stream to status string + foreach ($data_word_stream as $d) { + $this->status .= sprintf("byte=b%08b\n", $d); + } + // build data section + $buffer1 = ""; + foreach ($data_word_stream as $key => $dataitem) { + $buffer1 .= IecType::iecBYTE($dataitem); // register values x + $dataLen += 1; + } + // build body + $buffer2 = ""; + $buffer2 .= IecType::iecBYTE(15); // FC 15 = 15(0x0f) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT(count($data)); // bit count + $buffer2 .= IecType::iecBYTE((count($data) + 7) / 8); // byte count + $dataLen += 6; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); // unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * writeMultipleCoilsParser + * + * FC15 response parser + * + * @param string $packet + * @return bool + * @throws \Exception + */ + private function writeMultipleCoilsParser($packet) + { + $this->responseCode($packet); + return true; + } + + /** + * fc16 + * + * Alias to {@link writeMultipleRegister} method + * + * @param int $unitId + * @param int $reference + * @param array $data + * @param array $dataTypes + * @return bool + * @throws \Exception + */ + public function fc16($unitId, $reference, $data, $dataTypes) + { + return $this->writeMultipleRegister($unitId, $reference, $data, $dataTypes); + } + + /** + * writeMultipleRegister + * + * Modbus function FC16(0x10) - Write Multiple Register. + * + * This function writes {@link $data} array at {@link $reference} position of + * memory of a Modbus device given by {@link $unitId}. + * + * @param int $unitId usually ID of Modbus device + * @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at address 12288) + * address 12288) + * @param array $data Array of values to be written. + * @param array $dataTypes Array of types of values to be written. The array should consists of string "INT", + * "DINT" and "REAL". + * @return bool Success flag + * @throws \Exception + */ + public function writeMultipleRegister($unitId, $reference, $data, $dataTypes) + { + $this->status .= "writeMultipleRegister: START\n"; + // connect + $this->connect(); + // send FC16 + $packet = $this->writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $this->writeMultipleRegisterParser($rpacket); + // disconnect + $this->disconnect(); + $this->status .= "writeMultipleRegister: DONE\n"; + return true; + } + + /** + * writeMultipleRegisterPacketBuilder + * + * Packet builder FC16 - WRITE multiple register + * e.g.: 4dd90000000d0010300000030603e807d00bb8 + * + * @param int $unitId + * @param int $reference + * @param array $data + * @param array $dataTypes + * @return string + */ + private function writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes) + { + $dataLen = 0; + // build data section + $buffer1 = ""; + foreach ($data as $key => $dataitem) { + if ($dataTypes[$key] === 'INT') { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + } elseif ($dataTypes[$key] === 'DINT') { + $buffer1 .= IecType::iecDINT($dataitem, $this->endianness); // register values x + $dataLen += 4; + } elseif ($dataTypes[$key] === 'REAL') { + $buffer1 .= IecType::iecREAL($dataitem, $this->endianness); // register values x + $dataLen += 4; + } else { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + } + } + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(16); // FC 16 = 16(0x10) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT($dataLen / 2); // word count + $buffer2 .= IecType::iecBYTE($dataLen); // byte count + $dataLen += 6; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * writeMultipleRegisterParser + * + * FC16 response parser + * + * @param string $packet + * @return bool + * @throws \Exception + */ + private function writeMultipleRegisterParser($packet) + { + $this->responseCode($packet); + return true; + } + + /** + * fc22 + * + * Alias to {@link maskWriteRegister} method + * + * @param int $unitId + * @param int $reference + * @param int $andMask + * @param int $orMask + * @return bool + * @throws \Exception + */ + public function fc22($unitId, $reference, $andMask, $orMask) + { + return $this->maskWriteRegister($unitId, $reference, $andMask, $orMask); + } + + /** + * maskWriteRegister + * + * Modbus function FC22(0x16) - Mask Write Register. + * + * This function alter single bit(s) at {@link $reference} position of + * memory of a Modbus device given by {@link $unitId}. + * + * Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask)) + * + * @param int $unitId usually ID of Modbus device + * @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at address + * 12288) + * @param int $andMask + * @param int $orMask + * @return bool Success flag + * @throws \Exception + */ + public function maskWriteRegister($unitId, $reference, $andMask, $orMask) + { + $this->status .= "maskWriteRegister: START\n"; + // connect + $this->connect(); + // send FC22 + $packet = $this->maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $this->maskWriteRegisterParser($rpacket); + // disconnect + $this->disconnect(); + $this->status .= "maskWriteRegister: DONE\n"; + return true; + } + + /** + * maskWriteRegisterPacketBuilder + * + * Packet builder FC22 - MASK WRITE register + * + * @param int $unitId + * @param int $reference + * @param int $andMask + * @param int $orMask + * @return string + */ + private function maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask) + { + $dataLen = 0; + // build data section + $buffer1 = ''; + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(22); // FC 22 = 22(0x16) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT($andMask); // AND mask + $buffer2 .= IecType::iecINT($orMask); // OR mask + $dataLen += 7; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // lenght + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * maskWriteRegisterParser + * + * FC22 response parser + * + * @param string $packet + * @return bool + * @throws \Exception + */ + private function maskWriteRegisterParser($packet) + { + $this->responseCode($packet); + return true; + } + + /** + * fc23 + * + * Alias to {@link readWriteRegisters} method. + * + * @param int $unitId + * @param int $referenceRead + * @param int $quantity + * @param int $referenceWrite + * @param array $data + * @param array $dataTypes + * @return false|array + * @throws \Exception + */ + public function fc23($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes) + { + return $this->readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes); + } + + /** + * readWriteRegisters + * + * Modbus function FC23(0x17) - Read Write Registers. + * + * This function writes {@link $data} array at reference {@link $referenceWrite} + * position of memory of a Modbus device given by {@link $unitId}. Simultanously, + * it returns {@link $quantity} of Words (2 bytes) from reference {@link $referenceRead}. + * + * @param int $unitId usually ID of Modbus device + * @param int $referenceRead Reference in the device memory to read data (e.g. in device WAGO 750-841, memory MW0 starts at address 12288). MW0 starts at address 12288). + * MW0 starts at address 12288). + * @param int $quantity Amounth of the data to be read from device. + * @param int $referenceWrite Reference in the device memory to write data. + * @param array $data Array of values to be written. + * @param array $dataTypes Array of types of values to be written. The array should consists of string "INT", "DINT" and "REAL". + * "DINT" and "REAL". + * @return false|array Success flag or array of data. + * @throws \Exception + */ + public function readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes) + { + $this->status .= "readWriteRegisters: START\n"; + // connect + $this->connect(); + // send FC23 + $packet = $this->readWriteRegistersPacketBuilder( + $unitId, $referenceRead, $quantity, $referenceWrite, $data, + $dataTypes + ); + $this->status .= $this->printPacket($packet); + $this->send($packet); + // receive response + $rpacket = $this->rec(); + $this->status .= $this->printPacket($rpacket); + // parse packet + $receivedData = $this->readWriteRegistersParser($rpacket); + // disconnect + $this->disconnect(); + $this->status .= "writeMultipleRegister: DONE\n"; + // return + return $receivedData; + } + + /** + * readWriteRegistersPacketBuilder + * + * Packet FC23 builder - READ WRITE registers + * + * @param int $unitId + * @param int $referenceRead + * @param int $quantity + * @param int $referenceWrite + * @param array $data + * @param array $dataTypes + * @return string + */ + private function readWriteRegistersPacketBuilder( + $unitId, + $referenceRead, + $quantity, + $referenceWrite, + $data, + $dataTypes + ) { + + $dataLen = 0; + // build data section + $buffer1 = ''; + foreach ($data as $key => $dataitem) { + if ($dataTypes[$key] === 'INT') { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + } elseif ($dataTypes[$key] === 'DINT') { + $buffer1 .= IecType::iecDINT($dataitem, $this->endianness); // register values x + $dataLen += 4; + } elseif ($dataTypes[$key] === 'REAL') { + $buffer1 .= IecType::iecREAL($dataitem, $this->endianness); // register values x + $dataLen += 4; + } else { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + } + } + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(23); // FC 23 = 23(0x17) + // build body - read section + $buffer2 .= IecType::iecINT($referenceRead); // refnumber = 12288 + $buffer2 .= IecType::iecINT($quantity); // quantity + // build body - write section + $buffer2 .= IecType::iecINT($referenceWrite); // refnumber = 12288 + $buffer2 .= IecType::iecINT($dataLen / 2); // word count + $buffer2 .= IecType::iecBYTE($dataLen); // byte count + $dataLen += 10; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * readWriteRegistersParser + * + * FC23 response parser + * + * @param string $packet + * @return array|false + * @throws \Exception + */ + private function readWriteRegistersParser($packet) + { + $data = array(); + // if not exception + if (!$this->responseCode($packet)) { + return false; + } + // get data + for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { + $data[$i] = ord($packet[9 + $i]); + } + return $data; + } + + /** + * Set data receive timeout. + * Writes property timeout_sec + * + * @param float $seconds seconds + */ + public function setTimeout($seconds) + { + $this->timeout_sec = $seconds; + } + + /** + * Set socket read/write timeout. Null = no change. + * + * @param float|null $read_timeout_sec data read timeout (seconds, default 0.3) + * @param float|null $write_timeout_sec data write timeout (seconds, default 1.0) + * @internal param float $seconds seconds + */ + public function setSocketTimeout($read_timeout_sec, $write_timeout_sec) + { + // Set read timeout if given + if ($read_timeout_sec !== null) { + $this->socket_read_timeout_sec = $read_timeout_sec; + } + + // Set write timeout if given + if ($write_timeout_sec !== null) { + $this->socket_write_timeout_sec = $write_timeout_sec; + } + } } diff --git a/src/ModbusMasterTcp.php b/src/ModbusMasterTcp.php index e524af8..1296fac 100644 --- a/src/ModbusMasterTcp.php +++ b/src/ModbusMasterTcp.php @@ -8,14 +8,12 @@ namespace PHPModbus; * This source file is subject to the "PhpModbus license" that is bundled * with this package in the file license.txt. * - * - * @copyright Copyright (c) 2004, 2012 Jan Krakora - * @license PhpModbus license - * @category Phpmodbus - * @tutorial Phpmodbus.pkg - * @package Phpmodbus - * @version $id$ - * + * @copyright Copyright (c) 2004, 2012 Jan Krakora + * @license PhpModbus license + * @category Phpmodbus + * @tutorial Phpmodbus.pkg + * @package Phpmodbus + * @version $id$ */ /** @@ -23,29 +21,21 @@ namespace PHPModbus; * * This class deals with the MODBUS master using TCP. Extends ModbusMaster class. * - * Implemented MODBUS functions: - * - FC 1: read coils - * - FC 3: read multiple registers - * - FC 15: write multiple coils - * - FC 16: write multiple registers - * - FC 23: read write registers - * - * @author Jan Krakora - * @copyright Copyright (c) 2004, 2012 Jan Krakora - * @package Phpmodbus - * + * @author Jan Krakora + * @copyright Copyright (c) 2004, 2012 Jan Krakora + * @package Phpmodbus */ class ModbusMasterTcp extends ModbusMaster { - /** - * ModbusMasterTcp - * - * This is the constructor that defines {@link $host} IP address of the object. - * - * @param String $host An IP address of a Modbus TCP device. E.g. "192.168.1.1". - */ - public function __construct($host) - { - parent::__construct($host, "TCP"); - } + /** + * ModbusMasterTcp + * + * This is the constructor that defines {@link $host} IP address of the object. + * + * @param String $host An IP address of a Modbus TCP device. E.g. "192.168.1.1". + */ + public function __construct($host) + { + parent::__construct($host, "TCP"); + } } diff --git a/src/ModbusMasterUdp.php b/src/ModbusMasterUdp.php index 159244f..59beac8 100644 --- a/src/ModbusMasterUdp.php +++ b/src/ModbusMasterUdp.php @@ -8,14 +8,12 @@ namespace PHPModbus; * This source file is subject to the "PhpModbus license" that is bundled * with this package in the file license.txt. * - * - * @copyright Copyright (c) 2004, 2012 Jan Krakora - * @license PhpModbus license - * @category Phpmodbus - * @tutorial Phpmodbus.pkg - * @package Phpmodbus - * @version $id$ - * + * @copyright Copyright (c) 2004, 2012 Jan Krakora + * @license PhpModbus license + * @category Phpmodbus + * @tutorial Phpmodbus.pkg + * @package Phpmodbus + * @version $id$ */ /** @@ -23,29 +21,21 @@ namespace PHPModbus; * * This class deals with the MODBUS master using UDP stack. * - * Implemented MODBUS master functions: - * - FC 1: read coils - * - FC 3: read multiple registers - * - FC 15: write multiple coils - * - FC 16: write multiple registers - * - FC 23: read write registers - * - * @author Jan Krakora - * @copyright Copyright (c) 2004, 2012 Jan Krakora - * @package Phpmodbus - * + * @author Jan Krakora + * @copyright Copyright (c) 2004, 2012 Jan Krakora + * @package Phpmodbus */ class ModbusMasterUdp extends ModbusMaster { - /** - * ModbusMasterUdp - * - * This is the constructor that defines {@link $host} IP address of the object. - * - * @param String $host An IP address of a Modbus UDP device. E.g. "192.168.1.1". - */ - public function __construct($host) - { - parent::__construct($host, "UDP"); - } + /** + * ModbusMasterUdp + * + * This is the constructor that defines {@link $host} IP address of the object. + * + * @param String $host An IP address of a Modbus UDP device. E.g. "192.168.1.1". + */ + public function __construct($host) + { + parent::__construct($host, "UDP"); + } } diff --git a/src/PhpType.php b/src/PhpType.php index fbe58a7..51d98ff 100644 --- a/src/PhpType.php +++ b/src/PhpType.php @@ -1,6 +1,7 @@ 0)) { - $int = 0xFFFF8000 | $int; - } - // Convert the value - return (int)self::dword2signedInt($int); - } + /** + * bytes2signedInt + * + * The function converts array of 2 or 4 bytes to signed integer. + * The return value depends on order of the input bytes (endianning). + * + * @param array $values + * @param bool $bigEndian + * @return int + */ + public static function bytes2signedInt($values, $bigEndian = 0) + { + $data = array(); + $int = 0; + // Set the array to correct form + $data = self::checkData($values); + // Combine bytes + $int = self::combineBytes($data, $bigEndian); + // In the case of signed 2 byte value convert it to 4 byte one + if ((count($values) === 2) && ((0x8000 & $int) > 0)) { + $int = 0xFFFF8000 | $int; + } + // Convert the value + return (int)self::dword2signedInt($int); + } - /** - * bytes2unsignedInt - * - * The function converts array of 2 or 4 bytes to unsigned integer. - * The return value depends on order of the input bytes (endianning). - * - * @param array $values - * @param bool $bigEndian - * @return int|float - */ - public static function bytes2unsignedInt($values, $bigEndian = 0) - { - $data = array(); - $int = 0; - // Set the array to correct form - $data = self::checkData($values); - // Combine bytes - $int = self::combineBytes($data, $bigEndian); - // Convert the value - return self::dword2unsignedInt($int); - } + /** + * bytes2unsignedInt + * + * The function converts array of 2 or 4 bytes to unsigned integer. + * The return value depends on order of the input bytes (endianning). + * + * @param array $values + * @param bool $bigEndian + * @return int|float + */ + public static function bytes2unsignedInt($values, $bigEndian = 0) + { + $data = array(); + $int = 0; + // Set the array to correct form + $data = self::checkData($values); + // Combine bytes + $int = self::combineBytes($data, $bigEndian); + // Convert the value + return self::dword2unsignedInt($int); + } - /** - * bytes2string - * - * The function converts an values array to the string. The function detects - * the end of the string by 0x00 character as defined by string standards. - * - * @param array $values - * @param bool $bigEndian - * @return string - */ - public static function bytes2string($values, $bigEndian = 0) - { - // Prepare string variable - $str = ""; - // Parse the received data word array - for ($i = 0; $i < count($values); $i += 2) { - if ($bigEndian) { - if ($values[$i] != 0) { - $str .= chr($values[$i]); - } else { - break; - } - if ($values[$i + 1] != 0) { - $str .= chr($values[$i + 1]); - } else { - break; - } - } else { - if ($values[$i + 1] != 0) { - $str .= chr($values[$i + 1]); - } else { - break; - } - if ($values[$i] != 0) { - $str .= chr($values[$i]); - } else { - break; - } - } - } - // return string - return $str; - } + /** + * bytes2string + * + * The function converts an values array to the string. The function detects + * the end of the string by 0x00 character as defined by string standards. + * + * @param array $values + * @param bool $bigEndian + * @return string + */ + public static function bytes2string($values, $bigEndian = 0) + { + // Prepare string variable + $str = ""; + // Parse the received data word array + for ($i = 0; $i < count($values); $i += 2) { + if ($bigEndian) { + if ($values[$i] != 0) { + $str .= chr($values[$i]); + } else { + break; + } + if ($values[$i + 1] != 0) { + $str .= chr($values[$i + 1]); + } else { + break; + } + } else { + if ($values[$i + 1] != 0) { + $str .= chr($values[$i + 1]); + } else { + break; + } + if ($values[$i] != 0) { + $str .= chr($values[$i]); + } else { + break; + } + } + } + // return string + return $str; + } - /** - * real2float - * - * This function converts a value in IEC-1131 REAL single precision form to float. - * - * For more see [{@link http://en.wikipedia.org/wiki/Single_precision Single precision on Wiki}] or - * [{@link http://de.php.net/manual/en/function.base-convert.php PHP base_convert function commentary}, Todd Stokes - * @ Georgia Tech 21-Nov-2007] or - * [{@link http://www.php.net/manual/en/function.pack.php PHP pack/unpack functionality}] - * - * @param int $value in IEC REAL data type to be converted - * @return float float value - */ - private static function real2float($value) - { - // get unsigned long - $ulong = pack("L", $value); - // set float - $float = unpack("f", $ulong); + /** + * real2float + * + * This function converts a value in IEC-1131 REAL single precision form to float. + * + * For more see [{@link http://en.wikipedia.org/wiki/Single_precision Single precision on Wiki}] or + * [{@link http://de.php.net/manual/en/function.base-convert.php PHP base_convert function commentary}, Todd Stokes + * @ Georgia Tech 21-Nov-2007] or + * [{@link http://www.php.net/manual/en/function.pack.php PHP pack/unpack functionality}] + * + * @param int $value in IEC REAL data type to be converted + * @return float float value + */ + private static function real2float($value) + { + // get unsigned long + $ulong = pack('L', $value); + // set float + $float = unpack('f', $ulong); - return $float[1]; - } + return $float[1]; + } - /** - * dword2signedInt - * - * Switch double word to signed integer - * - * @param int $value - * @return int - */ - private static function dword2signedInt($value) - { - if ((0x80000000 & $value) != 0) { - return -(0x7FFFFFFF & ~$value) - 1; - } else { - return (0x7FFFFFFF & $value); - } - } + /** + * dword2signedInt + * + * Switch double word to signed integer + * + * @param int $value + * @return int + */ + private static function dword2signedInt($value) + { + if ((0x80000000 & $value) !== 0) { + return -(0x7FFFFFFF & ~$value) - 1; + } else { + return (0x7FFFFFFF & $value); + } + } - /** - * dword2signedInt - * - * Switch double word to unsigned integer - * - * @param int $value - * @return int|float - */ - private static function dword2unsignedInt($value) - { - if ((0x80000000 & $value) != 0) { - return ((float)(0x7FFFFFFF & $value)) + 2147483648; - } else { - return (int)(0x7FFFFFFF & $value); - } - } + /** + * dword2signedInt + * + * Switch double word to unsigned integer + * + * @param int $value + * @return int|float + */ + private static function dword2unsignedInt($value) + { + if ((0x80000000 & $value) !== 0) { + return ((float)(0x7FFFFFFF & $value)) + 2147483648; + } else { + return (int)(0x7FFFFFFF & $value); + } + } - /** - * checkData - * - * Check if the data variable is array, and check if the values are numeric - * - * @param int[] $data - * @return int - * @throws Exception - */ - private static function checkData($data) - { - // Check the data - if (!is_array($data) || - count($data) < 2 || - count($data) > 4 || - count($data) == 3 - ) { - throw new Exception('The input data should be an array of 2 or 4 bytes.'); - } - // Fill the rest of array by zeroes - if (count($data) == 2) { - $data[2] = 0; - $data[3] = 0; - } - // Check the values to be number - if (!is_numeric($data[0]) || - !is_numeric($data[1]) || - !is_numeric($data[2]) || - !is_numeric($data[3]) - ) { - throw new Exception('Data are not numeric or the array keys are not indexed by 0,1,2 and 3'); - } + /** + * checkData + * + * Check if the data variable is array, and check if the values are numeric + * + * @param int[] $data + * @return array|\int[] + * @throws Exception + */ + private static function checkData($data) + { + // Check the data + $count = count($data); + if (!is_array($data) + || $count < 2 + || $count > 4 + || $count === 3 + ) { + throw new Exception('The input data should be an array of 2 or 4 bytes.'); + } + // Fill the rest of array by zeroes + if ($count === 2) { + $data[2] = 0; + $data[3] = 0; + } + // Check the values to be number + if (!is_numeric($data[0]) + || !is_numeric($data[1]) + || !is_numeric($data[2]) + || !is_numeric($data[3]) + ) { + throw new Exception('Data are not numeric or the array keys are not indexed by 0,1,2 and 3'); + } - return $data; - } + return $data; + } - /** - * combineBytes - * - * Combine bytes together - * - * @param int $data - * @param bool $bigEndian - * @return int - */ - private static function combineBytes($data, $bigEndian) - { - $value = 0; - // Combine bytes - if ($bigEndian == 0) { - $value = (($data[3] & 0xFF) << 16) | - (($data[2] & 0xFF) << 24) | - (($data[1] & 0xFF)) | - (($data[0] & 0xFF) << 8); - } else { - $value = (($data[3] & 0xFF) << 24) | - (($data[2] & 0xFF) << 16) | - (($data[1] & 0xFF) << 8) | - (($data[0] & 0xFF)); - } + /** + * combineBytes + * + * Combine bytes together + * + * @param int $data + * @param bool $bigEndian + * @return int + */ + private static function combineBytes($data, $bigEndian) + { + $value = 0; + // Combine bytes + if ($bigEndian == 0) { + $value = (($data[3] & 0xFF) << 16) | + (($data[2] & 0xFF) << 24) | + ($data[1] & 0xFF) | + (($data[0] & 0xFF) << 8); + } else { + $value = (($data[3] & 0xFF) << 24) | + (($data[2] & 0xFF) << 16) | + (($data[1] & 0xFF) << 8) | + ($data[0] & 0xFF); + } - return $value; - } + return $value; + } } From 1e1c8ee5cec39da345850ebe787745b17597ff9f Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sat, 26 Nov 2016 23:13:51 +0200 Subject: [PATCH 03/17] refactor socket send and receive to separate method to reduce code duplication --- src/ModbusMaster.php | 403 +++++++++++++++++++++---------------------- 1 file changed, 197 insertions(+), 206 deletions(-) diff --git a/src/ModbusMaster.php b/src/ModbusMaster.php index 8e68a21..e5c5a84 100644 --- a/src/ModbusMaster.php +++ b/src/ModbusMaster.php @@ -43,64 +43,64 @@ use Exception; class ModbusMaster { /** -* * - * @var string Modbus device IP address -*/ + * + * @var string Modbus device IP address + */ public $host = '192.168.1.1'; /** -* * - * @var string gateway port -*/ + * + * @var string gateway port + */ public $port = 502; /** - * @var string (optional) client IP address when binding client -*/ + * @var string (optional) client IP address when binding client + */ public $client = ''; /** -* * - * @var string client port set when binding client to local ip&port -*/ + * + * @var string client port set when binding client to local ip&port + */ public $client_port = 502; /** -* * - * @var string ModbusMaster status messages (echo for debugging) -*/ + * + * @var string ModbusMaster status messages (echo for debugging) + */ public $status; /** -* * - * @var float Total response timeout (seconds, decimals allowed) -*/ + * + * @var float Total response timeout (seconds, decimals allowed) + */ public $timeout_sec = 5; /** - * @var float Socket read timeout (seconds, decimals allowed) -*/ + * @var float Socket read timeout (seconds, decimals allowed) + */ public $socket_read_timeout_sec = 0.3; /** -* * - * @var float Socket write timeout (seconds, decimals allowed) -*/ + * + * @var float Socket write timeout (seconds, decimals allowed) + */ public $socket_write_timeout_sec = 1; // 300 ms /** - * @var int Endianness codding (0 = little endian = 0, 1 = big endian) -*/ + * @var int Endianness codding (0 = little endian = 0, 1 = big endian) + */ public $endianness = 0; /** -* * - * @var string Socket protocol (TCP, UDP) -*/ + * + * @var string Socket protocol (TCP, UDP) + */ public $socket_protocol = 'UDP'; // /** -* * - * @var resource Communication socket -*/ + * + * @var resource Communication socket + */ private $sock; /** @@ -108,7 +108,7 @@ class ModbusMaster * * This is the constructor that defines {@link $host} IP address of the object. * - * @param String $host An IP address of a Modbus TCP device. E.g. "192.168.1.1" + * @param String $host An IP address of a Modbus TCP device. E.g. "192.168.1.1" * @param String $protocol Socket protocol (TCP, UDP) */ public function __construct($host, $protocol) @@ -161,21 +161,17 @@ class ModbusMaster public function readCoils($unitId, $reference, $quantity) { $this->status .= "readCoils: START\n"; - // connect - $this->connect(); - // send FC 1 - $packet = $this->readCoilsPacketBuilder($unitId, $reference, $quantity); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readCoilsParser($rpacket, $quantity); - // disconnect - $this->disconnect(); + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $reference, $quantity) { + return $this->readCoilsPacketBuilder($unitId, $reference, $quantity); + }, + function ($data) use ($quantity) { + return $this->readCoilsParser($data, $quantity); + } + ); + $this->status .= "readCoils: DONE\n"; - // return return $receivedData; } @@ -337,7 +333,7 @@ class ModbusMaster $readsocks[] = $this->sock; $writesocks = null; $exceptsocks = null; - $rec = ""; + $rec = ''; $totalReadTimeout = $this->timeout_sec; $lastAccess = microtime(true); $readTout = $this->secsToSecUsecArray($this->socket_read_timeout_sec); @@ -451,10 +447,21 @@ class ModbusMaster * * Disconnect the socket */ - private function disconnect() + protected function disconnect() { - socket_close($this->sock); - $this->status .= "Disconnected\n"; + if (is_resource($this->sock)) { + socket_close($this->sock); + $this->status .= "Disconnected\n"; + } + } + + + /** + * Close socket it still open + */ + public function __destruct() + { + $this->disconnect(); } /** @@ -491,21 +498,17 @@ class ModbusMaster public function readInputDiscretes($unitId, $reference, $quantity) { $this->status .= "readInputDiscretes: START\n"; - // connect - $this->connect(); - // send FC 2 - $packet = $this->readInputDiscretesPacketBuilder($unitId, $reference, $quantity); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readInputDiscretesParser($rpacket, $quantity); - // disconnect - $this->disconnect(); + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $reference, $quantity) { + return $this->readInputDiscretesPacketBuilder($unitId, $reference, $quantity); + }, + function ($data) use ($quantity) { + return $this->readInputDiscretesParser($data, $quantity); + } + ); + $this->status .= "readInputDiscretes: DONE\n"; - // return return $receivedData; } @@ -591,21 +594,17 @@ class ModbusMaster public function readMultipleRegisters($unitId, $reference, $quantity) { $this->status .= "readMultipleRegisters: START\n"; - // connect - $this->connect(); - // send FC 3 - $packet = $this->readMultipleRegistersPacketBuilder($unitId, $reference, $quantity); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readMultipleRegistersParser($rpacket); - // disconnect - $this->disconnect(); + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $reference, $quantity) { + return $this->readMultipleRegistersPacketBuilder($unitId, $reference, $quantity); + }, + function ($data) { + return $this->readMultipleRegistersParser($data); + } + ); + $this->status .= "readMultipleRegisters: DONE\n"; - // return return $receivedData; } @@ -696,21 +695,17 @@ class ModbusMaster public function readMultipleInputRegisters($unitId, $reference, $quantity) { $this->status .= "readMultipleInputRegisters: START\n"; - // connect - $this->connect(); - // send FC 4 - $packet = $this->readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readMultipleInputRegistersParser($rpacket); - // disconnect - $this->disconnect(); + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $reference, $quantity) { + return $this->readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity); + }, + function ($data) { + return $this->readMultipleInputRegistersParser($data); + } + ); + $this->status .= "readMultipleInputRegisters: DONE\n"; - // return return $receivedData; } @@ -778,7 +773,7 @@ class ModbusMaster * @return bool * @throws \Exception */ - public function fc5($unitId, $reference, $data) + public function fc5($unitId, $reference, array $data) { return $this->writeSingleCoil($unitId, $reference, $data); } @@ -798,24 +793,21 @@ class ModbusMaster * @return bool Success flag * @throws \Exception */ - public function writeSingleCoil($unitId, $reference, $data) + public function writeSingleCoil($unitId, $reference, array $data) { $this->status .= "writeSingleCoil: START\n"; - // connect - $this->connect(); - // send FC5 - $packet = $this->writeSingleCoilPacketBuilder($unitId, $reference, $data); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->writeSingleCoilParser($rpacket); - // disconnect - $this->disconnect(); + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $reference, $data) { + return $this->writeSingleCoilPacketBuilder($unitId, $reference, $data); + }, + function ($data) { + return $this->writeSingleCoilParser($data); + } + ); + $this->status .= "writeSingleCoil: DONE\n"; - return true; + return $receivedData; } /** @@ -823,12 +815,12 @@ class ModbusMaster * * Packet builder FC5 - WRITE single register * - * @param int $unitId - * @param int $reference + * @param int $unitId + * @param int $reference * @param array $data * @return string */ - private function writeSingleCoilPacketBuilder($unitId, $reference, $data) + private function writeSingleCoilPacketBuilder($unitId, $reference, array $data) { $dataLen = 0; // build data section @@ -883,7 +875,7 @@ class ModbusMaster * @return bool * @throws \Exception */ - public function fc6($unitId, $reference, $data) + public function fc6($unitId, $reference, array $data) { return $this->writeSingleRegister($unitId, $reference, $data); } @@ -903,24 +895,36 @@ class ModbusMaster * @return bool Success flag * @throws \Exception */ - public function writeSingleRegister($unitId, $reference, $data) + public function writeSingleRegister($unitId, $reference, array $data) { $this->status .= "writeSingleRegister: START\n"; - // connect - $this->connect(); - // send FC6 - $packet = $this->writeSingleRegisterPacketBuilder($unitId, $reference, $data); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->writeSingleRegisterParser($rpacket); - // disconnect - $this->disconnect(); + $result = $this->sendAndReceive( + function () use ($unitId, $reference, $data) { + return $this->writeSingleRegisterPacketBuilder($unitId, $reference, $data); + }, + function ($data) { + return $this->writeSingleRegisterParser($data); + } + ); $this->status .= "writeSingleRegister: DONE\n"; - return true; + return $result; + } + + public function sendAndReceive(callable $buildRequest, callable $parseResponse) + { + try { + $this->connect(); + $packet = $buildRequest(); + + $this->send($packet); + $data = $this->rec(); + + $this->status .= $this->printPacket($data); + return $parseResponse($data); + } finally { + $this->disconnect(); + } + } /** @@ -928,8 +932,8 @@ class ModbusMaster * * Packet builder FC6 - WRITE single register * - * @param int $unitId - * @param int $reference + * @param int $unitId + * @param int $reference * @param array $data * @return string */ @@ -985,7 +989,7 @@ class ModbusMaster * @return bool * @throws \Exception */ - public function fc15($unitId, $reference, $data) + public function fc15($unitId, $reference, array $data) { return $this->writeMultipleCoils($unitId, $reference, $data); } @@ -1004,24 +1008,21 @@ class ModbusMaster * @return bool * @throws \Exception */ - public function writeMultipleCoils($unitId, $reference, $data) + public function writeMultipleCoils($unitId, $reference, array $data) { $this->status .= "writeMultipleCoils: START\n"; - // connect - $this->connect(); - // send FC15 - $packet = $this->writeMultipleCoilsPacketBuilder($unitId, $reference, $data); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->writeMultipleCoilsParser($rpacket); - // disconnect - $this->disconnect(); + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $reference, $data) { + return $this->writeMultipleCoilsPacketBuilder($unitId, $reference, $data); + }, + function ($data) { + return $this->writeMultipleCoilsParser($data); + } + ); + $this->status .= "writeMultipleCoils: DONE\n"; - return true; + return $receivedData; } /** @@ -1029,8 +1030,8 @@ class ModbusMaster * * Packet builder FC15 - Write multiple coils * - * @param int $unitId - * @param int $reference + * @param int $unitId + * @param int $reference * @param array $data * @return string */ @@ -1109,7 +1110,7 @@ class ModbusMaster * @return bool * @throws \Exception */ - public function fc16($unitId, $reference, $data, $dataTypes) + public function fc16($unitId, $reference, array $data, array $dataTypes) { return $this->writeMultipleRegister($unitId, $reference, $data, $dataTypes); } @@ -1131,24 +1132,21 @@ class ModbusMaster * @return bool Success flag * @throws \Exception */ - public function writeMultipleRegister($unitId, $reference, $data, $dataTypes) + public function writeMultipleRegister($unitId, $reference, array $data, array $dataTypes) { $this->status .= "writeMultipleRegister: START\n"; - // connect - $this->connect(); - // send FC16 - $packet = $this->writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->writeMultipleRegisterParser($rpacket); - // disconnect - $this->disconnect(); + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $reference, $data, $dataTypes) { + return $this->writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes); + }, + function ($data) { + return $this->writeMultipleRegisterParser($data); + } + ); + $this->status .= "writeMultipleRegister: DONE\n"; - return true; + return $receivedData; } /** @@ -1157,13 +1155,13 @@ class ModbusMaster * Packet builder FC16 - WRITE multiple register * e.g.: 4dd90000000d0010300000030603e807d00bb8 * - * @param int $unitId - * @param int $reference + * @param int $unitId + * @param int $reference * @param array $data * @param array $dataTypes * @return string */ - private function writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes) + private function writeMultipleRegisterPacketBuilder($unitId, $reference, array $data, array $dataTypes) { $dataLen = 0; // build data section @@ -1254,21 +1252,18 @@ class ModbusMaster public function maskWriteRegister($unitId, $reference, $andMask, $orMask) { $this->status .= "maskWriteRegister: START\n"; - // connect - $this->connect(); - // send FC22 - $packet = $this->maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $this->maskWriteRegisterParser($rpacket); - // disconnect - $this->disconnect(); + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $reference, $andMask, $orMask) { + return $this->maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask); + }, + function ($data) { + return $this->maskWriteRegisterParser($data); + } + ); + $this->status .= "maskWriteRegister: DONE\n"; - return true; + return $receivedData; } /** @@ -1333,7 +1328,7 @@ class ModbusMaster * @return false|array * @throws \Exception */ - public function fc23($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes) + public function fc23($unitId, $referenceRead, $quantity, $referenceWrite, array $data, array $dataTypes) { return $this->readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes); } @@ -1358,27 +1353,22 @@ class ModbusMaster * @return false|array Success flag or array of data. * @throws \Exception */ - public function readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes) + public function readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, array $data, array $dataTypes) { $this->status .= "readWriteRegisters: START\n"; - // connect - $this->connect(); - // send FC23 - $packet = $this->readWriteRegistersPacketBuilder( - $unitId, $referenceRead, $quantity, $referenceWrite, $data, - $dataTypes + + $receivedData = $this->sendAndReceive( + function () use ($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes) { + return $this->readWriteRegistersPacketBuilder( + $unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes + ); + }, + function ($data) { + return $this->readWriteRegistersParser($data); + } ); - $this->status .= $this->printPacket($packet); - $this->send($packet); - // receive response - $rpacket = $this->rec(); - $this->status .= $this->printPacket($rpacket); - // parse packet - $receivedData = $this->readWriteRegistersParser($rpacket); - // disconnect - $this->disconnect(); + $this->status .= "writeMultipleRegister: DONE\n"; - // return return $receivedData; } @@ -1387,10 +1377,10 @@ class ModbusMaster * * Packet FC23 builder - READ WRITE registers * - * @param int $unitId - * @param int $referenceRead - * @param int $quantity - * @param int $referenceWrite + * @param int $unitId + * @param int $referenceRead + * @param int $quantity + * @param int $referenceWrite * @param array $data * @param array $dataTypes * @return string @@ -1400,10 +1390,11 @@ class ModbusMaster $referenceRead, $quantity, $referenceWrite, - $data, - $dataTypes - ) { - + array $data, + array $dataTypes + ) + { + $dataLen = 0; // build data section $buffer1 = ''; @@ -1481,7 +1472,7 @@ class ModbusMaster /** * Set socket read/write timeout. Null = no change. * - * @param float|null $read_timeout_sec data read timeout (seconds, default 0.3) + * @param float|null $read_timeout_sec data read timeout (seconds, default 0.3) * @param float|null $write_timeout_sec data write timeout (seconds, default 1.0) * @internal param float $seconds seconds */ From 8637a1957222fb978ad1df6e7390c5cbb41ba545 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sat, 26 Nov 2016 23:34:22 +0200 Subject: [PATCH 04/17] refactor thrown exceptions to be more specific than just Exception class --- src/IOException.php | 11 +++++++++++ src/ModbusException.php | 10 ++++++++++ src/ModbusMaster.php | 11 ++++++----- tests/ModbusMaster/ModbusExceptionTest.php | 11 +++++++---- 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 src/IOException.php create mode 100644 src/ModbusException.php diff --git a/src/IOException.php b/src/IOException.php new file mode 100644 index 0000000..53e73a9 --- /dev/null +++ b/src/IOException.php @@ -0,0 +1,11 @@ +sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); } else { - throw new Exception("Unknown socket protocol, should be 'TCP' or 'UDP'"); + throw new InvalidArgumentException("Unknown socket protocol, should be 'TCP' or 'UDP'"); } // Bind the client socket to a specific local port if (strlen($this->client) > 0) { $result = socket_bind($this->sock, $this->client, $this->client_port); if ($result === false) { - throw new Exception( + throw new IOException( "socket_bind() failed. Reason: ($result)" . socket_strerror(socket_last_error($this->sock)) ); @@ -215,7 +216,7 @@ class ModbusMaster // Connect the socket $result = @socket_connect($this->sock, $this->host, $this->port); if ($result === false) { - throw new Exception( + throw new IOException( "socket_connect() failed. Reason: ($result)" . socket_strerror(socket_last_error($this->sock)) ); @@ -349,7 +350,7 @@ class ModbusMaster } else { $timeSpentWaiting = microtime(true) - $lastAccess; if ($timeSpentWaiting >= $totalReadTimeout) { - throw new Exception( + throw new IOException( "Watchdog time expired [ $totalReadTimeout sec ]!!! " . "Connection to $this->host:$this->port is not established." ); @@ -435,7 +436,7 @@ class ModbusMaster $failure_str = 'UNDEFINED FAILURE CODE'; } // exception response - throw new Exception("Modbus response error code: $failure_code ($failure_str)"); + throw new ModbusException("Modbus response error code: $failure_code ($failure_str)"); } else { $this->status .= "Modbus response error code: NOERROR\n"; return true; diff --git a/tests/ModbusMaster/ModbusExceptionTest.php b/tests/ModbusMaster/ModbusExceptionTest.php index 0d31651..f1b04fc 100644 --- a/tests/ModbusMaster/ModbusExceptionTest.php +++ b/tests/ModbusMaster/ModbusExceptionTest.php @@ -1,6 +1,9 @@ expectException(\Exception::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Unknown socket protocol, should be 'TCP' or 'UDP'"); $modbus = new ModbusMaster('127.0.0.1', 'Mismatch'); @@ -17,7 +20,7 @@ class ModbusExceptionTest extends MockServerTestCase public function testPortClosedException() { - $this->expectException(\Exception::class); + $this->expectException(IOException::class); $this->expectExceptionMessage('socket_connect() failed. Reason: ()No connection could be made because the target machine actively refused it.'); $modbus = new ModbusMasterTcp('127.0.0.1'); @@ -27,7 +30,7 @@ class ModbusExceptionTest extends MockServerTestCase public function testTimeoutException() { - $this->expectException(\Exception::class); + $this->expectException(\RuntimeException::class); $mockResponse = '89130000000400010101'; // respond with 1 byte (00000001 bits set) [1] static::executeWithMockServer($mockResponse, function ($port) { @@ -45,7 +48,7 @@ class ModbusExceptionTest extends MockServerTestCase public function testThrowIllegalDataValueException() { - $this->expectException(\Exception::class); + $this->expectException(ModbusException::class); $this->expectExceptionMessage('Modbus response error code: 3 (ILLEGAL DATA VALUE)'); $mockResponse = 'da8700000003008303'; // respond with 1 WORD (2 bytes) [0, 3] From 030f0d526b3920f96742e6f0085f7359c9ecfda3 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 11:08:42 +0200 Subject: [PATCH 05/17] Fix tests on Linux (Ubuntu 16.4/Php7) --- tests/ModbusMaster/MockServerTestCase.php | 5 +++++ tests/ModbusMaster/ModbusExceptionTest.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/ModbusMaster/MockServerTestCase.php b/tests/ModbusMaster/MockServerTestCase.php index b6e93fd..b22f309 100644 --- a/tests/ModbusMaster/MockServerTestCase.php +++ b/tests/ModbusMaster/MockServerTestCase.php @@ -23,6 +23,11 @@ abstract class MockServerTestCase extends TestCase $clientData[] = $output; }); + if ('WIN' !== strtoupper(substr(PHP_OS, 0, 3))) { + // wait to spin up. needed for linux. unnessecary on Windows 10. + // Ugly but even with 150ms sleep test run faster on Linux + usleep(150000); + } $closure($port); }); diff --git a/tests/ModbusMaster/ModbusExceptionTest.php b/tests/ModbusMaster/ModbusExceptionTest.php index f1b04fc..3d0db1f 100644 --- a/tests/ModbusMaster/ModbusExceptionTest.php +++ b/tests/ModbusMaster/ModbusExceptionTest.php @@ -21,7 +21,7 @@ class ModbusExceptionTest extends MockServerTestCase public function testPortClosedException() { $this->expectException(IOException::class); - $this->expectExceptionMessage('socket_connect() failed. Reason: ()No connection could be made because the target machine actively refused it.'); + $this->expectExceptionMessage('socket_connect() failed. Reason:'); $modbus = new ModbusMasterTcp('127.0.0.1'); $modbus->setSocketTimeout(0.2, 0.2); From f51e71cffded4cf68676b94e5538b8ea3f1bc26d Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 13:13:05 +0200 Subject: [PATCH 06/17] bump php version to 5.5+, update readme --- README.md | 14 ++++++++++++-- composer.json | 2 +- tests/ModbusMaster/MockServerTestCase.php | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f478cd8..2349b3b 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protocol using PHP. -**NOTE: This is a fork to fix & update the library code (and code alone). Notably, the tests are probably all broken.** +**NOTE: This is a fork to fix & update the library code (and code alone).** > **What's new** > -> This fork adds a namespace and fixes issues encountered when porting to PHP 7 +> This fork adds a namespace and fixes issues encountered when porting to PHP 7, fixes old MS Windows specific tests ## Implemented features @@ -26,6 +26,7 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protoc ## Requirements * The PHP extension php_sockets.dll should be enabled (server php.ini file) + * PHP 5.5+ ## Example @@ -53,6 +54,15 @@ Use the `setTimeout($seconds)` and `setSocketTimeout($read_timeout_sec, $write_t Most of the code is (to some extent) commented and documented with PhpDoc. You should get useful tooltips in your IDE. +## Tests + +To run the test suite, you need install the dependencies via composer, then +run PHPUnit. + +NB: PHP 5.6+ is required for tests + + composer install + vendor/bin/phpunit ## GoogleCode legacy docs & downloads diff --git a/composer.json b/composer.json index d52d076..f643d4a 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "description": "PhpModbus with namespaces and updated to PHP 7", "license": "LGPL", "require": { - "php": "^5.3.2 || ^7.0", + "php": "^5.5 || ^7.0", "ext-sockets": "*" }, "require-dev": { diff --git a/tests/ModbusMaster/MockServerTestCase.php b/tests/ModbusMaster/MockServerTestCase.php index b22f309..43d902f 100644 --- a/tests/ModbusMaster/MockServerTestCase.php +++ b/tests/ModbusMaster/MockServerTestCase.php @@ -23,7 +23,7 @@ abstract class MockServerTestCase extends TestCase $clientData[] = $output; }); - if ('WIN' !== strtoupper(substr(PHP_OS, 0, 3))) { + if (strpos(PHP_OS, 'WIN') === false) { // wait to spin up. needed for linux. unnessecary on Windows 10. // Ugly but even with 150ms sleep test run faster on Linux usleep(150000); From d91e37a29064b0755d6fefa86794de9c6e840224 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 15:49:41 +0200 Subject: [PATCH 07/17] refactor thrown exceptions to be more specific than just Exception class --- .travis.yml | 25 +++++++++++++++++++++++++ composer.json | 5 +++++ phpunit.xml | 11 ----------- 3 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b55c9b9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache + +php: + - 5.6 + - 7.0 + +branches: + only: + - /^dev-.*$/ + - phpunit-support + +before_install: + - travis_retry composer self-update + +install: + - travis_retry composer update ${COMPOSER_FLAGS} --prefer-source --no-interaction + +script: + - composer test-ci \ No newline at end of file diff --git a/composer.json b/composer.json index f643d4a..012daf7 100644 --- a/composer.json +++ b/composer.json @@ -32,5 +32,10 @@ "psr-4": { "Tests\\": "tests/" } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-clover report/coverage.xml", + "test-coverage": "vendor/bin/phpunit --coverage-html report/html" } } diff --git a/phpunit.xml b/phpunit.xml index fb3d442..891c4ea 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -15,17 +15,6 @@ ./tests/* - - - - - src From f4bd2dee3ccb46ae18541264eb399b73bb4a1434 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 15:58:08 +0200 Subject: [PATCH 08/17] clean travis conf a bit --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b55c9b9..90e8fde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ before_install: - travis_retry composer self-update install: - - travis_retry composer update ${COMPOSER_FLAGS} --prefer-source --no-interaction + - travis_retry composer update --no-interaction script: - composer test-ci \ No newline at end of file From 5e8b3159a3a38a20ed945a8ec93fac6fe3fe364a Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 16:07:15 +0200 Subject: [PATCH 09/17] add timeout env variable for tests with slow/heavy load machines (travisCI) --- tests/ModbusMaster/MockServerTestCase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ModbusMaster/MockServerTestCase.php b/tests/ModbusMaster/MockServerTestCase.php index 43d902f..ba80b51 100644 --- a/tests/ModbusMaster/MockServerTestCase.php +++ b/tests/ModbusMaster/MockServerTestCase.php @@ -23,10 +23,10 @@ abstract class MockServerTestCase extends TestCase $clientData[] = $output; }); - if (strpos(PHP_OS, 'WIN') === false) { + if (strpos(PHP_OS, 'WIN') === false || getenv('MOCKSERVER_TIMEOUT_USEC') !== false) { // wait to spin up. needed for linux. unnessecary on Windows 10. // Ugly but even with 150ms sleep test run faster on Linux - usleep(150000); + usleep(getenv('MOCKSERVER_TIMEOUT_USEC') ?: 150000); } $closure($port); }); From c7c4431b1c9d4d8e2573cb10215062bf66cf968e Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 18:43:09 +0200 Subject: [PATCH 10/17] refactor all packet related or socket related code to separate classes --- src/ModbusMaster.php | 884 ++---------------- src/ModbusSocket.php | 325 +++++++ src/Packet/MaskWriteRegisterPacket.php | 65 ++ src/Packet/ReadCoilsPacket.php | 88 ++ src/Packet/ReadInputDiscretesPacket.php | 64 ++ .../ReadMultipleInputRegistersPacket.php | 68 ++ src/Packet/ReadMultipleRegistersPacket.php | 67 ++ src/Packet/ReadWriteRegistersPacket.php | 102 ++ src/Packet/WriteMultipleCoilsPacket.php | 89 ++ src/Packet/WriteMultipleRegisterPacket.php | 85 ++ src/Packet/WriteSingleCoilPacket.php | 70 ++ src/Packet/WriteSingleRegisterPacket.php | 68 ++ tests/ModbusMaster/UdpFc1ReadCoilsTest.php | 2 +- 13 files changed, 1154 insertions(+), 823 deletions(-) create mode 100644 src/ModbusSocket.php create mode 100644 src/Packet/MaskWriteRegisterPacket.php create mode 100644 src/Packet/ReadCoilsPacket.php create mode 100644 src/Packet/ReadInputDiscretesPacket.php create mode 100644 src/Packet/ReadMultipleInputRegistersPacket.php create mode 100644 src/Packet/ReadMultipleRegistersPacket.php create mode 100644 src/Packet/ReadWriteRegistersPacket.php create mode 100644 src/Packet/WriteMultipleCoilsPacket.php create mode 100644 src/Packet/WriteMultipleRegisterPacket.php create mode 100644 src/Packet/WriteSingleCoilPacket.php create mode 100644 src/Packet/WriteSingleRegisterPacket.php diff --git a/src/ModbusMaster.php b/src/ModbusMaster.php index 801fdeb..95f2fc3 100644 --- a/src/ModbusMaster.php +++ b/src/ModbusMaster.php @@ -3,7 +3,16 @@ namespace PHPModbus; use Exception; -use InvalidArgumentException; +use PHPModbus\Packet\MaskWriteRegisterPacket; +use PHPModbus\Packet\ReadCoilsPacket; +use PHPModbus\Packet\ReadInputDiscretesPacket; +use PHPModbus\Packet\ReadMultipleInputRegistersPacket; +use PHPModbus\Packet\ReadMultipleRegistersPacket; +use PHPModbus\Packet\ReadWriteRegistersPacket; +use PHPModbus\Packet\WriteMultipleCoilsPacket; +use PHPModbus\Packet\WriteMultipleRegisterPacket; +use PHPModbus\Packet\WriteSingleCoilPacket; +use PHPModbus\Packet\WriteSingleRegisterPacket; /** * Phpmodbus Copyright (c) 2004, 2013 Jan Krakora @@ -44,14 +53,10 @@ use InvalidArgumentException; class ModbusMaster { /** - * - * * @var string Modbus device IP address */ public $host = '192.168.1.1'; /** - * - * * @var string gateway port */ public $port = 502; @@ -60,20 +65,14 @@ class ModbusMaster */ public $client = ''; /** - * - * * @var string client port set when binding client to local ip&port */ public $client_port = 502; /** - * - * * @var string ModbusMaster status messages (echo for debugging) */ public $status; /** - * - * * @var float Total response timeout (seconds, decimals allowed) */ public $timeout_sec = 5; @@ -82,8 +81,6 @@ class ModbusMaster */ public $socket_read_timeout_sec = 0.3; /** - * - * * @var float Socket write timeout (seconds, decimals allowed) */ public $socket_write_timeout_sec = 1; // 300 ms @@ -92,17 +89,9 @@ class ModbusMaster */ public $endianness = 0; /** - * - * * @var string Socket protocol (TCP, UDP) */ - public $socket_protocol = 'UDP'; // - /** - * - * - * @var resource Communication socket - */ - private $sock; + public $socket_protocol = 'UDP'; /** * ModbusMaster @@ -165,10 +154,10 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $reference, $quantity) { - return $this->readCoilsPacketBuilder($unitId, $reference, $quantity); + return ReadCoilsPacket::build($unitId, $reference, $quantity); }, function ($data) use ($quantity) { - return $this->readCoilsParser($data, $quantity); + return ReadCoilsPacket::parse($data, $quantity); } ); @@ -176,104 +165,6 @@ class ModbusMaster return $receivedData; } - /** - * connect - * - * Connect the socket - * - * @return bool - * @throws Exception - */ - private function connect() - { - // Create a protocol specific socket - if ($this->socket_protocol === 'TCP') { - // TCP socket - $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - } elseif ($this->socket_protocol === 'UDP') { - // UDP socket - $this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); - } else { - throw new InvalidArgumentException("Unknown socket protocol, should be 'TCP' or 'UDP'"); - } - // Bind the client socket to a specific local port - if (strlen($this->client) > 0) { - $result = socket_bind($this->sock, $this->client, $this->client_port); - if ($result === false) { - throw new IOException( - "socket_bind() failed. Reason: ($result)" . - socket_strerror(socket_last_error($this->sock)) - ); - } else { - $this->status .= "Bound\n"; - } - } - - // Socket settings (send/write timeout) - $writeTimeout = $this->secsToSecUsecArray($this->socket_write_timeout_sec); - socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, $writeTimeout); - - // Connect the socket - $result = @socket_connect($this->sock, $this->host, $this->port); - if ($result === false) { - throw new IOException( - "socket_connect() failed. Reason: ($result)" . - socket_strerror(socket_last_error($this->sock)) - ); - } else { - $this->status .= "Connected\n"; - return true; - } - } - - /** - * Convert float in seconds to array - * - * @param float $secs - * @return array {sec: ..., usec: ...} - */ - private function secsToSecUsecArray($secs) - { - $remainder = $secs - floor($secs); - - return [ - 'sec' => round($secs - $remainder), - 'usec' => round($remainder * 1e6), - ]; - } - - /** - * readCoilsPacketBuilder - * - * FC1 packet builder - read coils - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return string - */ - private function readCoilsPacketBuilder($unitId, $reference, $quantity) - { - $dataLen = 0; - // build data section - $buffer1 = ''; - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(1); // FC 1 = 1(0x01) - // build body - read section - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - $dataLen += 5; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // length - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - /** * printPacket * @@ -284,135 +175,19 @@ class ModbusMaster */ private function printPacket($packet) { - $str = 'Packet: '; - for ($i = 0, $len = strlen($packet); $i < $len; $i++) { - $str .= $this->byte2hex(ord($packet[$i])); - } - $str .= "\n"; - return $str; + return 'Packet: ' . unpack('H*', $packet)[1] . "\n"; } /** - * byte2hex + * validateResponseCode * - * Parse data and get it to the Hex form - * - * @param int $value - * @return string - */ - private function byte2hex($value) - { - $h = dechex(($value >> 4) & 0x0F); - $l = dechex($value & 0x0F); - return "$h$l"; - } - - /** - * send - * - * Send the packet via Modbus - * - * @param string $packet - */ - private function send($packet) - { - socket_write($this->sock, $packet, strlen($packet)); - $this->status .= "Send\n"; - } - - /** - * rec - * - * Receive data from the socket - * - * @return bool - * @throws Exception - */ - private function rec() - { - socket_set_nonblock($this->sock); - $readsocks[] = $this->sock; - $writesocks = null; - $exceptsocks = null; - $rec = ''; - $totalReadTimeout = $this->timeout_sec; - $lastAccess = microtime(true); - $readTout = $this->secsToSecUsecArray($this->socket_read_timeout_sec); - - while (false !== socket_select($readsocks, $writesocks, $exceptsocks, $readTout['sec'], $readTout['usec'])) { - $this->status .= "Wait data ... \n"; - if (in_array($this->sock, $readsocks)) { - if (@socket_recv($this->sock, $rec, 2000, 0)) { // read max 2000 bytes - $this->status .= "Data received \n"; - return $rec; - } - $lastAccess = microtime(true); - } else { - $timeSpentWaiting = microtime(true) - $lastAccess; - if ($timeSpentWaiting >= $totalReadTimeout) { - throw new IOException( - "Watchdog time expired [ $totalReadTimeout sec ]!!! " . - "Connection to $this->host:$this->port is not established." - ); - } - } - $readsocks[] = $this->sock; - } - - return null; - } - - /** - * readCoilsParser - * - * FC 1 response parser - * - * @param string $packet - * @param int $quantity - * @return bool[] - * @throws \Exception - */ - private function readCoilsParser($packet, $quantity) - { - $data = array(); - // check Response code - $this->responseCode($packet); - // get data from stream - for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { - $data[$i] = ord($packet[9 + $i]); - } - // get bool values to array - $data_boolean_array = array(); - $di = 0; - foreach ($data as $value) { - for ($i = 0; $i < 8; $i++) { - if ($di == $quantity) { - continue; - } - // get boolean value - $v = ($value >> $i) & 0x01; - // build boolean array - if ($v == 0) { - $data_boolean_array[] = false; - } else { - $data_boolean_array[] = true; - } - $di++; - } - } - return $data_boolean_array; - } - - /** - * responseCode - * - * Check the Modbus response code + * Checks the Modbus response and throws exception if response contains failure code * * @param string $packet * @return bool * @throws Exception */ - private function responseCode($packet) + private function validateResponseCode($packet) { if ((ord($packet[7]) & 0x80) > 0) { // failure code @@ -429,13 +204,12 @@ class ModbusMaster 0x0A => 'GATEWAY PATH UNAVAILABLE', 0x0B => 'GATEWAY TARGET DEVICE FAILED TO RESPOND', ); - // get failure string + + $failure_str = 'UNDEFINED FAILURE CODE'; if (array_key_exists($failure_code, $failures)) { $failure_str = $failures[$failure_code]; - } else { - $failure_str = 'UNDEFINED FAILURE CODE'; } - // exception response + throw new ModbusException("Modbus response error code: $failure_code ($failure_str)"); } else { $this->status .= "Modbus response error code: NOERROR\n"; @@ -443,27 +217,6 @@ class ModbusMaster } } - /** - * disconnect - * - * Disconnect the socket - */ - protected function disconnect() - { - if (is_resource($this->sock)) { - socket_close($this->sock); - $this->status .= "Disconnected\n"; - } - } - - - /** - * Close socket it still open - */ - public function __destruct() - { - $this->disconnect(); - } /** * fc2 @@ -502,10 +255,10 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $reference, $quantity) { - return $this->readInputDiscretesPacketBuilder($unitId, $reference, $quantity); + return ReadInputDiscretesPacket::build($unitId, $reference, $quantity); }, function ($data) use ($quantity) { - return $this->readInputDiscretesParser($data, $quantity); + return ReadInputDiscretesPacket::parse($data, $quantity); } ); @@ -513,53 +266,6 @@ class ModbusMaster return $receivedData; } - /** - * readInputDiscretesPacketBuilder - * - * FC2 packet builder - read coils - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return string - */ - private function readInputDiscretesPacketBuilder($unitId, $reference, $quantity) - { - $dataLen = 0; - // build data section - $buffer1 = ''; - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(2); // FC 2 = 2(0x02) - // build body - read section - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - $dataLen += 5; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readInputDiscretesParser - * - * FC 2 response parser, alias to FC 1 parser i.e. readCoilsParser. - * - * @param string $packet - * @param int $quantity - * @return bool[] - * @throws \Exception - */ - private function readInputDiscretesParser($packet, $quantity) - { - return $this->readCoilsParser($packet, $quantity); - } - /** * fc3 * @@ -598,10 +304,10 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $reference, $quantity) { - return $this->readMultipleRegistersPacketBuilder($unitId, $reference, $quantity); + return ReadMultipleRegistersPacket::build($unitId, $reference, $quantity); }, function ($data) { - return $this->readMultipleRegistersParser($data); + return ReadMultipleRegistersPacket::parse($data); } ); @@ -609,59 +315,6 @@ class ModbusMaster return $receivedData; } - /** - * readMultipleRegistersPacketBuilder - * - * Packet FC 3 builder - read multiple registers - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return string - */ - private function readMultipleRegistersPacketBuilder($unitId, $reference, $quantity) - { - $dataLen = 0; - // build data section - $buffer1 = ''; - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(3); // FC 3 = 3(0x03) - // build body - read section - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - $dataLen += 5; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // length - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readMultipleRegistersParser - * - * FC 3 response parser - * - * @param string $packet - * @return array - * @throws \Exception - */ - private function readMultipleRegistersParser($packet) - { - $data = array(); - // check Response code - $this->responseCode($packet); - // get data - for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { - $data[$i] = ord($packet[9 + $i]); - } - return $data; - } - /** * fc4 * @@ -699,10 +352,10 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $reference, $quantity) { - return $this->readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity); + return ReadMultipleInputRegistersPacket::build($unitId, $reference, $quantity); }, function ($data) { - return $this->readMultipleInputRegistersParser($data); + return ReadMultipleInputRegistersPacket::parse($data); } ); @@ -710,58 +363,6 @@ class ModbusMaster return $receivedData; } - /** - * readMultipleInputRegistersPacketBuilder - * - * Packet FC 4 builder - read multiple input registers - * - * @param int $unitId - * @param int $reference - * @param int $quantity - * @return string - */ - private function readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity) - { - $dataLen = 0; - // build data section - $buffer1 = ''; - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(4); // FC 4 = 4(0x04) - // build body - read section - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - $dataLen += 5; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // length - $buffer3 .= IecType::iecBYTE($unitId); // unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readMultipleInputRegistersParser - * - * FC 4 response parser - * - * @param string $packet - * @return array - * @throws \Exception - */ - private function readMultipleInputRegistersParser($packet) - { - $data = array(); - // check Response code - $this->responseCode($packet); - // get data - for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { - $data[$i] = ord($packet[9 + $i]); - } - return $data; - } /** * fc5 @@ -800,10 +401,10 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $reference, $data) { - return $this->writeSingleCoilPacketBuilder($unitId, $reference, $data); + return WriteSingleCoilPacket::build($unitId, $reference, $data); }, - function ($data) { - return $this->writeSingleCoilParser($data); + function () { + return WriteSingleCoilPacket::parse(); } ); @@ -811,60 +412,6 @@ class ModbusMaster return $receivedData; } - /** - * writeSingleCoilPacketBuilder - * - * Packet builder FC5 - WRITE single register - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return string - */ - private function writeSingleCoilPacketBuilder($unitId, $reference, array $data) - { - $dataLen = 0; - // build data section - $buffer1 = ''; - foreach ($data as $key => $dataitem) { - if ($dataitem == true) { - $buffer1 = IecType::iecINT(0xFF00); - } else { - $buffer1 = IecType::iecINT(0x0000); - } - } - $dataLen += 2; - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(5); // FC5 = 5(0x05) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $dataLen += 3; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * writeSingleCoilParser - * - * FC5 response parser - * - * @param string $packet - * @return bool - * @throws \Exception - */ - private function writeSingleCoilParser($packet) - { - $this->responseCode($packet); - return true; - } - /** * fc6 * @@ -901,10 +448,10 @@ class ModbusMaster $this->status .= "writeSingleRegister: START\n"; $result = $this->sendAndReceive( function () use ($unitId, $reference, $data) { - return $this->writeSingleRegisterPacketBuilder($unitId, $reference, $data); + return WriteSingleRegisterPacket::build($unitId, $reference, $data); }, - function ($data) { - return $this->writeSingleRegisterParser($data); + function () { + return WriteSingleRegisterPacket::parse(); } ); $this->status .= "writeSingleRegister: DONE\n"; @@ -914,69 +461,32 @@ class ModbusMaster public function sendAndReceive(callable $buildRequest, callable $parseResponse) { try { - $this->connect(); + $socket = ModbusSocket::getBuilder() + ->setHost($this->host) + ->setPort($this->port) + ->setSocketProtocol($this->socket_protocol) + ->setClient($this->client) + ->setClientPort($this->client_port) + ->setTimeoutSec($this->timeout_sec) + ->setSocketReadTimeoutSec($this->socket_read_timeout_sec) + ->setSocketWriteTimeoutSec($this->socket_write_timeout_sec) + ->build(); + + $socket->connect(); + $packet = $buildRequest(); + $socket->send($packet); - $this->send($packet); - $data = $this->rec(); + $data = $socket->receive(); $this->status .= $this->printPacket($data); + + $this->validateResponseCode($data); return $parseResponse($data); } finally { - $this->disconnect(); + $this->status .= implode("\n", $socket->getStatusMessages()); + $socket->close(); } - - } - - /** - * writeSingleRegisterPacketBuilder - * - * Packet builder FC6 - WRITE single register - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return string - */ - private function writeSingleRegisterPacketBuilder($unitId, $reference, array $data) - { - $dataLen = 0; - // build data section - $buffer1 = ''; - foreach ($data as $key => $dataitem) { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - break; - } - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(6); // FC6 = 6(0x06) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $dataLen += 3; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // length - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * writeSingleRegisterParser - * - * FC6 response parser - * - * @param string $packet - * @return bool - * @throws \Exception - */ - private function writeSingleRegisterParser($packet) - { - $this->responseCode($packet); - return true; } /** @@ -1015,10 +525,10 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $reference, $data) { - return $this->writeMultipleCoilsPacketBuilder($unitId, $reference, $data); + return WriteMultipleCoilsPacket::build($unitId, $reference, $data); }, - function ($data) { - return $this->writeMultipleCoilsParser($data); + function () { + return WriteMultipleCoilsPacket::parse(); } ); @@ -1026,79 +536,6 @@ class ModbusMaster return $receivedData; } - /** - * writeMultipleCoilsPacketBuilder - * - * Packet builder FC15 - Write multiple coils - * - * @param int $unitId - * @param int $reference - * @param array $data - * @return string - */ - private function writeMultipleCoilsPacketBuilder($unitId, $reference, array $data) - { - $dataLen = 0; - // build bool stream to the WORD array - $data_word_stream = array(); - $data_word = 0; - $shift = 0; - for ($i = 0, $len = count($data); $i < $len; $i++) { - if ((($i % 8) === 0) && ($i > 0)) { - $data_word_stream[] = $data_word; - $shift = 0; - $data_word = 0; - $data_word |= (0x01 && $data[$i]) << $shift; - $shift++; - } else { - $data_word |= (0x01 && $data[$i]) << $shift; - $shift++; - } - } - $data_word_stream[] = $data_word; - // show binary stream to status string - foreach ($data_word_stream as $d) { - $this->status .= sprintf("byte=b%08b\n", $d); - } - // build data section - $buffer1 = ""; - foreach ($data_word_stream as $key => $dataitem) { - $buffer1 .= IecType::iecBYTE($dataitem); // register values x - $dataLen += 1; - } - // build body - $buffer2 = ""; - $buffer2 .= IecType::iecBYTE(15); // FC 15 = 15(0x0f) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT(count($data)); // bit count - $buffer2 .= IecType::iecBYTE((count($data) + 7) / 8); // byte count - $dataLen += 6; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // length - $buffer3 .= IecType::iecBYTE($unitId); // unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * writeMultipleCoilsParser - * - * FC15 response parser - * - * @param string $packet - * @return bool - * @throws \Exception - */ - private function writeMultipleCoilsParser($packet) - { - $this->responseCode($packet); - return true; - } - /** * fc16 * @@ -1139,10 +576,10 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $reference, $data, $dataTypes) { - return $this->writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes); + return WriteMultipleRegisterPacket::build($unitId, $reference, $data, $dataTypes, $this->endianness); }, - function ($data) { - return $this->writeMultipleRegisterParser($data); + function () { + return WriteMultipleRegisterPacket::parse(); } ); @@ -1150,71 +587,6 @@ class ModbusMaster return $receivedData; } - /** - * writeMultipleRegisterPacketBuilder - * - * Packet builder FC16 - WRITE multiple register - * e.g.: 4dd90000000d0010300000030603e807d00bb8 - * - * @param int $unitId - * @param int $reference - * @param array $data - * @param array $dataTypes - * @return string - */ - private function writeMultipleRegisterPacketBuilder($unitId, $reference, array $data, array $dataTypes) - { - $dataLen = 0; - // build data section - $buffer1 = ""; - foreach ($data as $key => $dataitem) { - if ($dataTypes[$key] === 'INT') { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - } elseif ($dataTypes[$key] === 'DINT') { - $buffer1 .= IecType::iecDINT($dataitem, $this->endianness); // register values x - $dataLen += 4; - } elseif ($dataTypes[$key] === 'REAL') { - $buffer1 .= IecType::iecREAL($dataitem, $this->endianness); // register values x - $dataLen += 4; - } else { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - } - } - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(16); // FC 16 = 16(0x10) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($dataLen / 2); // word count - $buffer2 .= IecType::iecBYTE($dataLen); // byte count - $dataLen += 6; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // length - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * writeMultipleRegisterParser - * - * FC16 response parser - * - * @param string $packet - * @return bool - * @throws \Exception - */ - private function writeMultipleRegisterParser($packet) - { - $this->responseCode($packet); - return true; - } - /** * fc22 * @@ -1256,10 +628,10 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $reference, $andMask, $orMask) { - return $this->maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask); + return MaskWriteRegisterPacket::build($unitId, $reference, $andMask, $orMask); }, function ($data) { - return $this->maskWriteRegisterParser($data); + return MaskWriteRegisterPacket::parse($data); } ); @@ -1267,53 +639,6 @@ class ModbusMaster return $receivedData; } - /** - * maskWriteRegisterPacketBuilder - * - * Packet builder FC22 - MASK WRITE register - * - * @param int $unitId - * @param int $reference - * @param int $andMask - * @param int $orMask - * @return string - */ - private function maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask) - { - $dataLen = 0; - // build data section - $buffer1 = ''; - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(22); // FC 22 = 22(0x16) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT($andMask); // AND mask - $buffer2 .= IecType::iecINT($orMask); // OR mask - $dataLen += 7; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // lenght - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * maskWriteRegisterParser - * - * FC22 response parser - * - * @param string $packet - * @return bool - * @throws \Exception - */ - private function maskWriteRegisterParser($packet) - { - $this->responseCode($packet); - return true; - } /** * fc23 @@ -1360,12 +685,12 @@ class ModbusMaster $receivedData = $this->sendAndReceive( function () use ($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes) { - return $this->readWriteRegistersPacketBuilder( - $unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes + return ReadWriteRegistersPacket::build( + $unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes, $this->endianness ); }, function ($data) { - return $this->readWriteRegistersParser($data); + return ReadWriteRegistersPacket::parse($data); } ); @@ -1373,91 +698,6 @@ class ModbusMaster return $receivedData; } - /** - * readWriteRegistersPacketBuilder - * - * Packet FC23 builder - READ WRITE registers - * - * @param int $unitId - * @param int $referenceRead - * @param int $quantity - * @param int $referenceWrite - * @param array $data - * @param array $dataTypes - * @return string - */ - private function readWriteRegistersPacketBuilder( - $unitId, - $referenceRead, - $quantity, - $referenceWrite, - array $data, - array $dataTypes - ) - { - - $dataLen = 0; - // build data section - $buffer1 = ''; - foreach ($data as $key => $dataitem) { - if ($dataTypes[$key] === 'INT') { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - } elseif ($dataTypes[$key] === 'DINT') { - $buffer1 .= IecType::iecDINT($dataitem, $this->endianness); // register values x - $dataLen += 4; - } elseif ($dataTypes[$key] === 'REAL') { - $buffer1 .= IecType::iecREAL($dataitem, $this->endianness); // register values x - $dataLen += 4; - } else { - $buffer1 .= IecType::iecINT($dataitem); // register values x - $dataLen += 2; - } - } - // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(23); // FC 23 = 23(0x17) - // build body - read section - $buffer2 .= IecType::iecINT($referenceRead); // refnumber = 12288 - $buffer2 .= IecType::iecINT($quantity); // quantity - // build body - write section - $buffer2 .= IecType::iecINT($referenceWrite); // refnumber = 12288 - $buffer2 .= IecType::iecINT($dataLen / 2); // word count - $buffer2 .= IecType::iecBYTE($dataLen); // byte count - $dataLen += 10; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // length - $buffer3 .= IecType::iecBYTE($unitId); //unit ID - - // return packet string - return $buffer3 . $buffer2 . $buffer1; - } - - /** - * readWriteRegistersParser - * - * FC23 response parser - * - * @param string $packet - * @return array|false - * @throws \Exception - */ - private function readWriteRegistersParser($packet) - { - $data = array(); - // if not exception - if (!$this->responseCode($packet)) { - return false; - } - // get data - for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { - $data[$i] = ord($packet[9 + $i]); - } - return $data; - } /** * Set data receive timeout. diff --git a/src/ModbusSocket.php b/src/ModbusSocket.php new file mode 100644 index 0000000..e4bb075 --- /dev/null +++ b/src/ModbusSocket.php @@ -0,0 +1,325 @@ +socket_protocol === 'TCP') { + // TCP socket + $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + } elseif ($this->socket_protocol === 'UDP') { + // UDP socket + $this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + } else { + throw new InvalidArgumentException("Unknown socket protocol, should be 'TCP' or 'UDP'"); + } + // Bind the client socket to a specific local port + if (strlen($this->client) > 0) { + $result = socket_bind($this->sock, $this->client, $this->client_port); + if ($result === false) { + throw new IOException( + "socket_bind() failed. Reason: ($result)" . + socket_strerror(socket_last_error($this->sock)) + ); + } else { + $this->statusMessages[] = 'Bound'; + } + } + + // Socket settings (send/write timeout) + $writeTimeout = $this->secsToSecUsecArray($this->socket_write_timeout_sec); + socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, $writeTimeout); + + // Connect the socket + $result = @socket_connect($this->sock, $this->host, $this->port); + if ($result === false) { + throw new IOException( + "socket_connect() failed. Reason: ($result)" . + socket_strerror(socket_last_error($this->sock)) + ); + } else { + $this->statusMessages[] = 'Connected'; + return true; + } + } + + /** + * receive + * + * Receive data from the socket + * + * @return bool + * @throws \Exception + */ + public function receive() + { + socket_set_nonblock($this->sock); + $readsocks[] = $this->sock; + $writesocks = null; + $exceptsocks = null; + $rec = ''; + $totalReadTimeout = $this->timeout_sec; + $lastAccess = microtime(true); + $readTout = $this->secsToSecUsecArray($this->socket_read_timeout_sec); + + while (false !== socket_select($readsocks, $writesocks, $exceptsocks, $readTout['sec'], $readTout['usec'])) { + $this->statusMessages[] = 'Wait data ... '; + if (in_array($this->sock, $readsocks, false)) { + if (@socket_recv($this->sock, $rec, 2000, 0)) { // read max 2000 bytes + $this->statusMessages[] = 'Data received'; + return $rec; + } + $lastAccess = microtime(true); + } else { + $timeSpentWaiting = microtime(true) - $lastAccess; + if ($timeSpentWaiting >= $totalReadTimeout) { + throw new IOException( + "Watchdog time expired [ $totalReadTimeout sec ]!!! " . + "Connection to $this->host:$this->port is not established." + ); + } + } + $readsocks[] = $this->sock; + } + + return null; + } + + /** + * send + * + * Send the packet via Modbus + * + * @param string $packet + */ + public function send($packet) + { + socket_write($this->sock, $packet, strlen($packet)); + $this->statusMessages[] = 'Send'; + } + + /** + * close + * + * Close the socket + */ + public function close() + { + if (is_resource($this->sock)) { + socket_close($this->sock); + $this->statusMessages[] = 'Disconnected'; + } + } + + /** + * Close socket it still open + */ + public function __destruct() + { + $this->close(); + } + + /** + * Convert float in seconds to array + * + * @param float $secs + * @return array {sec: ..., usec: ...} + */ + private function secsToSecUsecArray($secs) + { + $remainder = $secs - floor($secs); + + return [ + 'sec' => round($secs - $remainder), + 'usec' => round($remainder * 1e6), + ]; + } + + /** + * @return array + */ + public function getStatusMessages() + { + return $this->statusMessages; + } + +} + + +class ModbusSocketBuilder extends ModbusSocket +{ + /** + * @var ModbusSocket instance to be built + */ + private $modbusSocket; + + public function __construct() + { + $this->modbusSocket = new ModbusSocket(); + } + + /** + * Return built instance of ModbusSocket + * + * @return ModbusSocket built instance + */ + public function build() + { + return $this->modbusSocket; + } + + /** + * @param string $client + * @return ModbusSocketBuilder + */ + public function setClient($client) + { + $this->modbusSocket->client = $client; + return $this; + } + + /** + * @param string $client_port + * @return ModbusSocketBuilder + */ + public function setClientPort($client_port) + { + $this->modbusSocket->client_port = $client_port; + return $this; + } + + /** + * @param float $timeout_sec + * @return ModbusSocketBuilder + */ + public function setTimeoutSec($timeout_sec) + { + $this->modbusSocket->timeout_sec = $timeout_sec; + return $this; + } + + /** + * @param float $socket_read_timeout_sec + * @return ModbusSocketBuilder + */ + public function setSocketReadTimeoutSec($socket_read_timeout_sec) + { + $this->modbusSocket->socket_read_timeout_sec = $socket_read_timeout_sec; + return $this; + } + + /** + * @param float $socket_write_timeout_sec + * @return ModbusSocketBuilder + */ + public function setSocketWriteTimeoutSec($socket_write_timeout_sec) + { + $this->modbusSocket->socket_write_timeout_sec = $socket_write_timeout_sec; + return $this; + } + + /** + * @param string $socket_protocol + * @return ModbusSocketBuilder + */ + public function setSocketProtocol($socket_protocol) + { + $this->modbusSocket->socket_protocol = $socket_protocol; + return $this; + } + + /** + * @param string $host + * @return ModbusSocketBuilder + */ + public function setHost($host) + { + $this->modbusSocket->host = $host; + return $this; + } + + /** + * @param string $port + * @return ModbusSocketBuilder + */ + public function setPort($port) + { + $this->modbusSocket->port = $port; + return $this; + } + +} diff --git a/src/Packet/MaskWriteRegisterPacket.php b/src/Packet/MaskWriteRegisterPacket.php new file mode 100644 index 0000000..872445b --- /dev/null +++ b/src/Packet/MaskWriteRegisterPacket.php @@ -0,0 +1,65 @@ +> $i) & 0x01; + // build boolean array + if ($v == 0) { + $data_boolean_array[] = false; + } else { + $data_boolean_array[] = true; + } + $di++; + } + } + return $data_boolean_array; + } + +} \ No newline at end of file diff --git a/src/Packet/ReadInputDiscretesPacket.php b/src/Packet/ReadInputDiscretesPacket.php new file mode 100644 index 0000000..b0afa45 --- /dev/null +++ b/src/Packet/ReadInputDiscretesPacket.php @@ -0,0 +1,64 @@ + $dataitem) { + if ($dataTypes[$key] === 'INT') { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + } elseif ($dataTypes[$key] === 'DINT') { + $buffer1 .= IecType::iecDINT($dataitem, $endianness); // register values x + $dataLen += 4; + } elseif ($dataTypes[$key] === 'REAL') { + $buffer1 .= IecType::iecREAL($dataitem, $endianness); // register values x + $dataLen += 4; + } else { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + } + } + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(23); // FC 23 = 23(0x17) + // build body - read section + $buffer2 .= IecType::iecINT($referenceRead); // refnumber = 12288 + $buffer2 .= IecType::iecINT($quantity); // quantity + // build body - write section + $buffer2 .= IecType::iecINT($referenceWrite); // refnumber = 12288 + $buffer2 .= IecType::iecINT($dataLen / 2); // word count + $buffer2 .= IecType::iecBYTE($dataLen); // byte count + $dataLen += 10; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * FC23 response parser + * + * @param string $packet + * @return array|false + * @throws \Exception + */ + public static function parse($packet) + { + $data = array(); + // get data + for ($i = 0, $len = ord($packet[8]); $i < $len; $i++) { + $data[$i] = ord($packet[9 + $i]); + } + return $data; + } + +} \ No newline at end of file diff --git a/src/Packet/WriteMultipleCoilsPacket.php b/src/Packet/WriteMultipleCoilsPacket.php new file mode 100644 index 0000000..5851bf5 --- /dev/null +++ b/src/Packet/WriteMultipleCoilsPacket.php @@ -0,0 +1,89 @@ + 0)) { + $data_word_stream[] = $data_word; + $shift = 0; + $data_word = 0; + $data_word |= (0x01 && $data[$i]) << $shift; + $shift++; + } else { + $data_word |= (0x01 && $data[$i]) << $shift; + $shift++; + } + } + $data_word_stream[] = $data_word; + // show binary stream to status string +// foreach ($data_word_stream as $d) { +// $this->status .= sprintf("byte=b%08b\n", $d); +// } + // build data section + $buffer1 = ''; + foreach ($data_word_stream as $key => $dataitem) { + $buffer1 .= IecType::iecBYTE($dataitem); // register values x + $dataLen += 1; + } + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(15); // FC 15 = 15(0x0f) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT(count($data)); // bit count + $buffer2 .= IecType::iecBYTE((count($data) + 7) / 8); // byte count + $dataLen += 6; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); // unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * FC15 response parser + * + * @return bool + * @throws \Exception + */ + public static function parse() + { + return true; + } + +} \ No newline at end of file diff --git a/src/Packet/WriteMultipleRegisterPacket.php b/src/Packet/WriteMultipleRegisterPacket.php new file mode 100644 index 0000000..747b3b0 --- /dev/null +++ b/src/Packet/WriteMultipleRegisterPacket.php @@ -0,0 +1,85 @@ + $dataitem) { + if ($dataTypes[$key] === 'INT') { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + } elseif ($dataTypes[$key] === 'DINT') { + $buffer1 .= IecType::iecDINT($dataitem, $endianness); // register values x + $dataLen += 4; + } elseif ($dataTypes[$key] === 'REAL') { + $buffer1 .= IecType::iecREAL($dataitem, $endianness); // register values x + $dataLen += 4; + } else { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + } + } + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(16); // FC 16 = 16(0x10) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $buffer2 .= IecType::iecINT($dataLen / 2); // word count + $buffer2 .= IecType::iecBYTE($dataLen); // byte count + $dataLen += 6; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + + + /** + * FC16 response parser + * + * @return bool + * @throws \Exception + */ + public static function parse() + { + return true; + } + +} \ No newline at end of file diff --git a/src/Packet/WriteSingleCoilPacket.php b/src/Packet/WriteSingleCoilPacket.php new file mode 100644 index 0000000..5f68a12 --- /dev/null +++ b/src/Packet/WriteSingleCoilPacket.php @@ -0,0 +1,70 @@ + $dataitem) { + if ($dataitem == true) { + $buffer1 = IecType::iecINT(0xFF00); + } else { + $buffer1 = IecType::iecINT(0x0000); + } + } + $dataLen += 2; + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(5); // FC5 = 5(0x05) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $dataLen += 3; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // lenght + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * FC5 response parser + * + * @return bool + * @throws \Exception + */ + public static function parse() + { + return true; + } + +} \ No newline at end of file diff --git a/src/Packet/WriteSingleRegisterPacket.php b/src/Packet/WriteSingleRegisterPacket.php new file mode 100644 index 0000000..b8faf06 --- /dev/null +++ b/src/Packet/WriteSingleRegisterPacket.php @@ -0,0 +1,68 @@ + $dataitem) { + $buffer1 .= IecType::iecINT($dataitem); // register values x + $dataLen += 2; + break; + } + // build body + $buffer2 = ''; + $buffer2 .= IecType::iecBYTE(6); // FC6 = 6(0x06) + $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 + $dataLen += 3; + // build header + $buffer3 = ''; + $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $buffer3 .= IecType::iecINT(0); // protocol ID + $buffer3 .= IecType::iecINT($dataLen + 1); // length + $buffer3 .= IecType::iecBYTE($unitId); //unit ID + + // return packet string + return $buffer3 . $buffer2 . $buffer1; + } + + /** + * FC6 response parser + * + * @return bool + * @throws \Exception + */ + public static function parse() + { + return true; + } + +} \ No newline at end of file diff --git a/tests/ModbusMaster/UdpFc1ReadCoilsTest.php b/tests/ModbusMaster/UdpFc1ReadCoilsTest.php index 78a6e34..a2d563e 100644 --- a/tests/ModbusMaster/UdpFc1ReadCoilsTest.php +++ b/tests/ModbusMaster/UdpFc1ReadCoilsTest.php @@ -13,7 +13,7 @@ class UdpFc1ReadCoilsTest extends MockServerTestCase $modbus = new ModbusMasterUdp('127.0.0.1'); $modbus->port = $port; - usleep(50000); // no idea how to fix this. wait for server to "warm" up or modbus UDP socket will timeout. does not occur with TCP + usleep(150000); // no idea how to fix this. wait for server to "warm" up or modbus UDP socket will timeout. does not occur with TCP $this->assertEquals([1], $modbus->readCoils(0, 256, 1)); }, 'UDP'); From 604ba8586c9357bf03a7c773efbc9ff30d0c0084 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 20:38:04 +0200 Subject: [PATCH 11/17] replace dependency to sockets extension to built-in stream API --- README.md | 11 +- composer.json | 3 +- src/ModbusMaster.php | 9 +- src/ModbusSocket.php | 153 ++++++++++++--------- tests/ModbusMaster/ModbusExceptionTest.php | 2 +- 5 files changed, 105 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 2349b3b..b37d407 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,13 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protocol using PHP. -**NOTE: This is a fork to fix & update the library code (and code alone).** +**NOTE: This is a fork to fix & update the library code.** -> **What's new** -> -> This fork adds a namespace and fixes issues encountered when porting to PHP 7, fixes old MS Windows specific tests +##What's new + +* This fork adds a namespace and fixes issues encountered when porting to PHP 7 +* Removes dependency to [sockets extension](http://at2.php.net/manual/en/book.sockets.php). Now uses built-in [Stream API](http://www.php.net/manual/en/function.stream-socket-client.php) +* Fixes/replaces old MS Windows specific tests ## Implemented features @@ -25,7 +27,6 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protoc ## Requirements - * The PHP extension php_sockets.dll should be enabled (server php.ini file) * PHP 5.5+ diff --git a/composer.json b/composer.json index 012daf7..5730ece 100644 --- a/composer.json +++ b/composer.json @@ -4,8 +4,7 @@ "description": "PhpModbus with namespaces and updated to PHP 7", "license": "LGPL", "require": { - "php": "^5.5 || ^7.0", - "ext-sockets": "*" + "php": "^5.5 || ^7.0" }, "require-dev": { "react/socket": "~0.4.0", diff --git a/src/ModbusMaster.php b/src/ModbusMaster.php index 95f2fc3..a4349ff 100644 --- a/src/ModbusMaster.php +++ b/src/ModbusMaster.php @@ -76,14 +76,18 @@ class ModbusMaster * @var float Total response timeout (seconds, decimals allowed) */ public $timeout_sec = 5; + /** + * @var float Socket connect timeout (seconds, decimals allowed) + */ + public $socket_connect_timeout_sec = 1; /** * @var float Socket read timeout (seconds, decimals allowed) */ - public $socket_read_timeout_sec = 0.3; + public $socket_read_timeout_sec = 0.3; // 300 ms /** * @var float Socket write timeout (seconds, decimals allowed) */ - public $socket_write_timeout_sec = 1; // 300 ms + public $socket_write_timeout_sec = 1; /** * @var int Endianness codding (0 = little endian = 0, 1 = big endian) */ @@ -470,6 +474,7 @@ class ModbusMaster ->setTimeoutSec($this->timeout_sec) ->setSocketReadTimeoutSec($this->socket_read_timeout_sec) ->setSocketWriteTimeoutSec($this->socket_write_timeout_sec) + ->setSocketConnectTimeoutSec($this->socket_connect_timeout_sec) ->build(); $socket->connect(); diff --git a/src/ModbusSocket.php b/src/ModbusSocket.php index e4bb075..a1fa61a 100644 --- a/src/ModbusSocket.php +++ b/src/ModbusSocket.php @@ -33,14 +33,18 @@ class ModbusSocket * @var float Total response timeout (seconds, decimals allowed) */ protected $timeout_sec = 5; + /** + * @var float Socket connect timeout (seconds, decimals allowed) + */ + protected $socket_connect_timeout_sec = 1; /** * @var float Socket read timeout (seconds, decimals allowed) */ - protected $socket_read_timeout_sec = 0.3; + protected $socket_read_timeout_sec = 0.3; // 300 ms /** * @var float Socket write timeout (seconds, decimals allowed) */ - protected $socket_write_timeout_sec = 1; // 300 ms + protected $socket_write_timeout_sec = 1; /** * @var string Socket protocol (TCP, UDP) */ @@ -56,13 +60,14 @@ class ModbusSocket /** * @var resource Communication socket */ - protected $sock; + private $streamSocket; /** * @var array status messages */ protected $statusMessages = []; - public static function getBuilder() { + public static function getBuilder() + { return new ModbusSocketBuilder(); } @@ -77,44 +82,54 @@ class ModbusSocket */ public function connect() { - // Create a protocol specific socket - if ($this->socket_protocol === 'TCP') { - // TCP socket - $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - } elseif ($this->socket_protocol === 'UDP') { - // UDP socket - $this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); - } else { - throw new InvalidArgumentException("Unknown socket protocol, should be 'TCP' or 'UDP'"); + $protocol = null; + switch ($this->socket_protocol) { + case 'TCP': + case 'UDP': + $protocol = strtolower($this->socket_protocol); + break; + default: + throw new InvalidArgumentException("Unknown socket protocol, should be 'TCP' or 'UDP'"); } - // Bind the client socket to a specific local port + + $opts = []; if (strlen($this->client) > 0) { - $result = socket_bind($this->sock, $this->client, $this->client_port); - if ($result === false) { - throw new IOException( - "socket_bind() failed. Reason: ($result)" . - socket_strerror(socket_last_error($this->sock)) - ); - } else { - $this->statusMessages[] = 'Bound'; - } + // Bind the client stream to a specific local port + $opts = array( + 'socket' => array( + 'bindto' => "{$this->client}:{$this->client_port}", + ), + ); + } + $context = stream_context_create($opts); + + $this->streamSocket = @stream_socket_client( + "$protocol://$this->host:$this->port", + $errno, + $errstr, + $this->socket_connect_timeout_sec, + STREAM_CLIENT_CONNECT, + $context + ); + + if (false === $this->streamSocket) { + $message = "Unable to create client socket to {$protocol}://{$this->host}:{$this->port}: {$errstr}"; + throw new IOException($message, $errno); } - // Socket settings (send/write timeout) - $writeTimeout = $this->secsToSecUsecArray($this->socket_write_timeout_sec); - socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, $writeTimeout); - - // Connect the socket - $result = @socket_connect($this->sock, $this->host, $this->port); - if ($result === false) { - throw new IOException( - "socket_connect() failed. Reason: ($result)" . - socket_strerror(socket_last_error($this->sock)) - ); - } else { - $this->statusMessages[] = 'Connected'; - return true; + if (strlen($this->client) > 0) { + $this->statusMessages[] = 'Bound'; } + $this->statusMessages[] = 'Connected'; + + stream_set_blocking($this->streamSocket, false); // use non-blocking stream + + $writeTimeoutParts = $this->secsToSecUsecArray($this->socket_write_timeout_sec); + // set as stream timeout as we use 'stream_select' to read data and this method has its own timeout + // this call will only affect our fwrite parts (send data method) + stream_set_timeout($this->streamSocket, $writeTimeoutParts['sec'], $writeTimeoutParts['usec']); + + return true; } /** @@ -127,35 +142,37 @@ class ModbusSocket */ public function receive() { - socket_set_nonblock($this->sock); - $readsocks[] = $this->sock; - $writesocks = null; - $exceptsocks = null; - $rec = ''; $totalReadTimeout = $this->timeout_sec; $lastAccess = microtime(true); - $readTout = $this->secsToSecUsecArray($this->socket_read_timeout_sec); - - while (false !== socket_select($readsocks, $writesocks, $exceptsocks, $readTout['sec'], $readTout['usec'])) { - $this->statusMessages[] = 'Wait data ... '; - if (in_array($this->sock, $readsocks, false)) { - if (@socket_recv($this->sock, $rec, 2000, 0)) { // read max 2000 bytes - $this->statusMessages[] = 'Data received'; - return $rec; + + $readTimeout = $this->secsToSecUsecArray($this->socket_read_timeout_sec); + while (true) { + $read = array($this->streamSocket); + $write = null; + $except = null; + if (false !== stream_select($read, $write, $except, $readTimeout['sec'], $readTimeout['usec'])) { + $this->statusMessages[] = 'Wait data ... '; + + if (in_array($this->streamSocket, $read, false)) { + $data = fread($this->streamSocket, 2048); // read max 2048 bytes + if (!empty($data)) { + $this->statusMessages[] = 'Data received'; + return $data; //FIXME what if we are waiting for more than that? + } + $lastAccess = microtime(true); + } else { + $timeSpentWaiting = microtime(true) - $lastAccess; + if ($timeSpentWaiting >= $totalReadTimeout) { + throw new IOException( + "Watchdog time expired [ {$totalReadTimeout} sec ]!!! " . + "Connection to {$this->host}:{$this->port} is not established." + ); + } } - $lastAccess = microtime(true); } else { - $timeSpentWaiting = microtime(true) - $lastAccess; - if ($timeSpentWaiting >= $totalReadTimeout) { - throw new IOException( - "Watchdog time expired [ $totalReadTimeout sec ]!!! " . - "Connection to $this->host:$this->port is not established." - ); - } + throw new IOException("Failed to read data from {$this->host}:{$this->port}."); } - $readsocks[] = $this->sock; } - return null; } @@ -168,7 +185,7 @@ class ModbusSocket */ public function send($packet) { - socket_write($this->sock, $packet, strlen($packet)); + fwrite($this->streamSocket, $packet, strlen($packet)); $this->statusMessages[] = 'Send'; } @@ -179,8 +196,8 @@ class ModbusSocket */ public function close() { - if (is_resource($this->sock)) { - socket_close($this->sock); + if (is_resource($this->streamSocket)) { + fclose($this->streamSocket); $this->statusMessages[] = 'Disconnected'; } } @@ -322,4 +339,14 @@ class ModbusSocketBuilder extends ModbusSocket return $this; } + /** + * @param float $socket_connect_timeout_sec + * @return ModbusSocketBuilder + */ + public function setSocketConnectTimeoutSec($socket_connect_timeout_sec) + { + $this->modbusSocket->socket_connect_timeout_sec = $socket_connect_timeout_sec; + return $this; + } + } diff --git a/tests/ModbusMaster/ModbusExceptionTest.php b/tests/ModbusMaster/ModbusExceptionTest.php index 3d0db1f..ac7e3a4 100644 --- a/tests/ModbusMaster/ModbusExceptionTest.php +++ b/tests/ModbusMaster/ModbusExceptionTest.php @@ -21,7 +21,7 @@ class ModbusExceptionTest extends MockServerTestCase public function testPortClosedException() { $this->expectException(IOException::class); - $this->expectExceptionMessage('socket_connect() failed. Reason:'); + $this->expectExceptionMessage('Unable to create client socket to'); $modbus = new ModbusMasterTcp('127.0.0.1'); $modbus->setSocketTimeout(0.2, 0.2); From 6561e406a15bbdb201dc3db3bb1eeb2eecfd3ce0 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 20:46:41 +0200 Subject: [PATCH 12/17] update readme --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b37d407..23f2850 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protoc ##What's new * This fork adds a namespace and fixes issues encountered when porting to PHP 7 -* Removes dependency to [sockets extension](http://at2.php.net/manual/en/book.sockets.php). Now uses built-in [Stream API](http://www.php.net/manual/en/function.stream-socket-client.php) +* Removes dependency to [sockets extension](http://www.php.net/manual/en/book.sockets.php). Now uses built-in [Stream API](http://www.php.net/manual/en/function.stream-socket-client.php) * Fixes/replaces old MS Windows specific tests @@ -63,7 +63,11 @@ run PHPUnit. NB: PHP 5.6+ is required for tests composer install - vendor/bin/phpunit + composer test + +To report test coverage (created inside ./report/html): + + composer test-coverage ## GoogleCode legacy docs & downloads From f6818a21c49ac68ff24202fc5e85e836c880c9e4 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 22:01:51 +0200 Subject: [PATCH 13/17] let modbus add send packet to status message, clean a little WriteMultipleCoilsPacket methods --- src/ModbusMaster.php | 5 +- src/Packet/WriteMultipleCoilsPacket.php | 87 ++++++++++--------- .../Fc15WriteMultipleCoilsTest.php | 20 +++++ 3 files changed, 70 insertions(+), 42 deletions(-) diff --git a/src/ModbusMaster.php b/src/ModbusMaster.php index a4349ff..e35b586 100644 --- a/src/ModbusMaster.php +++ b/src/ModbusMaster.php @@ -179,7 +179,7 @@ class ModbusMaster */ private function printPacket($packet) { - return 'Packet: ' . unpack('H*', $packet)[1] . "\n"; + return 'packet: ' . unpack('H*', $packet)[1] . "\n"; } /** @@ -480,11 +480,12 @@ class ModbusMaster $socket->connect(); $packet = $buildRequest(); + $this->status .= 'Sending ' . $this->printPacket($packet); $socket->send($packet); $data = $socket->receive(); - $this->status .= $this->printPacket($data); + $this->status .= 'Received ' . $this->printPacket($data); $this->validateResponseCode($data); return $parseResponse($data); diff --git a/src/Packet/WriteMultipleCoilsPacket.php b/src/Packet/WriteMultipleCoilsPacket.php index 5851bf5..fd5dab0 100644 --- a/src/Packet/WriteMultipleCoilsPacket.php +++ b/src/Packet/WriteMultipleCoilsPacket.php @@ -17,6 +17,9 @@ namespace PHPModbus\Packet; use PHPModbus\IecType; +/** + * Builds/parses packet in Modbus TCP/IP format (http://www.simplymodbus.ca/TCP.htm) + */ class WriteMultipleCoilsPacket { /** @@ -30,49 +33,25 @@ class WriteMultipleCoilsPacket public static function build($unitId, $reference, array $data) { $dataLen = 0; - // build bool stream to the WORD array - $data_word_stream = array(); - $data_word = 0; - $shift = 0; - for ($i = 0, $len = count($data); $i < $len; $i++) { - if ((($i % 8) === 0) && ($i > 0)) { - $data_word_stream[] = $data_word; - $shift = 0; - $data_word = 0; - $data_word |= (0x01 && $data[$i]) << $shift; - $shift++; - } else { - $data_word |= (0x01 && $data[$i]) << $shift; - $shift++; - } - } - $data_word_stream[] = $data_word; - // show binary stream to status string -// foreach ($data_word_stream as $d) { -// $this->status .= sprintf("byte=b%08b\n", $d); -// } - // build data section - $buffer1 = ''; - foreach ($data_word_stream as $key => $dataitem) { - $buffer1 .= IecType::iecBYTE($dataitem); // register values x - $dataLen += 1; - } + + list($pduData, $wordCount) = self::getDataConvertedToIecWords($data); + $dataLen += $wordCount; + // build body - $buffer2 = ''; - $buffer2 .= IecType::iecBYTE(15); // FC 15 = 15(0x0f) - $buffer2 .= IecType::iecINT($reference); // refnumber = 12288 - $buffer2 .= IecType::iecINT(count($data)); // bit count - $buffer2 .= IecType::iecBYTE((count($data) + 7) / 8); // byte count + $pduHeader = ''; + $pduHeader .= IecType::iecBYTE(15); // FC 15 = 15(0x0f) + $pduHeader .= IecType::iecINT($reference); // refnumber = 12288 + $pduHeader .= IecType::iecINT(count($data)); // bit count + $pduHeader .= IecType::iecBYTE((count($data) + 7) / 8); // byte count $dataLen += 6; - // build header - $buffer3 = ''; - $buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID - $buffer3 .= IecType::iecINT(0); // protocol ID - $buffer3 .= IecType::iecINT($dataLen + 1); // length - $buffer3 .= IecType::iecBYTE($unitId); // unit ID - // return packet string - return $buffer3 . $buffer2 . $buffer1; + $mbapHeader = ''; + $mbapHeader .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID + $mbapHeader .= IecType::iecINT(0); // protocol ID + $mbapHeader .= IecType::iecINT($dataLen + 1); // length + $mbapHeader .= IecType::iecBYTE($unitId); // unit ID + + return $mbapHeader . $pduHeader . $pduData; } /** @@ -86,4 +65,32 @@ class WriteMultipleCoilsPacket return true; } + /** + * @param array $data + * @return array + */ + private static function getDataConvertedToIecWords(array $data) + { + $data_word_stream = array(); + $data_word = 0; + $shift = 0; + for ($i = 0, $len = count($data); $i < $len; $i++) { + if ((($i % 8) === 0) && ($i > 0)) { + //shift to next word + $data_word_stream[] = $data_word; + $shift = 0; + $data_word = 0; + } + $data_word |= (0x01 && $data[$i]) << $shift; + $shift++; + } + $data_word_stream[] = $data_word; + + $pduData = ''; + foreach ($data_word_stream as $key => $dataitem) { + $pduData .= IecType::iecBYTE($dataitem); + } + return array($pduData, count($data_word_stream)); + } + } \ No newline at end of file diff --git a/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php b/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php index bc59828..2d7a178 100644 --- a/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php +++ b/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php @@ -18,4 +18,24 @@ class Fc15WriteMultipleCoilsTest extends MockServerTestCase $packetWithoutTransactionId = substr($clientData[0], 4); $this->assertEquals('00000008000f300000030105', $packetWithoutTransactionId); } + + public function testFc15WriteMultipleCoilsWithMultiWordPacket() + { + $mockResponse = 'a51100000006000f00000020'; + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + $this->assertTrue($modbus->fc15(0, 0, + [ + 1, 0, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + ])); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('0000000b000f0000002004ed0ff0ff', $packetWithoutTransactionId); + } } \ No newline at end of file From 8c482eee3d29197474bea3c616b6fcf774670084 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sun, 27 Nov 2016 22:03:00 +0200 Subject: [PATCH 14/17] add class autoloading to example scripts to they could be used directly --- examples/example_750841_Mmemory.php | 1 + examples/example_datatype.php | 2 ++ examples/example_fc1.php | 1 + examples/example_fc15.php | 2 ++ examples/example_fc16.php | 2 ++ examples/example_fc2.php | 1 + examples/example_fc22.php | 2 ++ examples/example_fc23.php | 2 ++ examples/example_fc3.php | 1 + examples/example_fc4.php | 2 ++ examples/example_fc5.php | 2 ++ examples/example_fc6.php | 2 ++ 12 files changed, 20 insertions(+) diff --git a/examples/example_750841_Mmemory.php b/examples/example_750841_Mmemory.php index 6fa8f90..0650ca5 100644 --- a/examples/example_750841_Mmemory.php +++ b/examples/example_750841_Mmemory.php @@ -1,4 +1,5 @@ Date: Mon, 28 Nov 2016 00:22:42 +0200 Subject: [PATCH 15/17] read more than 2048 bytes from stream --- .travis.yml | 1 - src/ModbusSocket.php | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 90e8fde..6fcd103 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ php: branches: only: - /^dev-.*$/ - - phpunit-support before_install: - travis_retry composer self-update diff --git a/src/ModbusSocket.php b/src/ModbusSocket.php index a1fa61a..479155b 100644 --- a/src/ModbusSocket.php +++ b/src/ModbusSocket.php @@ -150,14 +150,15 @@ class ModbusSocket $read = array($this->streamSocket); $write = null; $except = null; + $data = ''; if (false !== stream_select($read, $write, $except, $readTimeout['sec'], $readTimeout['usec'])) { $this->statusMessages[] = 'Wait data ... '; if (in_array($this->streamSocket, $read, false)) { - $data = fread($this->streamSocket, 2048); // read max 2048 bytes + $data .= fread($this->streamSocket, 2048); // read max 2048 bytes if (!empty($data)) { $this->statusMessages[] = 'Data received'; - return $data; //FIXME what if we are waiting for more than that? + return $data; } $lastAccess = microtime(true); } else { From 95ecf4bf5b7e1f04f85de19fa240e4e3490bd8b0 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Wed, 7 Dec 2016 15:01:12 +0200 Subject: [PATCH 16/17] Rename ModbusSocket to ModbusConnection, support php5.4 --- composer.json | 2 +- src/IOException.php | 11 - src/ModbusMaster.php | 45 ++-- src/Network/IOException.php | 12 ++ .../ModbusConnection.php} | 193 +++--------------- src/Network/ModbusConnectionBuilder.php | 111 ++++++++++ src/Network/ModbusConnectionProperties.php | 120 +++++++++++ tests/ModbusMaster/ModbusExceptionTest.php | 31 +-- tests/PhpType/Bytes2MixedTest.php | 14 +- tests/PhpType/Bytes2RealTest.php | 8 +- tests/PhpType/Bytes2SignedIntTest.php | 14 +- tests/PhpType/Bytes2StringTest.php | 6 +- tests/PhpType/Bytes2UnSignedIntTest.php | 14 +- ...PhpTypeArrayExceptionWithTextArrayTest.php | 14 +- .../PhpTypeArraySizeExceptionsTest.php | 24 ++- 15 files changed, 365 insertions(+), 254 deletions(-) delete mode 100644 src/IOException.php create mode 100644 src/Network/IOException.php rename src/{ModbusSocket.php => Network/ModbusConnection.php} (54%) create mode 100644 src/Network/ModbusConnectionBuilder.php create mode 100644 src/Network/ModbusConnectionProperties.php diff --git a/composer.json b/composer.json index 5730ece..5558ddf 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "description": "PhpModbus with namespaces and updated to PHP 7", "license": "LGPL", "require": { - "php": "^5.5 || ^7.0" + "php": "^5.4 || ^7.0" }, "require-dev": { "react/socket": "~0.4.0", diff --git a/src/IOException.php b/src/IOException.php deleted file mode 100644 index 53e73a9..0000000 --- a/src/IOException.php +++ /dev/null @@ -1,11 +0,0 @@ -setHost($this->host) + ->setPort($this->port) + ->setProtocol($this->socket_protocol) + ->setClient($this->client) + ->setClientPort($this->client_port) + ->setTimeoutSec($this->timeout_sec) + ->setReadTimeoutSec($this->socket_read_timeout_sec) + ->setWriteTimeoutSec($this->socket_write_timeout_sec) + ->setConnectTimeoutSec($this->socket_connect_timeout_sec) + ->build(); + try { - $socket = ModbusSocket::getBuilder() - ->setHost($this->host) - ->setPort($this->port) - ->setSocketProtocol($this->socket_protocol) - ->setClient($this->client) - ->setClientPort($this->client_port) - ->setTimeoutSec($this->timeout_sec) - ->setSocketReadTimeoutSec($this->socket_read_timeout_sec) - ->setSocketWriteTimeoutSec($this->socket_write_timeout_sec) - ->setSocketConnectTimeoutSec($this->socket_connect_timeout_sec) - ->build(); - - $socket->connect(); + $connection->connect(); $packet = $buildRequest(); $this->status .= 'Sending ' . $this->printPacket($packet); - $socket->send($packet); + $connection->send($packet); - $data = $socket->receive(); + $data = $connection->receive(); $this->status .= 'Received ' . $this->printPacket($data); $this->validateResponseCode($data); + + $this->closeConnection($connection); return $parseResponse($data); - } finally { - $this->status .= implode("\n", $socket->getStatusMessages()); - $socket->close(); + } catch (Exception $e) { + $this->closeConnection($connection); + throw $e; } } + private function closeConnection(ModbusConnection $connection) + { + $this->status .= implode("\n", $connection->getStatusMessages()); + $connection->close(); + } + /** * fc15 * diff --git a/src/Network/IOException.php b/src/Network/IOException.php new file mode 100644 index 0000000..f8e70b3 --- /dev/null +++ b/src/Network/IOException.php @@ -0,0 +1,12 @@ +host = $builder->getHost(); + $this->port = $builder->getPort(); + $this->client = $builder->getClient(); + $this->clientPort = $builder->getClientPort(); + $this->timeoutSec = $builder->getTimeoutSec(); + $this->connectTimeoutSec = $builder->getConnectTimeoutSec(); + $this->readTimeoutSec = $builder->getReadTimeoutSec(); + $this->writeTimeoutSec = $builder->getWriteTimeoutSec(); + $this->protocol = $builder->getProtocol(); + } + public static function getBuilder() { - return new ModbusSocketBuilder(); + return new ModbusConnectionBuilder(); } /** @@ -78,15 +53,15 @@ class ModbusSocket * * @return bool * @throws \InvalidArgumentException - * @throws \PHPModbus\IOException + * @throws \PHPModbus\Network\IOException */ public function connect() { $protocol = null; - switch ($this->socket_protocol) { + switch ($this->protocol) { case 'TCP': case 'UDP': - $protocol = strtolower($this->socket_protocol); + $protocol = strtolower($this->protocol); break; default: throw new InvalidArgumentException("Unknown socket protocol, should be 'TCP' or 'UDP'"); @@ -97,7 +72,7 @@ class ModbusSocket // Bind the client stream to a specific local port $opts = array( 'socket' => array( - 'bindto' => "{$this->client}:{$this->client_port}", + 'bindto' => "{$this->client}:{$this->clientPort}", ), ); } @@ -107,7 +82,7 @@ class ModbusSocket "$protocol://$this->host:$this->port", $errno, $errstr, - $this->socket_connect_timeout_sec, + $this->connectTimeoutSec, STREAM_CLIENT_CONNECT, $context ); @@ -124,7 +99,7 @@ class ModbusSocket stream_set_blocking($this->streamSocket, false); // use non-blocking stream - $writeTimeoutParts = $this->secsToSecUsecArray($this->socket_write_timeout_sec); + $writeTimeoutParts = $this->secsToSecUsecArray($this->writeTimeoutSec); // set as stream timeout as we use 'stream_select' to read data and this method has its own timeout // this call will only affect our fwrite parts (send data method) stream_set_timeout($this->streamSocket, $writeTimeoutParts['sec'], $writeTimeoutParts['usec']); @@ -142,10 +117,10 @@ class ModbusSocket */ public function receive() { - $totalReadTimeout = $this->timeout_sec; + $totalReadTimeout = $this->timeoutSec; $lastAccess = microtime(true); - $readTimeout = $this->secsToSecUsecArray($this->socket_read_timeout_sec); + $readTimeout = $this->secsToSecUsecArray($this->readTimeoutSec); while (true) { $read = array($this->streamSocket); $write = null; @@ -234,120 +209,4 @@ class ModbusSocket { return $this->statusMessages; } - -} - - -class ModbusSocketBuilder extends ModbusSocket -{ - /** - * @var ModbusSocket instance to be built - */ - private $modbusSocket; - - public function __construct() - { - $this->modbusSocket = new ModbusSocket(); - } - - /** - * Return built instance of ModbusSocket - * - * @return ModbusSocket built instance - */ - public function build() - { - return $this->modbusSocket; - } - - /** - * @param string $client - * @return ModbusSocketBuilder - */ - public function setClient($client) - { - $this->modbusSocket->client = $client; - return $this; - } - - /** - * @param string $client_port - * @return ModbusSocketBuilder - */ - public function setClientPort($client_port) - { - $this->modbusSocket->client_port = $client_port; - return $this; - } - - /** - * @param float $timeout_sec - * @return ModbusSocketBuilder - */ - public function setTimeoutSec($timeout_sec) - { - $this->modbusSocket->timeout_sec = $timeout_sec; - return $this; - } - - /** - * @param float $socket_read_timeout_sec - * @return ModbusSocketBuilder - */ - public function setSocketReadTimeoutSec($socket_read_timeout_sec) - { - $this->modbusSocket->socket_read_timeout_sec = $socket_read_timeout_sec; - return $this; - } - - /** - * @param float $socket_write_timeout_sec - * @return ModbusSocketBuilder - */ - public function setSocketWriteTimeoutSec($socket_write_timeout_sec) - { - $this->modbusSocket->socket_write_timeout_sec = $socket_write_timeout_sec; - return $this; - } - - /** - * @param string $socket_protocol - * @return ModbusSocketBuilder - */ - public function setSocketProtocol($socket_protocol) - { - $this->modbusSocket->socket_protocol = $socket_protocol; - return $this; - } - - /** - * @param string $host - * @return ModbusSocketBuilder - */ - public function setHost($host) - { - $this->modbusSocket->host = $host; - return $this; - } - - /** - * @param string $port - * @return ModbusSocketBuilder - */ - public function setPort($port) - { - $this->modbusSocket->port = $port; - return $this; - } - - /** - * @param float $socket_connect_timeout_sec - * @return ModbusSocketBuilder - */ - public function setSocketConnectTimeoutSec($socket_connect_timeout_sec) - { - $this->modbusSocket->socket_connect_timeout_sec = $socket_connect_timeout_sec; - return $this; - } - -} +} \ No newline at end of file diff --git a/src/Network/ModbusConnectionBuilder.php b/src/Network/ModbusConnectionBuilder.php new file mode 100644 index 0000000..166436f --- /dev/null +++ b/src/Network/ModbusConnectionBuilder.php @@ -0,0 +1,111 @@ +host)) { + throw new \LogicException('host property can not be left null or empty!'); + } + return new ModbusConnection($this); + } + + /** + * @param string $client + * @return ModbusConnectionBuilder + */ + public function setClient($client) + { + $this->client = $client; + return $this; + } + + /** + * @param string $clientPort + * @return ModbusConnectionBuilder + */ + public function setClientPort($clientPort) + { + $this->clientPort = $clientPort; + return $this; + } + + /** + * @param float $timeoutSec + * @return ModbusConnectionBuilder + */ + public function setTimeoutSec($timeoutSec) + { + $this->timeoutSec = $timeoutSec; + return $this; + } + + /** + * @param float $connectTimeoutSec + * @return ModbusConnectionBuilder + */ + public function setConnectTimeoutSec($connectTimeoutSec) + { + $this->connectTimeoutSec = $connectTimeoutSec; + return $this; + } + + /** + * @param float $readTimeoutSec + * @return ModbusConnectionBuilder + */ + public function setReadTimeoutSec($readTimeoutSec) + { + $this->readTimeoutSec = $readTimeoutSec; + return $this; + } + + /** + * @param float $writeTimeoutSec + * @return ModbusConnectionBuilder + */ + public function setWriteTimeoutSec($writeTimeoutSec) + { + $this->writeTimeoutSec = $writeTimeoutSec; + return $this; + } + + /** + * @param string $protocol + * @return ModbusConnectionBuilder + */ + public function setProtocol($protocol) + { + $this->protocol = $protocol; + return $this; + } + + /** + * @param string $host + * @return ModbusConnectionBuilder + */ + public function setHost($host) + { + $this->host = $host; + return $this; + } + + /** + * @param string $port + * @return ModbusConnectionBuilder + */ + public function setPort($port) + { + $this->port = $port; + return $this; + } +} diff --git a/src/Network/ModbusConnectionProperties.php b/src/Network/ModbusConnectionProperties.php new file mode 100644 index 0000000..05066f1 --- /dev/null +++ b/src/Network/ModbusConnectionProperties.php @@ -0,0 +1,120 @@ +client; + } + + /** + * @return string client port set when binding client to local ip&port + */ + public function getClientPort() + { + return $this->clientPort; + } + + /** + * @return float Total response timeout (seconds, decimals allowed) + */ + public function getTimeoutSec() + { + return $this->timeoutSec; + } + + /** + * @return float maximum timeout when establishing connection (seconds, decimals allowed) + */ + public function getConnectTimeoutSec() + { + return $this->connectTimeoutSec; + } + + /** + * @return float read timeout (seconds, decimals allowed) + */ + public function getReadTimeoutSec() + { + return $this->readTimeoutSec; + } + + /** + * @return float maximum timeout for write operation on connection (seconds, decimals allowed) + */ + public function getWriteTimeoutSec() + { + return $this->writeTimeoutSec; + } + + /** + * @return string network protocol (TCP, UDP) + */ + public function getProtocol() + { + return $this->protocol; + } + + /** + * @return string Modbus device IP address + */ + public function getHost() + { + return $this->host; + } + + /** + * @return string gateway port + */ + public function getPort() + { + return $this->port; + } + +} \ No newline at end of file diff --git a/tests/ModbusMaster/ModbusExceptionTest.php b/tests/ModbusMaster/ModbusExceptionTest.php index ac7e3a4..1e83df9 100644 --- a/tests/ModbusMaster/ModbusExceptionTest.php +++ b/tests/ModbusMaster/ModbusExceptionTest.php @@ -2,40 +2,41 @@ namespace Tests\ModbusMaster; use InvalidArgumentException; -use PHPModbus\IOException; -use PHPModbus\ModbusException; use PHPModbus\ModbusMaster; use PHPModbus\ModbusMasterTcp; class ModbusExceptionTest extends MockServerTestCase { + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Unknown socket protocol, should be 'TCP' or 'UDP' + */ public function testThrowProtocolMismatchException() { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("Unknown socket protocol, should be 'TCP' or 'UDP'"); - $modbus = new ModbusMaster('127.0.0.1', 'Mismatch'); $modbus->readCoils(0, 256, 1); } + /** + * @expectedException \PHPModbus\Network\IOException + * @expectedExceptionMessage Unable to create client socket to + */ public function testPortClosedException() { - $this->expectException(IOException::class); - $this->expectExceptionMessage('Unable to create client socket to'); - $modbus = new ModbusMasterTcp('127.0.0.1'); $modbus->setSocketTimeout(0.2, 0.2); $modbus->readCoils(0, 256, 1); } + /** + * @expectedException \RuntimeException + * @expectedExceptionMessageRegExp /Watchdog time expired \[ 0\.5 sec \]!!! Connection to 127\.0\.0\.1:.* is not established/ + */ public function testTimeoutException() { - $this->expectException(\RuntimeException::class); $mockResponse = '89130000000400010101'; // respond with 1 byte (00000001 bits set) [1] static::executeWithMockServer($mockResponse, function ($port) { - $this->expectExceptionMessage("Watchdog time expired [ 0.5 sec ]!!! Connection to 127.0.0.1:{$port} is not established."); - $modbus = new ModbusMaster('127.0.0.1', 'UDP'); $modbus->port = $port; $modbus->setTimeout(0.5); @@ -45,12 +46,12 @@ class ModbusExceptionTest extends MockServerTestCase }, 'UDP', 1); } - + /** + * @expectedException \PHPModbus\ModbusException + * @expectedExceptionMessage Modbus response error code: 3 (ILLEGAL DATA VALUE) + */ public function testThrowIllegalDataValueException() { - $this->expectException(ModbusException::class); - $this->expectExceptionMessage('Modbus response error code: 3 (ILLEGAL DATA VALUE)'); - $mockResponse = 'da8700000003008303'; // respond with 1 WORD (2 bytes) [0, 3] $clientData = static::executeWithMockServer($mockResponse, function ($port) { $modbus = new ModbusMaster('127.0.0.1', 'TCP'); diff --git a/tests/PhpType/Bytes2MixedTest.php b/tests/PhpType/Bytes2MixedTest.php index fcaa25b..3692910 100644 --- a/tests/PhpType/Bytes2MixedTest.php +++ b/tests/PhpType/Bytes2MixedTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class PhpTypeBytes2Mixed extends TestCase { - const DATA = [ + private $data = [ "0" => 125, // 32098 (DINT) "1" => 98, "2" => 0, @@ -31,15 +31,15 @@ class PhpTypeBytes2Mixed extends TestCase public function testUnsignedInt() { - $this->assertEquals(32098, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + $this->assertEquals(32098, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 4))); } public function testSignedInt() { - $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 4, 4))); - $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 8, 4))); - $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 12, 4))); - $this->assertEquals(-25000, PhpType::bytes2signedInt(array_slice(self::DATA, 16, 2))); - $this->assertEquals(25000, PhpType::bytes2signedInt(array_slice(self::DATA, 18, 2))); + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice($this->data, 4, 4))); + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice($this->data, 8, 4))); + $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice($this->data, 12, 4))); + $this->assertEquals(-25000, PhpType::bytes2signedInt(array_slice($this->data, 16, 2))); + $this->assertEquals(25000, PhpType::bytes2signedInt(array_slice($this->data, 18, 2))); } } diff --git a/tests/PhpType/Bytes2RealTest.php b/tests/PhpType/Bytes2RealTest.php index 2fd7b80..1bb7458 100644 --- a/tests/PhpType/Bytes2RealTest.php +++ b/tests/PhpType/Bytes2RealTest.php @@ -7,7 +7,7 @@ use PHPUnit_Framework_TestCase; class Bytes2Real extends PHPUnit_Framework_TestCase { - const DATA = [ + private $data = [ 0 => 0, 1 => 0, 2 => 68, @@ -24,8 +24,8 @@ class Bytes2Real extends PHPUnit_Framework_TestCase public function testByte2Real() { - $this->assertEquals(1000, PhpType::bytes2float(array_slice(self::DATA, 0, 4))); - $this->assertEquals(2000, PhpType::bytes2float(array_slice(self::DATA, 4, 4))); - $this->assertEquals(1.25, PhpType::bytes2float(array_slice(self::DATA, 8, 4))); + $this->assertEquals(1000, PhpType::bytes2float(array_slice($this->data, 0, 4))); + $this->assertEquals(2000, PhpType::bytes2float(array_slice($this->data, 4, 4))); + $this->assertEquals(1.25, PhpType::bytes2float(array_slice($this->data, 8, 4))); } } diff --git a/tests/PhpType/Bytes2SignedIntTest.php b/tests/PhpType/Bytes2SignedIntTest.php index 362a30d..dcf4467 100644 --- a/tests/PhpType/Bytes2SignedIntTest.php +++ b/tests/PhpType/Bytes2SignedIntTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class Bytes2SignedInt extends TestCase { - const DATA = [ + private $data = [ 0xFF, // -1 0xFF, 0xFF, @@ -31,12 +31,12 @@ class Bytes2SignedInt extends TestCase public function testByte2SignedInt() { - $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 0, 2))); - $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 0, 4))); + $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice($this->data, 0, 2))); + $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice($this->data, 0, 4))); - $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 4, 4))); - $this->assertEquals(1, PhpType::bytes2signedInt(array_slice(self::DATA, 8, 4))); - $this->assertEquals(-2147483648, PhpType::bytes2signedInt(array_slice(self::DATA, 12, 4))); - $this->assertEquals(2147483647, PhpType::bytes2signedInt(array_slice(self::DATA, 16, 4))); + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice($this->data, 4, 4))); + $this->assertEquals(1, PhpType::bytes2signedInt(array_slice($this->data, 8, 4))); + $this->assertEquals(-2147483648, PhpType::bytes2signedInt(array_slice($this->data, 12, 4))); + $this->assertEquals(2147483647, PhpType::bytes2signedInt(array_slice($this->data, 16, 4))); } } diff --git a/tests/PhpType/Bytes2StringTest.php b/tests/PhpType/Bytes2StringTest.php index 8b5d788..851c331 100644 --- a/tests/PhpType/Bytes2StringTest.php +++ b/tests/PhpType/Bytes2StringTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class Bytes2String extends TestCase { - const DATA = [ // String "Hello word!" + private $data = [ // String "Hello word!" 0x48, //H 0x65, //e 0x6c, //l @@ -26,7 +26,7 @@ class Bytes2String extends TestCase public function testBytesToString() { - $this->assertEquals('eHll oowlr!da', PhpType::bytes2string(self::DATA)); - $this->assertEquals('Hello world!', PhpType::bytes2string(self::DATA, true)); + $this->assertEquals('eHll oowlr!da', PhpType::bytes2string($this->data)); + $this->assertEquals('Hello world!', PhpType::bytes2string($this->data, true)); } } diff --git a/tests/PhpType/Bytes2UnSignedIntTest.php b/tests/PhpType/Bytes2UnSignedIntTest.php index 2c424fc..14e2698 100644 --- a/tests/PhpType/Bytes2UnSignedIntTest.php +++ b/tests/PhpType/Bytes2UnSignedIntTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class Bytes2UnSignedIntTest extends TestCase { - const DATA = [ + private $data = [ 0xFF, // -1 0xFF, 0xFF, @@ -31,12 +31,12 @@ class Bytes2UnSignedIntTest extends TestCase public function testByte2SignedInt() { - $this->assertEquals(65535, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2))); - $this->assertEquals(4294967295, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + $this->assertEquals(65535, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 2))); + $this->assertEquals(4294967295, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 4))); - $this->assertEquals(0, PhpType::bytes2unsignedInt(array_slice(self::DATA, 4, 4))); - $this->assertEquals(1, PhpType::bytes2unsignedInt(array_slice(self::DATA, 8, 4))); - $this->assertEquals(2147483648, PhpType::bytes2unsignedInt(array_slice(self::DATA, 12, 4))); - $this->assertEquals(2147483647, PhpType::bytes2unsignedInt(array_slice(self::DATA, 16, 4))); + $this->assertEquals(0, PhpType::bytes2unsignedInt(array_slice($this->data, 4, 4))); + $this->assertEquals(1, PhpType::bytes2unsignedInt(array_slice($this->data, 8, 4))); + $this->assertEquals(2147483648, PhpType::bytes2unsignedInt(array_slice($this->data, 12, 4))); + $this->assertEquals(2147483647, PhpType::bytes2unsignedInt(array_slice($this->data, 16, 4))); } } diff --git a/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php b/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php index 0565a13..62efe58 100644 --- a/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php +++ b/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php @@ -6,22 +6,26 @@ use PHPUnit\Framework\TestCase; class PhpTypeArrayExceptionWithTextArrayTest extends TestCase { - const DATA = [ + private $data = [ "0" => 100, // 32098 (DINT) "1" => "e", "2" => 0, "3" => 0 ]; + /** + * @expectedException \Exception + */ public function testExceptionWhenSize2ContainsString() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 2)); } + /** + * @expectedException \Exception + */ public function testExceptionWhenSize4ContainsString() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 4)); } } diff --git a/tests/PhpType/PhpTypeArraySizeExceptionsTest.php b/tests/PhpType/PhpTypeArraySizeExceptionsTest.php index 6003ad4..1782fc2 100644 --- a/tests/PhpType/PhpTypeArraySizeExceptionsTest.php +++ b/tests/PhpType/PhpTypeArraySizeExceptionsTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class PhpTypeArraySizeExceptionsTest extends TestCase { - const DATA = [ + private $data = [ "0" => 100, // 32098 (DINT) "1" => 2, "2" => 0, @@ -15,32 +15,38 @@ class PhpTypeArraySizeExceptionsTest extends TestCase "5" => 2 ]; + /** + * @expectedException \Exception + */ public function testExceptionWhenSizeShort() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 1)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 1)); } + /** + * @expectedException \Exception + */ public function testExceptionWhenSizeShort3() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 3)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 3)); } + /** + * @expectedException \Exception + */ public function testExceptionWhenSizeLong() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 5)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 5)); } public function testNoExceptionWhenSize2() { - $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2))); + $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 2))); } public function testNoExceptionWhenSize4() { - $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 4))); } } \ No newline at end of file From 441635e6aebfa66f6b1dd3a57d9d725161f40624 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Wed, 7 Dec 2016 15:07:00 +0200 Subject: [PATCH 17/17] add php 5.4 / 5.5 to travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6fcd103..265e73a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ cache: - $HOME/.composer/cache php: + - 5.4 + - 5.5 - 5.6 - 7.0