这是一个简易版的modbus-rtu协议处理的实现,可根据需求自行修改
<?php
namespace app\library;
use Workerman\Connection\ConnectionInterface;
/**
* 自定义modbus-rtu协议
* 正常应答格式:地址码(1字节) + 功能码(1字节) + 数据(0-252字节) + crc校验码(2字节)
* 功能码取值范围:1-127
* 异常应答格式:地址码(1字节) + 异常功能码(1字节) + 异常码(1字节) + crc校验码(2字节)
* 异常功能码 = 正常功能码+0x80 取值范围:129(1+128)- 255(127+128)
* 异常码:01,02,03,04,05,06,07,08,0A,0B
*/
class ModbusRtuProtocol
{
// 常用功能码
const READ_COILS = 1;
const READ_INPUT_DISCRETES = 2;
const READ_HOLDING_REGISTERS = 3;
const READ_INPUT_REGISTERS = 4;
const WRITE_SINGLE_COIL = 5;
const WRITE_SINGLE_REGISTER = 6;
const WRITE_MULTIPLE_COILS = 15;
const WRITE_MULTIPLE_REGISTERS = 16;
const MASK_WRITE_REGISTER = 22;
const READ_WRITE_MULTIPLE_REGISTERS = 23;
// 异常码增量
const EXCEPTION_BITMASK = 128;
// 附加数据长度
protected static $extraData = [
'imei' => 10,
'ping' => 4
];
/**
* 检查包的完整性
*
* 如果可以在$recv_buffer中得到请求包的长度则返回整个包的长度
* 否则返回0,表示需要更多的数据才能得到当前请求包的长度
* 如果返回false或者负数,则代表错误的请求,则连接会断开
*
* @param string $recv_buffer
* @param ConnectionInterface $connection
*
* @return int
*/
public static function input($buffer, ConnectionInterface $connection)
{
$recv_len = strlen($buffer);
// 少于4个字节则返回0,继续等待数据
if ($recv_len < 4) {
return 0;
}
// 是否包含二进制字符 if (preg_match('~[^\x20-\x7E\t\r\n]~', $buffer) > 0) {}
// 以开始4字节字符判断
$args = self::$extraData;
$ascTag = substr($buffer, 0, 4);
if (isset($args[$ascTag])) {
$frame_length = $args[$ascTag];
} else {
try {
$isCompare = self::isCompleteLengthRTU($buffer);
// 长度不够继续等待
if ($isCompare === false) {
return 0;
}
$frame_length = $recv_len;
} catch (\Throwable $th) {
// 断开连接
$connection->close();
return 0;
}
}
// 如果缓冲区的长度小于数据帧的长度,则返回0,继续等待数据
if ($recv_len < $frame_length) {
return 0;
}
return $frame_length;
}
/**
* 用于请求解包
*
* input返回值大于0,并且WorkerMan收到了足够的数据,则自动调用decode
* 然后触发onMessage回调,并将decode解码后的数据传递给onMessage回调的第二个参数
* 也就是说当收到完整的客户端请求时,会自动调用decode解码,无需业务代码中手动调用
* @param ConnectionInterface $connection
* @param string $recv_buffer
*/
public static function decode($buffer)
{
$relation = self::$extraData;
$tagstr = substr($buffer, 0, 4);
// 返回字符串
// if (isset($relation[$tagstr])) {
// return $buffer;
// }
// return bin2hex($buffer);
// 返回数组
$type = 'report';
$data = bin2hex($buffer);
if (isset($relation[$tagstr])) {
$type = $tagstr;
$data = $buffer;
}
return compact('type', 'data');
}
/**
* 用于请求打包
*
* 当需要向客户端发送数据即调用$connection->send($data);时
* 会自动把$data用encode打包一次,变成符合协议的数据格式,然后再发送给客户端
* 也就是说发送给客户端的数据会自动encode打包,无需业务代码中手动调用
* @param ConnectionInterface $connection
* @param mixed $data
*/
public static function encode($buffer)
{
// TEST TODO...
$buffer = str_replace(' ', '', $buffer);
if (!ctype_xdigit($buffer)) $buffer = bin2hex($buffer);
return hex2bin($buffer);
}
/**
* 检测是否完整rtu格式数据包
*
* @param [type] $binaryData
* @return boolean
*/
public static function isCompleteLengthRTU($binaryData)
{
// 最小RTU数据包长度为5字节(1字节地址+1字节功能码+1字节错误码或1字节长度+2字节CRC)
$length = strlen($binaryData);
if ($length < 5) {
return false;
}
// 功能码
$functionCode = ord($binaryData[1]);
// 异常响应
if ($functionCode - self::EXCEPTION_BITMASK > 0) {
return true;
}
switch ($functionCode) {
case self::READ_COILS:
case self::READ_INPUT_DISCRETES:
case self::READ_HOLDING_REGISTERS:
case self::READ_INPUT_REGISTERS:
case self::READ_WRITE_MULTIPLE_REGISTERS:
$responseBytesLen = 3 + ord($binaryData[2]);
break;
case self::WRITE_SINGLE_COIL:
case self::WRITE_SINGLE_REGISTER:
case self::WRITE_MULTIPLE_COILS:
case self::WRITE_MULTIPLE_REGISTERS:
$responseBytesLen = 6;
break;
case self::MASK_WRITE_REGISTER:
$responseBytesLen = 8;
break;
default:
throw new \Exception("can not determine complete length for unsupported modbus function code");
}
// 加上2位crc校验码
$expectedLength = $responseBytesLen + 2;
if ($length > $expectedLength) {
throw new \Exception("packet length more bytes than expected");
}
return $length === $expectedLength;
}
}
评论 (0)