"""Serial transport of the station-internal link (the app's Phase 03 framing). ``[AA][55][type][length LE][payload][crc16 LE]``, CRC-16/CCITT-FALSE over type+length+payload. Frame types: 0x10 link message, 0x11 test channel, 0x7F log line. Requires pyserial. """ from __future__ import annotations import enum import struct import threading from typing import Callable, List, Tuple MAXIMUM_PAYLOAD = 1536 class FrameType(enum.IntEnum): LINK = 0x10 TEST = 0x11 LOG = 0x7F def crc16_ccitt_false(data: bytes) -> int: crc = 0xFFFF for octet in data: crc ^= octet << 8 for _ in range(8): crc = ((crc << 1) ^ 0x1021) & 0xFFFF if crc & 0x8000 else (crc << 1) & 0xFFFF return crc def encode_frame(frame_type: int, payload: bytes) -> bytes: if len(payload) > MAXIMUM_PAYLOAD: raise ValueError('frame payload exceeds %d octets' % MAXIMUM_PAYLOAD) head = struct.pack(' List[Tuple[int, bytes]]: out = [] for b in data: s = self.state if s == 'SYNC0': self.state = 'SYNC1' if b == 0xAA else 'SYNC0' elif s == 'SYNC1': self.state = 'TYPE' if b == 0x55 else ('SYNC1' if b == 0xAA else 'SYNC0') elif s == 'TYPE': self.type = b self.state = 'LEN_LO' elif s == 'LEN_LO': self.length = b self.state = 'LEN_HI' elif s == 'LEN_HI': self.length |= b << 8 self.payload = bytearray() if self.length > MAXIMUM_PAYLOAD: self.state = 'SYNC0' else: self.state = 'CRC_LO' if self.length == 0 else 'PAYLOAD' elif s == 'PAYLOAD': self.payload.append(b) if len(self.payload) >= self.length: self.state = 'CRC_LO' elif s == 'CRC_LO': self.crc = b self.state = 'CRC_HI' elif s == 'CRC_HI': self.crc |= b << 8 head = struct.pack('