aldas / modbus-tcp-client
Modbus TCP协议客户端库
Requires
- php: ^8.0
- ext-mbstring: *
Requires (Dev)
- phpstan/phpstan: ^1.10.50
- phpunit/phpunit: ^9.6.15
- psr/log: ^1.1.4
- react/child-process: ^0.6.5
- react/datagram: ^1.9
- react/socket: ^1.15
Suggests
- psr/log: Required for using the Log middleware with BinaryStreamConnection
README
- Modbus TCP/IP规范:http://www.modbus.org/specs.php
- Modbus TCP/IP和RTU的简单描述:http://www.simplymodbus.ca/TCP.htm
安装
使用Composer将此库作为依赖项安装。
composer require aldas/modbus-tcp-client
支持的功能
- FC1 - 读取线圈 (ReadCoilsRequest / ReadCoilsResponse)
- FC2 - 读取输入离散 (ReadInputDiscretesRequest / ReadInputDiscretesResponse)
- FC3 - 读取保持寄存器 (ReadHoldingRegistersRequest / ReadHoldingRegistersResponse)
- FC4 - 读取输入寄存器 (ReadInputRegistersRequest / ReadInputRegistersResponse)
- FC5 - 写单个线圈 (WriteSingleCoilRequest / WriteSingleCoilResponse)
- FC6 - 写单个寄存器 (WriteSingleRegisterRequest / WriteSingleRegisterResponse)
- FC11 - 获取通信事件计数器 (GetCommEventCounterRequest / GetCommEventCounterResponse)
- FC15 - 写多个线圈 (WriteMultipleCoilsRequest / WriteMultipleCoilsResponse)
- FC16 - 写多个寄存器 (WriteMultipleRegistersRequest / WriteMultipleRegistersResponse)
- FC17 - 报告服务器ID (ReportServerIDRequest / ReportServerIDResponse)
- FC22 - 面具写入寄存器 (MaskWriteRegisterRequest / MaskWriteRegisterResponse)
- FC23 - 读取/写入多个寄存器 (ReadWriteMultipleRegistersRequest / ReadWriteMultipleRegistersResponse)
实用函数
- Packet::isCompleteLength - 检查数据是否为完整的 Modbus TCP 请求或响应报文
- Packet::isCompleteLengthRTU() - 检查数据是否为完整的 Modbus RTU 响应报文
- ErrorResponse::is - 检查数据是否为 Modbus TCP 错误报文
要求
目的
此库受 phpmodbus 库的影响,旨在提供解耦的 Modbus 协议(请求/响应报文)和网络相关功能,以便您可以使用自己选择的网络代码(ext_sockets/streams/Reactphp/Amp 异步流)构建 Modbus 客户端,或者使用库提供的网络类(php Streams)
字节序
适用于存储在 Word/Double/Quad word 寄存器中的多字节数据,基本上是除了 (u)int16/byte/char 之外的所有内容。
因此,如果我们从网络接收到 0x12345678(字节:ABCD)并将其转换为 32 位寄存器,则可能有 4 种不同的方式来解释字节和字序,这取决于 Modbus 服务器架构和客户端架构。NB:TCP 和 UDP 以大端序传输,因此我们选择此作为示例的基础
库支持以下字节和字序
- 大端(ABCD - word1 = 0x1234,word2 = 0x5678)
- 大端低字首先(CDAB - word1 = 0x5678,word2 = 0x1234)(由 Wago-750 使用)
- 小端(DCBA - word1 = 0x3412,word2 = 0x7856)
- 小端低字首先(BADC - word1 = 0x7856,word2 = 0x3412)
用于解析的默认(全局)字节序可以通过以下方式更改
Endian::$defaultEndian = Endian::BIG_ENDIAN_LOW_WORD_FIRST;
对于非全局情况,请参阅 API 方法参数列表,如果方法支持使用自定义字节序。
有关更多信息,请参阅 Endian.php,有关支持的数据类型,请参阅 Types.php
数据类型
Modbus 是二进制协议,它围绕寄存器/Word(16位,2字节数据)和线圈(1位数据)的地址展开。线圈是布尔值,但寄存器/Word 或多个寄存器可以存储不同的数据类型。以下是访问寄存器中不同数据类型的方法
大多数数据类型都有可选参数,用于提供字节序。默认情况下,数据以大端低字首先的字节序解析。
示例:以小端低字首先的方式获取 uint32 值
$dword->getUInt32(Endian::LITTLE_ENDIAN | Endian::LOW_WORD_FIRST);
1bit - 16bit 数据类型
1-16bit 数据类型由 Word
类持有,该类持有 2 字节数据。
$address = 100; $word = $response->getWordAt($address);
以下方法可用于从单个 Word
实例获取不同的类型
boolean
- 1位,true/false,$word->isBitSet(11)
byte
- 8位,1字节,范围 0 到 255- Word 的第一个字节(0)
$word->getHighByteAsInt()
- Word 的最后一个字节(1)
$word->getLowByteAsInt()
- Word 的第一个字节(0)
uint16
- 16位,2字节,范围 0 到 65535$word->getUInt16()
int16
- 16位,2字节,范围 -32768 到 32767$word->getInt16()
以及以下附加方法
$word->getBytes()
返回一个包含两个整数的数组(0-255)
32位数据类型
17-32位数据类型由 DoubleWord
类持有,它包含4字节数据。
$address = 100; $dword = $response->getDoubleWordAt($address);
以下方法可以用于从单个 DoubleWord
实例获取不同类型的数据
uint32
- 32位,4字节,范围0到4294967295,$dword->getUInt32()
int32
- 64位,8字节,范围-2147483648到2147483647,$dword->getInt32()
float
- 64位,8字节,范围-3.4e+38到3.4e+38,$dword->getFloat()
以及以下附加方法
$dword->getBytes()
返回一个包含四个整数的数组(0-255),代表 DoubleWord$dword->getHighBytesAsWord()
返回前两个字节作为Word
$dword->getLowBytesAsWord()
返回后两个字节作为Word
64位数据类型
64位数据类型由 QuadWord
类持有,它包含8字节数据。注意:64位PHP仅支持最多63位(有符号)整数。
$address = 100; $qword = $response->getQuadWordAt($address);
以下方法可以用于从单个 QuadWord
实例获取不同类型的数据
uint32
- 64位,8字节,范围0到9223372036854775807,$dword->getUInt64()
int32
- 64位,8字节,范围-9223372036854775808到9223372036854775807,$dword->getInt64()
double
- 64位,8字节,范围2.2250738585072e-308到1.7976931348623e+308,$dword->getDouble()
以及以下附加方法
$qword->getBytes()
返回一个包含8个整数的数组(0-255),代表 QuadWord$qword->getHighBytesAsDoubleWord()
返回前4个字节作为DoubleWord
$qword->getLowBytesAsDoubleWord()
返回后4个字节作为DoubleWord
字符串
ASCII(8位字符)字符串可以从响应中作为UTF-8字符串提取
$address = 20; $length = 10; $string = $response->getAsciiStringAt($address, $length);
Modbus TCP(fc3 - 读取保持寄存器)的示例
一些Modbus功能示例在 examples/ 文件夹中
高级用法
- 使用 ReachPHP 的命令行轮询器 examples/example_cli_poller.php
- 使用非阻塞IO并行发送/接收数据包
- 使用 ReactPHP,请参阅 'examples/example_parallel_requests_reactphp.php'
- 使用 Amp,请参阅 'examples/example_parallel_requests_amp.php'
使用高级API请求多个数据包
$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());
响应结构
[ [ 'pump2_feedbackalarm_do' => true, ], [ 'battery3_voltage_wo' => 12, ], [ 'username_plc2' => 'prefix_admin', ] ]
低级 - 发送数据包
$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(); }
Modbus RTU over TCP 的示例
Modbus RTU 和 Modbus TCP 之间的区别在于
- RTU 报头仅包含从站 ID。TCP/IP 报头包含事务 ID、协议 ID、长度和单元 ID
- RTU 封包附加了2字节 CRC
有关更详细说明,请参阅 http://www.simplymodbus.ca/TCP.htm
这个库最初是为 Modbus TCP 而设计的,但它支持将数据包转换为 RTU,并从 RTU 转换。请参阅此 examples/rtu.php 示例。
$rtuBinaryPacket = RtuConverter::toRtu(new ReadHoldingRegistersRequest($startAddress, $quantity, $slaveId)); $binaryData = $connection->connect()->sendAndReceive($rtuBinaryPacket); $responseAsTcpPacket = RtuConverter::fromRtu($binaryData);
Modbus RTU over USB to Serial(RS485)适配器的示例
请参阅 'examples/rtu_usb_to_serial.php' 中的 Linux 示例
Modbus RTU over TCP + 高级API使用的示例
请参阅 'examples/rtu_over_tcp_with_higherlevel_api.php' 中的示例
使用 ReactPHP/Amp 的非阻塞套接字IO的示例(即,modbus 请求在“并行”中运行)
- ‘examples/example_parallel_requests_reactphp.php - 使用 ReactPHP 网络库的示例,实现非阻塞套接字 IO (https://github.com/reactphp/socket)
- ‘examples/example_parallel_requests_amp.php - 使用 Amp 网络库的示例,实现非阻塞套接字 IO https://github.com/amphp/socket
使用 ReactPHP 的 Modbus 服务器示例(接受请求)
- ‘examples/example_response_server.php - Modbus 服务器示例
使用 PHP 内置的 web 服务器快速尝试与 PLC 进行通信
示例文件夹中有 index.php,可以与 PHP 内置的 web 服务器一起使用来测试与自己的 PLC 的通信。
git clone https://github.com/aldas/modbus-tcp-client.git
cd modbus-tcp-client
composer install
php -S localhost:8080 -t examples/
现在在浏览器中打开 https://:8080。查看来自 index.php 的附加查询参数。
变更日志
查看 CHANGELOG.md
测试
- 所有
composer test
- 单元测试
composer test-unit
- 集成测试
composer test-integration
针对 Windows 用户
- 所有
vendor/bin/phpunit
- 单元测试
vendor/bin/phpunit --testsuite 'unit-tests'
- 集成测试
vendor/bin/phpunit --testsuite 'integration-tests'
静态分析
运行 PHPStan 分析 compose check