- Modbus TCP/IP specification: http://www.modbus.org/specs.php
- Modbus TCP/IP and RTU simpler description: http://www.simplymodbus.ca/TCP.htm
Use Composer to install this library as dependency.
composer require aldas/modbus-tcp-client
- FC1 - Read Coils (ReadCoilsRequest / ReadCoilsResponse)
- FC2 - Read Input Discretes (ReadInputDiscretesRequest / ReadInputDiscretesResponse)
- FC3 - Read Holding Registers (ReadHoldingRegistersRequest / ReadHoldingRegistersResponse)
- FC4 - Read Input Registers (ReadInputRegistersRequest / ReadInputRegistersResponse)
- FC5 - Write Single Coil (WriteSingleCoilRequest / WriteSingleCoilResponse)
- FC6 - Write Single Register (WriteSingleRegisterRequest / WriteSingleRegisterResponse)
- FC15 - Write Multiple Coils (WriteMultipleCoilsRequest / WriteMultipleCoilsResponse)
- FC16 - Write Multiple Registers (WriteMultipleRegistersRequest / WriteMultipleRegistersResponse)
- FC22 - Mask Write Register (MaskWriteRegisterRequest / MaskWriteRegisterResponse)
- FC23 - Read / Write Multiple Registers (ReadWriteMultipleRegistersRequest / ReadWriteMultipleRegistersResponse)
- Packet::isCompleteLength - checks if data is complete Modbus TCP packet
- Packet::isCompleteLengthRTU() - checks if data is complete Modbus RTU packet
- ErrorResponse::is - checks if data is Modbus TCP error packet
- PHP 8.0+
- Release 2.4.0 was last to support PHP 7 (7.4 might work with v3.0.0)
- Release 0.2.0 was last to support PHP 5.6
This library is influenced by phpmodbus library and meant to be provide decoupled Modbus protocol (request/response packets) and networking related features so you could build modbus client with our own choice of networking code (ext_sockets/streams/Reactphp/Amp asynchronous streams) or use library provided networking classes (php Streams)
Applies to multibyte data that are stored in Word/Double/Quad word registers basically everything that is not (u)int16/byte/char.
So if we receive from network 0x12345678 (bytes: ABCD) and want to convert that to a 32 bit register there could be 4 different ways to interpret bytes and word order depending on modbus server architecture and client architecture. NB: TCP, and UDP, are transmitted in big-endian order so we choose this as base for examples
Library supports following byte and word orders:
- Big endian (ABCD - word1 = 0x1234, word2 = 0x5678)
- Big endian low word first (CDAB - word1 = 0x5678, word2 = 0x1234) (used by Wago-750)
- Little endian (DCBA - word1 = 0x3412, word2 = 0x7856)
- Little endian low word first (BADC - word1 = 0x7856, word2 = 0x3412)
Default (global) endianess used for parsing can be changed with:
Endian::$defaultEndian = Endian::BIG_ENDIAN_LOW_WORD_FIRST;
For non-global cases see API methods argument list if method support using custom endianess.
See Endian.php for additional info and Types.php for supported data types.
Some of the Modbus function examples are in examples/ folder
Advanced usage:
- command line poller with ReachPHP examples/example_cli_poller.php
- send/recieve packets parallel using non-blocking IO:
- using ReactPHP see 'examples/example_parallel_requests_reactphp.php'
- using Amp see 'examples/example_parallel_requests_amp.php'
Request multiple packets with higher level API:
$address = 'tcp://127.0.0.1:5022';
$unitID = 0; // also known as 'slave ID'
$fc3 = ReadRegistersBuilder::newReadHoldingRegisters($address, $unitID)
->bit(256, 15, 'pump2_feedbackalarm_do')
// will be split into 2 requests as 1 request can return only range of 124 registers max
->int16(657, 'battery3_voltage_wo')
// will be another request as uri is different for subsequent int16 register
->useUri('tcp://127.0.0.1:5023')
->string(
669,
10,
'username_plc2',
function ($value, $address, $response) {
return 'prefix_' . $value; // optional: transform value after extraction
},
function (\Exception $exception, Address $address, $response) {
// optional: callback called then extraction failed with an error
return $address->getType() === Address::TYPE_STRING ? '' : null; // does not make sense but gives you an idea
}
)
->build(); // returns array of 3 ReadHoldingRegistersRequest requests
// this will use PHP non-blocking stream io to recieve responses
$responseContainer = (new NonBlockingClient(['readTimeoutSec' => 0.2]))->sendRequests($fc3);
print_r($responseContainer->getData()); // array of assoc. arrays (keyed by address name)
print_r($responseContainer->getErrors());
Response structure
[
[ 'pump2_feedbackalarm_do' => true, ],
[ 'battery3_voltage_wo' => 12, ],
[ 'username_plc2' => 'prefix_admin', ]
]
Low level - send packets:
$connection = BinaryStreamConnection::getBuilder()
->setHost('192.168.0.1')
->build();
$packet = new ReadHoldingRegistersRequest(256, 8); //create FC3 request packet
try {
$binaryData = $connection->connect()->sendAndReceive($packet);
//parse binary data to response object
$response = ResponseFactory::parseResponseOrThrow($binaryData);
//same as 'foreach ($response->getWords() as $word) {'
foreach ($response as $word) {
print_r($word->getInt16());
}
// print registers as double words in big endian low word first order (as WAGO-750 does)
foreach ($response->getDoubleWords() as $dword) {
print_r($dword->getInt32(Endian::BIG_ENDIAN_LOW_WORD_FIRST));
}
// set internal index to match start address to simplify array access
$responseWithStartAddress = $response->withStartAddress(256);
print_r($responseWithStartAddress[256]->getBytes()); // use array access to get word
print_r($responseWithStartAddress->getDoubleWordAt(257)->getFloat());
} catch (Exception $exception) {
echo $exception->getMessage() . PHP_EOL;
} finally {
$connection->close();
}
Difference between Modbus RTU and Modbus TCP is that:
- RTU header contains only slave id. TCP/IP header contains of transaction id, protocol id, length, unitid
- RTU packed has 2 byte CRC appended
See http://www.simplymodbus.ca/TCP.htm for more detailsed explanation
This library was/is originally meant for Modbus TCP but it has support to convert packet to RTU and from RTU. See this examples/rtu.php for example.
$rtuBinaryPacket = RtuConverter::toRtu(new ReadHoldingRegistersRequest($startAddress, $quantity, $slaveId));
$binaryData = $connection->connect()->sendAndReceive($rtuBinaryPacket);
$responseAsTcpPacket = RtuConverter::fromRtu($binaryData);
See Linux example in 'examples/rtu_usb_to_serial.php'
See example in 'examples/rtu_over_tcp_with_higherlevel_api.php'
- 'examples/example_parallel_requests_reactphp.php - example of non-blocking socket IO with ReactPHP socket library (https://github.com/reactphp/socket)
- 'examples/example_parallel_requests_amp.php - example of non-blocking socket IO with Amp socket library https://github.com/amphp/socket
- 'examples/example_response_server.php - example of modbus server
Examples folder has index.php which can be used with php built-in web server to test out communication with our own PLCs.
git clone https://github.com/aldas/modbus-tcp-client.git
cd modbus-tcp-client
composer install
php -S localhost:8080 -t examples/
Now open http://localhost:8080 in browser. See additional query parameters from index.php.
See CHANGELOG.md
- all
composer test
- unit tests
composer test-unit
- integration tests
composer test-integration
For Windows users:
- all
vendor/bin/phpunit
- unit tests
vendor/bin/phpunit --testsuite 'unit-tests'
- integration tests
vendor/bin/phpunit --testsuite 'integration-tests'
Run PHPStan analysis compose check