python高级:通用modbus协议过滤器

经常与物联网打交道的人经常会遇到各种各样的modbus协议,有的长的正正规规,有的长的奇奇怪怪,不管如何,大概率都是类似于下面的样子:
大部分modbus协议样子
一个数据帧的样子无外乎如此,因此在接入各种硬件的时候,反反复复编写类似的协议代码,十分揪心,特别是还要每次处理粘包问题,因此如果有一款通用的协议过滤器处理各类协议解析,那就非常舒服了。
目前我的想法如下:
过滤器大致流程
如果有一个过滤器在数据帧和明文内容中间帮我们过滤数据,而且是傻瓜式操作,那就可以完成一个通用工具的要求。

一、过滤器应该带什么方法

一个好的通用型过滤器需要带哪些方法,我觉得应该要有如下几个:

  • 数据帧规则说明
  • crc校验支持
  • 过滤消息体

1.1数据帧规则说明

数据帧规则说明可以帮助开发者将各式各样的数据帧规则说明以一个样式传递给过滤器,让过滤器明白这个协议长什么样子,从而支持各类型的协议。

1.2crc校验支持

每一段数据帧都会有校验码,一般为各类型的crc,因此支持各种不同的校验方法是实现消息解析的重要步骤。在过滤器中,crc校验支持一个需要知道哪些数据需要计算到crc校验中,第二个是crc校验结果是否正确。

1.3过滤数据帧

这是一个过滤数据帧的主要方法。

二、过滤器使用什么方式实现

过滤器将采用面向对象的方式实现,由于python没有接口的概念,这里我将使用抽象类来创建一个基类,子类集成基类,实现抽象方法来实现一个定制化的过滤器。

2.1采用面向对象的原因

为了能够处理粘包问题,将会把未处理的消息内容存到内存中,直到整个数据帧获取完整,因此一个对象作为过滤器为最佳,可以完美解决粘包问题。

三、数据帧规则制定

数据帧的规则将用一个数组来表现,每一个元素即为数据帧的一段内容,下面以一个实例来作为规则编写:
数据帧例子

[
	{"key": "header", "len": 1, "fmt": "B"},
	{"key": "ver", "len": 1, "fmt": "B"},
	{"key": "msg_len", "len": 1, "fmt": "B"},
	{"key": "data", "len": "{msg_len.val} - {crc.len}", "fmt": ""},
	{"key": "crc", "len": 2, "fmt": "h", "group": "{msg_len}{data}"}
]

3.1规则字段说明

  • key:该数据的关键字
  • len:该数据的长度,可以为数字,也可以为相应数据的运算内容字符串
  • fmt:该数据的打包格式,用struct解析,当存在内容时,将尝试解析数据赋值给val字段,如果为空,则将原始字节流赋值给val
  • group:当关键字是crc的时候,用来框定哪些数据进行计算
  • val:数据解析后的内容,消息解析过程中自动创建的字段,无需在规则中声明

msg_len.val:获取msg_len的val字段内容
crc.len:获取crc的数据长度
msg_len:获取msg_len的字节流数据

四、具体代码

话不多说,代码直接拿去:

# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod
import struct
import copy
import re


class AbstractProtocolBaseFilter(metaclass=ABCMeta):
    def __init__(self):
        self.current_index = 0
        self.data = {}
        self.tmp_socket_data = b""
        self.__check_rule_ok = True

    @abstractmethod
    def rules(self):
        """
        规则说明
        :return:
        """
        return []

    @abstractmethod
    def crc_imp(self, crc_data):
        """
        crc实现方法
        :param crc_data:
        :return:
        """
        return 0

    def filter(self, socket_data):
        """
        过滤数据方法
        :param socket_data:
        :return:
        """
        if not self.__check_rule_ok or not self.rules():
            raise Exception("the rule is not available")

        if not self.data:
            try:
                self.clear_data()
            except:
                self.__check_rule_ok = False

        self.tmp_socket_data += socket_data
        tmp_check_index = 0
        results = []
        if self.__check_rule_ok:
            for i, v in enumerate(self.tmp_socket_data):
                rule = self.rules()[self.current_index]
                key_name = rule["key"]
                result = self.data[key_name]

                try:
                    length_str = result["len"]
                    fmt_str = result["fmt"]
                except:
                    self.__check_rule_ok = False
                    break

                length = self.__get_length(length_str)

                if length is None:
                    self.__check_rule_ok = False
                    break

                # 长度与内容长度一致或指令中有部分内容可以为0时
                if length == (i + 1 - tmp_check_index) or length == 0:
                    if length > 0:
                        result["bytes"] = self.tmp_socket_data[tmp_check_index: i + 1]
                        tmp_check_index = i + 1

                    if not fmt_str:
                        result["val"] = result["bytes"]
                    else:
                        result["val"] = struct.unpack(fmt_str, result["bytes"])[0]

                    self.current_index += 1

                    # 当一组协议数据解析成功,重置下标到0
                    if self.current_index > len(self.rules()) - 1:
                        is_crc_check = True
                        # 如果是crc校验码数据,则比对crc
                        if key_name == "crc":
                            try:
                                group_str = result["group"]
                                crc_bytes_data = self.__get_crc_group(group_str)
                                if crc_bytes_data is None:
                                    self.__check_rule_ok = False
                                    break

                                if self.crc_imp(crc_bytes_data) == result["val"]:
                                    is_crc_check = True
                                else:
                                    is_crc_check = False
                            except:
                                self.__check_rule_ok = False
                                break

                        if is_crc_check:
                            results.append(copy.deepcopy(self.data))

                        self.current_index = 0
                        self.clear_data()

        self.tmp_socket_data = self.tmp_socket_data[tmp_check_index:]

        return results

    def clear_data(self):
        """
        初始化data数据
        :return:
        """
        for rule in self.rules():
            key_name = rule["key"]
            self.data[key_name] = rule
            self.data[key_name]["bytes"] = b""
            self.data[key_name]["val"] = None

    def __get_crc_group(self, group_str):
        """
        解析获取group内容
        :param group_str:
        :return:
        """
        if isinstance(group_str, str):
            res_list = re.findall(r"{(.+?)}", group_str)
            group_bytes_data = b""
            for res in res_list:
                try:
                    group_bytes_data += self.data[res]["bytes"]
                except:
                    return None
            return group_bytes_data
        return None

    def __get_length(self, length_str):
        """
        解析获取数据长度
        :param length_str:
        :return:
        """
        if isinstance(length_str, int):
            return length_str
        elif length_str.isdigit():
            try:
                return int(length_str)
            except:
                return None
        elif isinstance(length_str, str):
            res_list = re.findall(r"{(.+?)}", length_str)
            value_list = []
            for res in res_list:
                split_list = res.split(".")
                try:
                    key_name = split_list[0]
                    val_name = split_list[1]
                    value = self.data[key_name][val_name]

                    if value is None:
                        return None

                    value_list.append(value)
                except:
                    return None

            if len(res_list) != len(value_list):
                return None

            length_str = length_str.replace("{", "").replace("}", "")
            for i, res in enumerate(res_list):
                value = value_list[i]
                length_str = length_str.replace(f"{res}", str(value))

            try:
                length = eval(length_str)
                return length
            except:
                return None

        return None

定制过滤器

class CustomProtocolFilter(AbstractProtocolBaseFilter):
    def crc_imp(self, crc_data):
        # crc处理
        return 0

    def rules(self):
        return [
            {"key": "header", "len": 1, "fmt": "B"},
			{"key": "ver", "len": 1, "fmt": "B"},
			{"key": "msg_len", "len": 1, "fmt": "B"},
			{"key": "data", "len": "{msg_len.val} - {crc.len}", "fmt": ""},
			{"key": "crc", "len": 2, "fmt": "h", "group": "{msg_len}{data}"}
        ]

调用

protocol_filter = CustomProtocolFilter()
try:
	res = protocol_filter.filter(bytes_data)
except Exception as e:
	print(e)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值