【问题标题】:Deserializing byte stream to objects将字节流反序列化为对象
【发布时间】:2016-05-08 23:05:25
【问题描述】:

我正在构建一个从串行端口接收字节的 python 项目。这些字节是对发送的命令的响应(也通过串行端口)。响应没有识别标记,即仅从字节来看,我不知道这是哪个命令响应。解码器当然需要提前知道这是对哪个命令的响应。

我希望将传入的字节序列表示为嵌套对象,指示帧、标头、有效负载、解码的有效负载等。我更愿意一次将 1 个字节推送到解码器并让它调用一旦接收到足够完整对象的字节后回调(如果有错误或超时,则为 errorCallback)。

实际的帧有一个起始字节和一个结束字节。它有一个带有几个字节的标头(一些 id、命令状态(基本上是 ok/fail)),一个是数据长度字节。后面是数据,后面是校验和(单字节)。数据是对命令的响应。

响应是可预测的,因为前面的字节决定了接下来的字节的含义。

示例响应:

aa:00:0c:00:01:00:00:d3:8d:d4:5c:50:01:04:e0:6e:bb

分解:

aa: start frame
    00: id
    0c: data length (incl status): 12 bytes
    00: command status (byte 1)
        01: 1 data frame (byte 2)
            00:00: flags of first data frame (byte 3-4)
            d3:8d:d4:5c:50:01:04:e0: first data (aa and bb could be in it) (byte 5-12)
    6e: checksum (includes all bytes except start, checksum, end bytes)
bb: end frame

这是串行端口通信,可能会丢失字节(并产生额外的字节),我希望使用超时来处理重置(如果没有先发送命令,则不会响应)。

我真的很想要一种面向对象的方法,其中解码器会产生一个对象,当序列化时,它会再次产生相同的字节序列。我使用的是 python 2.7,但实际上任何面向对象的语言都可以(只要我可以将其转换为 python)。

我只是不确定如何构建解码器以使其看起来整洁。我正在寻找一个完整的解决方案,只是让我朝着正确的方向前进(正确的方向在这里有些主观)。

【问题讨论】:

  • 如果您发布一些代码,您更有可能得到对您的问题的积极回应。如果它不整洁,或者没有使用任何高级技巧,请不要担心。发布一些代码不仅表明您已经诚实地尝试自己解决问题,而且一段代码是一种非常有用的方式来准确显示您正在尝试做的事情。
  • 您的意思是您只是希望将传入的固定长度响应解析为可以转换回字节的对象(带有属性)?
  • 我确实有一些代码,但它太可怕了,实际上不能正常工作。此外,它并没有满足我将其模块化的愿望。 TisteAndii 下面的答案实际上比我的答案要好。

标签: python object serialization stream byte


【解决方案1】:

我不完全理解您想要做什么,但是如果您想从某些设备接收固定长度的响应并使其成为某个对象的属性,这样可以吗?:

START_FRAME = 0xAA
END_FRAME = 0xBB
TIMEOUT = 2

class Response:
def __init__(self, response):
    if (len(response) - 6) % 11 == 0 and response[0] == START_FRAME and response[-1] == END_FRAME:  # verify that its a valid response
        self.header = {}   # build header
        self.header['id'] = response[1]
        self.header['length'] = response[2]
        self.header['status'] = response[3]
        self.r_checksum = response[-2]  # get checksum from response
        self.checksum = self.header['id'] ^ self.header['length'] ^ self.header['status']   # verify the checksum
        self.payload = response[4:-2] # get raw payload slice
        self.decode(self.payload)  # parse payload
        if self.r_checksum == self.checksum: # final check
            self.parsed = True
        else:
            self.parsed = False
    else:  # if response didnt follow the pattern
        self.parsed = False
def decode(self, packet):  # decode payload
    self.frm_count = packet[0]   # get number of data frames
    self.checksum ^= self.frm_count  
    self.decoded = []   # hold decoded payload
    frames = packet[1:]
    for c in range(self.frm_count):  # iterate through data frames
        flags = frames[(c*10):(c*10 + 2)]
        for f in flags:
            self.checksum ^= f
        data = frames[(c*10 + 2):(c+1)*10]
        for d in data:
            self.checksum ^= d
        self.decoded.append({'frame': c+1, 'flags': flags, 'data':data})
def serialize():  # reconstruct response
    res = bytearray()
    res.append(START_FRAME)
    res.extend([self.header['id'], self.header['length'], self.header['status']])
    res.extend(self.payload)
    res.extend([self.checksum, END_FRAME])
    return res

response = bytearray()
ser = serial.Serial('COM3', 9600)   # timeout is 2 seconds
last_read = time.clock()
while time.clock() -  last_read < TIMEOUT:
    while ser.inWaiting() > 0:
        response.append(ser.read())
        last_read = time.clock()
decoded_res = Response(response)
if decoded_res.parsed:
    # do stuff
else:
    print('Invalid response!')

此代码假定可能有多个数据帧,数据帧前面紧跟一个字节,指示数据帧的数量。 与串行通信所需的时间相比,解析数据包的速度更快(即使是 115200 波特)。我认为整个事情大约是 O(n)。

【讨论】:

  • 您的假设对于显示的响应是正确的。我确实得到了各种不同的响应,这些响应必须以各自的方式解码,具体取决于发送的命令。通过添加额外的解码方法(每个命令一个;正确地),可以扩展您的代码以考虑到这一点。我认为你的建议对我来说已经足够好,即使课程需要一个完整的帧来开始解码。我曾希望采用更面向流的方法。使用超时将字节流分成数据包可能会变得不可靠(而且速度很慢)。我希望这不会是一个问题。非常感谢!
  • 也许你可以扩展你所说的面向流的意思?在你理解它之前你是否需要一个完整的框架,因为框架可能会被证明是不完整/无效的?您可以减少超时时间(如果您的波特率足够高),甚至可以摆脱 time.sleep() 而不是使用 time.clock() 左右来测量间隔,而不是浪费一秒钟什么都不做。让我编辑代码
  • 我担心的主要是响应是否是背靠背的,即不能仅通过查看超时来拆分数据包(因为没有超时)。但我认为在向设备发送新请求之前,我可以在每次响应后稍作延迟。我需要尽快发送尽可能多的请求(并获得尽可能多的回复)。 1-2 个字符的超时时间应该是可以承受的。通过面向流,我想到了一种将无尽的流字节发送到类的方法,并在它有一个完整的数据包后让它调用回调。更多字节将导致更多回调。您的代码是一个很好的起点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多