__all__ = ["Unpacker"] import asyncio from collections import namedtuple import io import struct from tabnanny import check import warnings from ._parsing import parse from . import tools class Unpacker: """ Create an Unpacker to decode a byte stream into HART protocol messages. The ``file_like`` parameter should be an object which data can be sourced from. It should support the ``read()`` method. The ``on_error`` parameter selects the action to take if invalid data is detected. If set to ``"continue"`` (the default), bytes will be discarded if the byte sequence does not appear to be a valid message. If set to ``"warn"``, the behaviour is identical, but a warning message will be emitted. To instead immediately abort the stream decoding and raise a ``RuntimeError``, set to ``"raise"``. :param file_like: A file-like object which data can be `read()` from. :param on_error: Action to take if invalid data is detected. """ def __init__(self, file_like=None, on_error="continue"): if file_like is None: self._file = io.BytesIO() else: self._file = file_like self.buf = b"" self.on_error = on_error # print(self._file) def __iter__(self): return self def _decoding_error(self, message="Error decoding message from buffer."): """ Take appropriate action if parsing of data stream fails. :param message: Warning or error message string. """ if self.on_error == "raise": raise RuntimeError(message) if self.on_error == "warn": warnings.warn(message) def _read_one_byte_if_possible(self): if self._file.in_waiting > 0: # print(1) return self._file.read(1) else: raise StopIteration def __next__(self): # must work with at least two bytes to start with while len(self.buf) < 3: self.buf += self._read_one_byte_if_possible() # keep reading until we find a minimum preamble # print(self.buf, 11) while self.buf[:3] not in [b"\xFF\xFF\x06", b"\xFF\xFF\x86"]: self.buf += self._read_one_byte_if_possible() # print(self.buf) self.buf = self.buf[1:] self._decoding_error("Head of buffer not recognized as valid preamble") # now the head of our buffer is the start charachter plus two preamble # we will read all the way through status if self.buf[2] & 0x80: l = 12 else: l = 8 while len(self.buf) < l: self.buf += self._read_one_byte_if_possible() # now we can use the bytecount to read through the data and checksum # # print(self.buf) # print(type(self.buf[l - 4]), 222) if self.buf[l - 4] == 15: # 对command15进行特殊操作 修复报文错误 # bytecount = 19 self.buf = self.buf[:l - 3] + b'\x13\x00\x00\x00' bytecount = self.buf[l - 3] response_length = l + bytecount - 1 # print(self.buf, bytecount) while len(self.buf) < response_length: self.buf += self._read_one_byte_if_possible() # checksum # print(self.buf) checksum = int.from_bytes( tools.calculate_checksum(self.buf[2 : response_length - 1]), "big" ) # print(self.buf) if checksum != self.buf[response_length - 1]: # print(66666666) self._decoding_error("Invalid checksum.") raise StopIteration # print(self.buf) # parse response = self.buf[2:response_length] # print(response, 'test') # print(response) dict_ = parse(response) # clear buffer if len(self.buf) == response_length: self.buf = b"" else: self.buf = self.buf[response_length + 3 :] # return return dict_ def __aiter__(self): return self async def __anext__(self): while True: try: return next(self) except StopIteration: await asyncio.sleep(0.001) def feed(self, data: bytes): """ Add byte data to the input stream. The input stream must support random access, if it does not, must be fed externally (e.g. serial port data). :param data: Byte array containing data to add. """ pos = self._file.tell() self._file.seek(0, 2) self._file.write(data) self._file.seek(pos)