2017 © Pedro Peláez
 

library modbus-tcp-client

Modbus TCP protocol client library

image

aldas/modbus-tcp-client

Modbus TCP protocol client library

  • Saturday, July 28, 2018
  • by contrerasojuanc
  • Repository
  • 5 Watchers
  • 21 Stars
  • 231 Installations
  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 5 Forks
  • 3 Open issues
  • 8 Versions
  • 93 % Grown

The README.md

Modbus TCP and RTU over TCP protocol client

Latest Version Packagist Software License codecov, (*1)

  • Modbus TCP/IP specification: http://www.modbus.org/specs.php
  • Modbus TCP/IP and RTU simpler description: http://www.simplymodbus.ca/TCP.htm

Installation

Use Composer to install this library as dependency., (*2)

composer require aldas/modbus-tcp-client

Supported functions

Utility functions

Requirements

  • 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

Intention

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), (*3)

Addresses

This library uses PDU / protocol level address numbers (0 is very first register/coil). This is different from numbering schemes (0xxxxx, 1xxxxx, 3xxxxx, and 4xxxxx). The x denotes coil or register address starting from 1., (*4)

So when your documentation gives you address as 300102 you should interpret it in this library context as: FC3, address 101, (*5)

Endianness

Applies to multibyte data that are stored in Word/Double/Quad word registers basically everything that is not (u)int16/byte/char., (*6)

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, (*7)

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), (*8)

Default (global) endianess used for parsing can be changed with:, (*9)

Endian::$defaultEndian = Endian::BIG_ENDIAN_LOW_WORD_FIRST;

For non-global cases see API methods argument list if method support using custom endianess., (*10)

See Endian.php for additional info and Types.php for supported data types., (*11)

Data types

Modbus is binary protocol which revolves about addresses of Registers/Word (16bit, 2 byte of data) and Coils (1 bit of data). Coils are booleans but Register/Word or multiple Registers can hold different data types. Following is ways to access different data types from Registers:, (*12)

Most of the for data types have optional arguments for providing Endian type. By default data is parsed as being Big Endian Low Word first endian., (*13)

Exaple: getting uint32 value as Little Endian Low Word First, (*14)

$dword->getUInt32(Endian::LITTLE_ENDIAN | Endian::LOW_WORD_FIRST);

1bit - 16bit data types

1-16bit data types are hold by Word class which hold 2 bytes of data., (*15)

$address = 100;
$word = $response->getWordAt($address);

Following methods exists to get different types out of single Word instance: * boolean - 1bit, true/false, $word->isBitSet(11) * byte - 8bit, 1 byte, range 0 to 255 * first byte of Word (0) $word->getHighByteAsInt() * last byte of Word (1) $word->getLowByteAsInt() * uint16 - 16bit, 2 byte, range 0 to 65535 $word->getUInt16() * int16 - 16bit, 2 byte, range -32768 to 32767 $word->getInt16(), (*16)

and following additional methods: * $word->getBytes() return Words as array of 2 integers (0-255), (*17)

32bit data types

17-32bit data types are hold by DoubleWord class which hold 4 bytes of data., (*18)

$address = 100;
$dword = $response->getDoubleWordAt($address);

Following methods exists to get different types out of single DoubleWord instance: * uint32 - 32bit, 4 bytes, range 0 to 4294967295, $dword->getUInt32() * int32 - 64bit, 8 bytes, range -2147483648 to 2147483647, $dword->getInt32() * float - 64bit, 8 bytes, range -3.4e+38 to 3.4e+38, $dword->getFloat(), (*19)

and following additional methods: * $dword->getBytes() return DoubleWord as array of 4 integers (0-255) * $dword->getHighBytesAsWord() returns first 2 bytes as Word * $dword->getLowBytesAsWord() returns last 2 bytes as Word, (*20)

64bit data types

64bit data types are hold by QuadWord class which hold 8 bytes of data. NB: 64-bit PHP supports only up to 63-bit (signed) integers., (*21)

$address = 100;
$qword = $response->getQuadWordAt($address);

Following methods exists to get different types out of single QuadWord instance: * uint32 - 64bit, 8 bytes, range 0 to 9223372036854775807, $dword->getUInt64() * int32 - 64bit, 8 bytes, range -9223372036854775808 to 9223372036854775807, $dword->getInt64() * double - 64bit, 8 bytes, range 2.2250738585072e-308 to 1.7976931348623e+308, $dword->getDouble(), (*22)

and following additional methods: * $qword->getBytes() return QuadWord as array of 8 integers (0-255) * $qword->getHighBytesAsDoubleWord() returns first 4 bytes as DoubleWord * $qword->getLowBytesAsDoubleWord() returns last 4 bytes as DoubleWord, (*23)

Strings

ASCII (8bit character) string can be extracted from response as utf-8 string, (*24)

$address = 20;
$length = 10;
$string = $response->getAsciiStringAt($address, $length);

Example of Modbus TCP (fc3 - read holding registers)

Some of the Modbus function examples are in examples/ folder, (*25)

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', (*26)

Request multiple packets with higher level API:, (*27)

$address = 'tcp://127.0.0.1:5022';
$unitID = 0; // also known as 'slave ID'
$fc3 = ReadRegistersBuilder::newReadHoldingRegisters($address, $unitID)
    ->unaddressableRanges([[100,110], [1512]])
    ->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, (*28)

[
    [ 'pump2_feedbackalarm_do' => true, ],
    [ 'battery3_voltage_wo' => 12, ],
    [ 'username_plc2' => 'prefix_admin', ]
]

Low level - send packets:, (*29)

$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();
}

Example of Modbus RTU over TCP

Difference between Modbus RTU and Modbus TCP is that:, (*30)

  1. RTU header contains only slave id. TCP/IP header contains of transaction id, protocol id, length, unitid
  2. RTU packed has 2 byte CRC appended

See http://www.simplymodbus.ca/TCP.htm for more detailsed explanation, (*31)

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., (*32)

$rtuBinaryPacket = RtuConverter::toRtu(new ReadHoldingRegistersRequest($startAddress, $quantity, $slaveId));
$binaryData = $connection->connect()->sendAndReceive($rtuBinaryPacket);
$responseAsTcpPacket = RtuConverter::fromRtu($binaryData);

Example of Modbus RTU over USB to Serial (RS485) adapter

See Linux example in 'examples/rtu_usb_to_serial.php', (*33)

Example of Modbus RTU over TCP + higher level API usage

See example in 'examples/rtu_over_tcp_with_higherlevel_api.php', (*34)

Example of non-blocking socket IO with ReactPHP/Amp (i.e. modbus request are run in 'parallel')

Example Modbus server (accepting requests) with ReactPHP

Try communication with PLCs quickly using php built-in web server

Examples folder has index.php which can be used with php built-in web server to test out communication with our own PLCs., (*35)

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., (*36)

Changelog

See CHANGELOG.md, (*37)

Tests

  • 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', (*38)

Static analysis

Run PHPStan analysis compose check, (*39)

The Versions

28/07 2018

dev-master

9999999-dev

Modbus TCP protocol client library

  Sources   Download

Apache-2.0 Apache2

The Requires

  • php ^7.0

 

The Development Requires

modbus

28/07 2018

1.0.1

1.0.1.0

Modbus TCP protocol client library

  Sources   Download

Apache-2.0

The Requires

  • php ^7.0

 

The Development Requires

modbus

02/06 2018

1.0.0-rc

1.0.0.0-RC

Modbus TCP protocol client library

  Sources   Download

Apache-2.0

The Requires

  • php ^7.0

 

The Development Requires

modbus

14/05 2018

0.2.1

0.2.1.0

Modbus TCP protocol client library

  Sources   Download

Apache-2.0

The Requires

 

The Development Requires

modbus

30/04 2018

0.2.0

0.2.0.0

Modbus TCP protocol client library

  Sources   Download

Apache-2.0

The Requires

 

The Development Requires

modbus

30/03 2018

0.1.1

0.1.1.0

Modbus TCP protocol client library

  Sources   Download

Apache-2.0

The Requires

 

The Development Requires

modbus

15/11 2017

0.1.0

0.1.0.0

Modbus TCP protocol client library

  Sources   Download

Apache2

The Requires

 

The Development Requires

modbus

10/10 2017

0.0.1

0.0.1.0

Modbus TCP protocol client library

  Sources   Download

Apache2

The Requires

 

The Development Requires

modbus