workerman自定义协议-----modbus-rtu协议
标签搜索

workerman自定义协议-----modbus-rtu协议

十年
2023-06-08 / 0 评论 / 22 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2023年06月09日,已超过922天没有更新,若内容或图片失效,请留言反馈。

<?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;
    }
}
本文共 496 个字数,平均阅读时长 ≈ 2分钟
0

评论 (0)

取消