{"id":39596244,"url":"https://github.com/aldas/modbus-tcp-client","last_synced_at":"2026-01-18T07:48:54.255Z","repository":{"id":14063950,"uuid":"75876497","full_name":"aldas/modbus-tcp-client","owner":"aldas","description":"PHP client for Modbus TCP and Modbus RTU over TCP (can be used for serial)","archived":false,"fork":false,"pushed_at":"2024-12-23T08:47:44.000Z","size":573,"stargazers_count":210,"open_issues_count":3,"forks_count":59,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-10-21T07:37:52.144Z","etag":null,"topics":["modbus","modbus-client","modbus-rtu","modbus-serial","modbus-tcp","php"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aldas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-07T21:18:40.000Z","updated_at":"2025-10-15T06:32:03.000Z","dependencies_parsed_at":"2024-06-21T01:05:46.631Z","dependency_job_id":"2a834d1b-96c9-44e4-8f28-9a90a15c8fbb","html_url":"https://github.com/aldas/modbus-tcp-client","commit_stats":{"total_commits":136,"total_committers":5,"mean_commits":27.2,"dds":0.02941176470588236,"last_synced_commit":"3d6af61c15de90aa3655b6883b42f5c30e7d7ece"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/aldas/modbus-tcp-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldas%2Fmodbus-tcp-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldas%2Fmodbus-tcp-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldas%2Fmodbus-tcp-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldas%2Fmodbus-tcp-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aldas","download_url":"https://codeload.github.com/aldas/modbus-tcp-client/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldas%2Fmodbus-tcp-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28533172,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"online","status_checked_at":"2026-01-18T02:00:07.578Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["modbus","modbus-client","modbus-rtu","modbus-serial","modbus-tcp","php"],"created_at":"2026-01-18T07:48:53.683Z","updated_at":"2026-01-18T07:48:54.234Z","avatar_url":"https://github.com/aldas.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Modbus TCP and RTU over TCP protocol client\n\n[![Latest Version](https://img.shields.io/packagist/v/aldas/modbus-tcp-client.svg)](https://packagist.org/packages/aldas/modbus-tcp-client)\n[![Packagist](https://img.shields.io/packagist/dm/aldas/modbus-tcp-client.svg)](https://packagist.org/packages/aldas/modbus-tcp-client)\n[![Software License](https://img.shields.io/packagist/l/aldas/modbus-tcp-client.svg)](LICENSE)\n[![codecov](https://codecov.io/gh/aldas/modbus-tcp-client/branch/master/graph/badge.svg)](https://codecov.io/gh/aldas/modbus-tcp-client)\n\n* Modbus TCP/IP specification: http://www.modbus.org/specs.php\n* Modbus TCP/IP and RTU simpler description: http://www.simplymodbus.ca/TCP.htm\n## Installation\n\nUse [Composer](https://getcomposer.org/) to install this library as dependency.\n```bash\ncomposer require aldas/modbus-tcp-client\n```\n\n## Supported functions\n\n* FC1 - Read Coils ([ReadCoilsRequest](src/Packet/ModbusFunction/ReadCoilsRequest.php) / [ReadCoilsResponse](src/Packet/ModbusFunction/ReadCoilsResponse.php))\n* FC2 - Read Input Discretes ([ReadInputDiscretesRequest](src/Packet/ModbusFunction/ReadInputDiscretesRequest.php) / [ReadInputDiscretesResponse](src/Packet/ModbusFunction/ReadInputDiscretesResponse.php))\n* FC3 - Read Holding Registers ([ReadHoldingRegistersRequest](src/Packet/ModbusFunction/ReadHoldingRegistersRequest.php) / [ReadHoldingRegistersResponse](src/Packet/ModbusFunction/ReadHoldingRegistersResponse.php))\n* FC4 - Read Input Registers ([ReadInputRegistersRequest](src/Packet/ModbusFunction/ReadInputRegistersRequest.php) / [ReadInputRegistersResponse](src/Packet/ModbusFunction/ReadInputRegistersResponse.php))\n* FC5 - Write Single Coil ([WriteSingleCoilRequest](src/Packet/ModbusFunction/WriteSingleCoilRequest.php) / [WriteSingleCoilResponse](src/Packet/ModbusFunction/WriteSingleCoilResponse.php))\n* FC6 - Write Single Register ([WriteSingleRegisterRequest](src/Packet/ModbusFunction/WriteSingleRegisterRequest.php) / [WriteSingleRegisterResponse](src/Packet/ModbusFunction/WriteSingleRegisterResponse.php))\n* FC11 - Get Communication Event Counter ([GetCommEventCounterRequest](src/Packet/ModbusFunction/GetCommEventCounterRequest.php) / [GetCommEventCounterResponse](src/Packet/ModbusFunction/GetCommEventCounterResponse.php))\n* FC15 - Write Multiple Coils ([WriteMultipleCoilsRequest](src/Packet/ModbusFunction/WriteMultipleCoilsRequest.php) / [WriteMultipleCoilsResponse](src/Packet/ModbusFunction/WriteMultipleCoilsResponse.php))\n* FC16 - Write Multiple Registers ([WriteMultipleRegistersRequest](src/Packet/ModbusFunction/WriteMultipleRegistersRequest.php) / [WriteMultipleRegistersResponse](src/Packet/ModbusFunction/WriteMultipleRegistersResponse.php))\n* FC17 - Report Server ID ([ReportServerIDRequest](src/Packet/ModbusFunction/ReportServerIDRequest.php) / [ReportServerIDResponse](src/Packet/ModbusFunction/ReportServerIDResponse.php))\n* FC22 - Mask Write Register ([MaskWriteRegisterRequest](src/Packet/ModbusFunction/MaskWriteRegisterRequest.php) / [MaskWriteRegisterResponse](src/Packet/ModbusFunction/MaskWriteRegisterResponse.php))\n* FC23 - Read / Write Multiple Registers ([ReadWriteMultipleRegistersRequest](src/Packet/ModbusFunction/ReadWriteMultipleRegistersRequest.php) / [ReadWriteMultipleRegistersResponse](src/Packet/ModbusFunction/ReadWriteMultipleRegistersResponse.php))\n\n### Utility functions\n\n* [Packet::isCompleteLength](src/Utils/Packet.php) - checks if data is complete Modbus TCP request or response packet\n* [Packet::isCompleteLengthRTU()](src/Utils/Packet.php) - checks if data is complete Modbus RTU response packet\n* [ErrorResponse::is](src/Packet/ErrorResponse.php) - checks if data is Modbus TCP error packet\n\n## Requirements\n\n* PHP 8.0+\n* Release [2.4.0](https://github.com/aldas/modbus-tcp-client/tree/2.4.0) was last to support PHP 7 (7.4 might work with v3.0.0)\n* Release [0.2.0](https://github.com/aldas/modbus-tcp-client/tree/0.2.0) was last to support PHP 5.6\n\n## Intention\nThis library is influenced by [phpmodbus](https://github.com/adduc/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)\n\n## Addresses\n\nThis library uses PDU / protocol level address numbers (`0` is very first register/coil). This is different from numbering \nschemes (`0xxxxx`, `1xxxxx`, `3xxxxx`, and `4xxxxx`). The `x` denotes coil or register address starting from `1`.\n\nSo when your documentation gives you address as `300102` you should interpret it in this library context as: FC3, address `101`\n\n## Endianness\nApplies to multibyte data that are stored in Word/Double/Quad word registers basically everything\nthat is not (u)int16/byte/char. \n\nSo if we receive from network 0x12345678 (bytes: ABCD) and want to convert that to a 32 bit register there could be 4 different \nways to interpret bytes and word order depending on modbus server architecture and client architecture.\nNB: TCP, and UDP, are transmitted in big-endian order so we choose this as base for examples\n\nLibrary supports following byte and word orders:\n* Big endian (ABCD - word1 = 0x1234, word2 = 0x5678) \n* Big endian low word first (CDAB - word1 = 0x5678, word2 = 0x1234) (used by Wago-750)\n* Little endian (DCBA - word1 = 0x3412, word2 = 0x7856)\n* Little endian low word first (BADC - word1 = 0x7856, word2 = 0x3412)\n\nDefault (global) endianess used for parsing can be changed with:\n```php\nEndian::$defaultEndian = Endian::BIG_ENDIAN_LOW_WORD_FIRST;\n```\n\nFor non-global cases see API methods argument list if method support using custom endianess.\n\nSee [Endian.php](src/Utils/Endian.php) for additional info and [Types.php](src/Utils/Types.php) for supported data types.\n\n\n## Data types\n\nModbus is binary protocol which revolves about addresses of Registers/Word (16bit, 2 byte of data) and Coils (1 bit of data).\nCoils are booleans but Register/Word or multiple Registers can hold different data types. Following is ways to access different\ndata types from Registers:\n\nMost 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.\n\nExaple: getting uint32 value as Little Endian Low Word First\n```php\n$dword-\u003egetUInt32(Endian::LITTLE_ENDIAN | Endian::LOW_WORD_FIRST);\n```\n\n### 1bit - 16bit data types\n\n1-16bit data types are hold by `Word` class which hold 2 bytes of data.\n```php\n$address = 100;\n$word = $response-\u003egetWordAt($address);\n```\n\nFollowing methods exists to get different types out of single `Word` instance:\n* `boolean` - 1bit, true/false, `$word-\u003eisBitSet(11)`\n* `byte` - 8bit, 1 byte, range 0 to 255\n  * first byte of Word (0) `$word-\u003egetHighByteAsInt()`\n  * last byte of Word (1) `$word-\u003egetLowByteAsInt()`\n* `uint16` - 16bit, 2 byte, range 0 to 65535 `$word-\u003egetUInt16()`\n* `int16` - 16bit, 2 byte, range -32768 to 32767 `$word-\u003egetInt16()`\n\nand following additional methods:\n* `$word-\u003egetBytes()` return Words as array of 2 integers (0-255)\n\n\n### 32bit data types\n\n17-32bit data types are hold by `DoubleWord` class which hold 4 bytes of data.\n```php\n$address = 100;\n$dword = $response-\u003egetDoubleWordAt($address);\n```\n\nFollowing methods exists to get different types out of single `DoubleWord` instance:\n* `uint32` - 32bit, 4 bytes, range 0 to 4294967295, `$dword-\u003egetUInt32()`\n* `int32` - 64bit, 8 bytes, range -2147483648 to 2147483647, `$dword-\u003egetInt32()`\n* `float` - 64bit, 8 bytes, range -3.4e+38 to 3.4e+38, `$dword-\u003egetFloat()`\n\nand following additional methods:\n* `$dword-\u003egetBytes()` return DoubleWord as array of 4 integers (0-255)\n* `$dword-\u003egetHighBytesAsWord()` returns first 2 bytes as `Word`\n* `$dword-\u003egetLowBytesAsWord()` returns last 2 bytes as `Word` \n\n\n### 64bit data types\n\n64bit data types are hold by `QuadWord` class which hold 8 bytes of data.\nNB: 64-bit PHP supports only up to 63-bit (signed) integers.\n```php\n$address = 100;\n$qword = $response-\u003egetQuadWordAt($address);\n```\n\nFollowing methods exists to get different types out of single `QuadWord` instance:\n* `uint32` - 64bit, 8 bytes, range 0 to 9223372036854775807, `$dword-\u003egetUInt64()`\n* `int32` - 64bit, 8 bytes, range -9223372036854775808 to 9223372036854775807, `$dword-\u003egetInt64()`\n* `double` - 64bit, 8 bytes, range 2.2250738585072e-308 to 1.7976931348623e+308, `$dword-\u003egetDouble()`\n\nand following additional methods:\n* `$qword-\u003egetBytes()` return `QuadWord` as array of 8 integers (0-255)\n* `$qword-\u003egetHighBytesAsDoubleWord()` returns first 4 bytes as `DoubleWord`\n* `$qword-\u003egetLowBytesAsDoubleWord()` returns last 4 bytes as `DoubleWord`\n\n\n### Strings\n\nASCII (8bit character) string can be extracted from response as utf-8 string\n```php\n$address = 20;\n$length = 10;\n$string = $response-\u003egetAsciiStringAt($address, $length);\n```\n\n\n## Example of Modbus TCP (fc3 - read holding registers)\n\nSome of the Modbus function examples are in [examples/](examples) folder\n\nAdvanced usage:\n* command line poller with ReachPHP [examples/example_cli_poller.php](examples/example_cli_poller.php)\n* send/recieve packets parallel using non-blocking IO:\n  * using [ReactPHP](https://reactphp.org/) see 'examples/[example_parallel_requests_reactphp.php](examples/example_parallel_requests_reactphp.php)'\n  * using [Amp](https://amphp.org/amp/) see 'examples/[example_parallel_requests_amp.php](examples/example_parallel_requests_amp.php)'\n\nRequest multiple packets with higher level API:\n```php\n$address = 'tcp://127.0.0.1:5022';\n$unitID = 0; // also known as 'slave ID'\n$fc3 = ReadRegistersBuilder::newReadHoldingRegisters($address, $unitID)\n    -\u003eunaddressableRanges([[100,110], [1512]])\n    -\u003ebit(256, 15, 'pump2_feedbackalarm_do')\n    // will be split into 2 requests as 1 request can return only range of 124 registers max\n    -\u003eint16(657, 'battery3_voltage_wo')\n    // will be another request as uri is different for subsequent int16 register\n    -\u003euseUri('tcp://127.0.0.1:5023')\n    -\u003estring(\n        669,\n        10,\n        'username_plc2',\n        function ($value, $address, $response) {\n            return 'prefix_' . $value; // optional: transform value after extraction\n        },\n        function (\\Exception $exception, Address $address, $response) {\n            // optional: callback called then extraction failed with an error\n            return $address-\u003egetType() === Address::TYPE_STRING ? '' : null; // does not make sense but gives you an idea\n        }\n    )\n    -\u003ebuild(); // returns array of 3 ReadHoldingRegistersRequest requests\n\n// this will use PHP non-blocking stream io to recieve responses\n$responseContainer = (new NonBlockingClient(['readTimeoutSec' =\u003e 0.2]))-\u003esendRequests($fc3);\nprint_r($responseContainer-\u003egetData()); // array of assoc. arrays (keyed by address name)\nprint_r($responseContainer-\u003egetErrors());\n```\nResponse structure\n```php\n[\n    [ 'pump2_feedbackalarm_do' =\u003e true, ],\n    [ 'battery3_voltage_wo' =\u003e 12, ],\n    [ 'username_plc2' =\u003e 'prefix_admin', ]\n]\n```\n\nLow level - send packets:\n```php\n$connection = BinaryStreamConnection::getBuilder()\n    -\u003esetHost('192.168.0.1')\n    -\u003ebuild();\n    \n$packet = new ReadHoldingRegistersRequest(256, 8); //create FC3 request packet\n\ntry {\n    $binaryData = $connection-\u003econnect()-\u003esendAndReceive($packet);\n\n    //parse binary data to response object\n    $response = ResponseFactory::parseResponseOrThrow($binaryData);\n    \n    //same as 'foreach ($response-\u003egetWords() as $word) {'\n    foreach ($response as $word) { \n        print_r($word-\u003egetInt16());\n    }\n    // print registers as double words in big endian low word first order (as WAGO-750 does)\n    foreach ($response-\u003egetDoubleWords() as $dword) {\n        print_r($dword-\u003egetInt32(Endian::BIG_ENDIAN_LOW_WORD_FIRST));\n    }\n        \n    // set internal index to match start address to simplify array access\n    $responseWithStartAddress = $response-\u003ewithStartAddress(256);\n    print_r($responseWithStartAddress[256]-\u003egetBytes()); // use array access to get word\n    print_r($responseWithStartAddress-\u003egetDoubleWordAt(257)-\u003egetFloat());\n} catch (Exception $exception) {\n    echo $exception-\u003egetMessage() . PHP_EOL;\n} finally {\n    $connection-\u003eclose();\n}\n```\n\n## Example of Modbus RTU over TCP\nDifference between Modbus RTU and Modbus TCP is that:\n\n1. RTU header contains only slave id. TCP/IP header contains of transaction id, protocol id, length, unitid\n2. RTU packed has 2 byte CRC appended\n\nSee http://www.simplymodbus.ca/TCP.htm for more detailsed explanation\n\nThis 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](examples/rtu.php) for example.\n```php\n$rtuBinaryPacket = RtuConverter::toRtu(new ReadHoldingRegistersRequest($startAddress, $quantity, $slaveId));\n$binaryData = $connection-\u003econnect()-\u003esendAndReceive($rtuBinaryPacket);\n$responseAsTcpPacket = RtuConverter::fromRtu($binaryData);\n```\n\n## Example of Modbus RTU over USB to Serial (RS485) adapter\n\nSee Linux example in 'examples/[rtu_usb_to_serial.php](examples/rtu_usb_to_serial.php)'\n\n\n## Example of Modbus RTU over TCP + higher level API usage\n\nSee example in 'examples/[rtu_over_tcp_with_higherlevel_api.php](examples/rtu_over_tcp_with_higherlevel_api.php)'\n\n\n## Example of non-blocking socket IO with ReactPHP/Amp (i.e. modbus request are run in 'parallel')\n\n* 'examples/[example_parallel_requests_reactphp.php](examples/example_parallel_requests_reactphp.php) - example of non-blocking socket IO with ReactPHP socket library (https://github.com/reactphp/socket)\n* 'examples/[example_parallel_requests_amp.php](examples/example_parallel_requests_amp.php) - example of non-blocking socket IO with Amp socket library https://github.com/amphp/socket \n\n## Example Modbus server (accepting requests) with ReactPHP\n\n* 'examples/[example_response_server.php](examples/example_response_server.php) - example of modbus server \n\n\n## Try communication with PLCs quickly using php built-in web server\n\nExamples folder has [index.php](examples/index.php) which can be used with php built-in web server to test\nout communication with our own PLCs.\n\n```\ngit clone https://github.com/aldas/modbus-tcp-client.git\ncd modbus-tcp-client\ncomposer install\nphp -S localhost:8080 -t examples/\n```\n\nNow open \u003chttp://localhost:8080\u003e in browser. See additional query parameters from [index.php](examples/index.php).\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md)\n\n## Tests\n\n* all `composer test`\n* unit tests `composer test-unit`\n* integration tests `composer test-integration`\n\nFor Windows users:\n* all ` vendor/bin/phpunit`\n* unit tests ` vendor/bin/phpunit --testsuite 'unit-tests'`\n* integration tests ` vendor/bin/phpunit --testsuite 'integration-tests'`\n\n# Static analysis\n\nRun [PHPStan](https://phpstan.org/) analysis `compose check`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faldas%2Fmodbus-tcp-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faldas%2Fmodbus-tcp-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faldas%2Fmodbus-tcp-client/lists"}