""" 串口通信协议实现 支持两台设备之间可靠的数据传输 协议帧格式: +------+------+--------+------+--------+------+ | HEAD | LEN | CMD | DATA | CRC | TAIL | +------+------+--------+------+--------+------+ | 0xAA | 2B | 1B | N字节 | 2B | 0x55 | +------+------+--------+------+--------+------+ HEAD: 帧头标识 (1字节, 0xAA) LEN: 数据长度 (2字节, 大端序, 包含CMD+DATA) CMD: 命令字 (1字节) DATA: 数据内容 (N字节) CRC: CRC16校验 (2字节, 大端序) TAIL: 帧尾标识 (1字节, 0x55) """ import serial import struct from enum import IntEnum from typing import Optional, Callable import threading class Command(IntEnum): """命令类型定义""" HEARTBEAT = 0x01 # 心跳包 DATA_QUERY = 0x02 # 数据查询 DATA_RESPONSE = 0x03 # 数据响应 CONTROL = 0x04 # 控制命令 ACK = 0x05 # 应答 NACK = 0x06 # 否定应答 class SerialProtocol: """串口协议类""" # 协议常量 FRAME_HEAD = 0xAA FRAME_TAIL = 0x55 # 最小帧长度: HEAD(1) + LEN(2) + CMD(1) + CRC(2) + TAIL(1) MIN_FRAME_LEN = 7 def __init__(self, port: str, baudrate: int = 115200, timeout: float = 1.0): """ 初始化串口协议 Args: port: 串口名称 (如 'COM1', '/dev/ttyUSB0') baudrate: 波特率 timeout: 超时时间(秒) """ self.port = port self.baudrate = baudrate self.timeout = timeout self.serial = None self.running = False self.receive_thread = None self.receive_callback: Optional[Callable] = None def open(self) -> bool: """打开串口""" try: self.serial = serial.Serial( port=self.port, baudrate=self.baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout ) return True except Exception as e: print(f"打开串口失败: {e}") return False def close(self): """关闭串口""" self.stop_receive() if self.serial and self.serial.is_open: self.serial.close() @staticmethod def calc_crc16(data: bytes) -> int: """ 计算CRC16校验值 (CRC-16/MODBUS) Args: data: 需要计算校验的数据 Returns: CRC16校验值 """ crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 0x0001: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc def build_frame(self, cmd: int, data: bytes = b'') -> bytes: """ 构建数据帧 Args: cmd: 命令字 data: 数据内容 Returns: 完整的数据帧 """ # 计算长度 (CMD + DATA) length = 1 + len(data) # 构建帧体 (不含CRC和TAIL) frame_body = (struct.pack('>BHB', self.FRAME_HEAD, length, cmd) + data) # 计算CRC (对HEAD+LEN+CMD+DATA进行校验) crc = self.calc_crc16(frame_body) # 添加CRC和TAIL frame = frame_body + struct.pack('>HB', crc, self.FRAME_TAIL) return frame def parse_frame(self, frame: bytes) -> Optional[dict]: """ 解析数据帧 Args: frame: 接收到的数据帧 Returns: 解析结果字典 {'cmd': 命令字, 'data': 数据}, 失败返回None """ if len(frame) < self.MIN_FRAME_LEN: return None # 检查帧头和帧尾 if frame[0] != self.FRAME_HEAD or frame[-1] != self.FRAME_TAIL: return None try: # 解析长度 length = struct.unpack('>H', frame[1:3])[0] # 检查帧长度是否匹配 # HEAD + LEN + (CMD+DATA) + CRC + TAIL expected_len = 1 + 2 + length + 2 + 1 if len(frame) != expected_len: return None # 解析命令字 cmd = frame[3] # 提取数据 data = frame[4:4+length-1] # 提取CRC received_crc = struct.unpack('>H', frame[-3:-1])[0] # 计算CRC并校验 calc_crc = self.calc_crc16(frame[:-3]) if received_crc != calc_crc: print(f"CRC校验失败: 接收={received_crc:04X}, " f"计算={calc_crc:04X}") return None return {'cmd': cmd, 'data': data} except Exception as e: print(f"解析帧失败: {e}") return None def send_frame(self, cmd: int, data: bytes = b'') -> bool: """ 发送数据帧 Args: cmd: 命令字 data: 数据内容 Returns: 发送是否成功 """ if not self.serial or not self.serial.is_open: print("串口未打开") return False try: frame = self.build_frame(cmd, data) self.serial.write(frame) return True except Exception as e: print(f"发送失败: {e}") return False def receive_frame(self) -> Optional[dict]: """ 接收一帧数据 (阻塞式) Returns: 解析结果字典或None """ if not self.serial or not self.serial.is_open: return None try: # 等待帧头 while True: byte = self.serial.read(1) if not byte: return None if byte[0] == self.FRAME_HEAD: break # 读取长度字段 len_bytes = self.serial.read(2) if len(len_bytes) != 2: return None length = struct.unpack('>H', len_bytes)[0] # 读取剩余数据: CMD + DATA + CRC + TAIL remaining = self.serial.read(length + 3) if len(remaining) != length + 3: return None # 重组完整帧 frame = bytes([self.FRAME_HEAD]) + len_bytes + remaining # 解析帧 return self.parse_frame(frame) except Exception as e: print(f"接收失败: {e}") return None def start_receive(self, callback: Callable[[int, bytes], None]): """ 启动接收线程 Args: callback: 回调函数 callback(cmd, data) """ if self.running: return self.receive_callback = callback self.running = True self.receive_thread = threading.Thread( target=self._receive_loop, daemon=True) self.receive_thread.start() def stop_receive(self): """停止接收线程""" self.running = False if self.receive_thread: self.receive_thread.join(timeout=2.0) def _receive_loop(self): """接收循环""" while self.running: result = self.receive_frame() if result and self.receive_callback: try: self.receive_callback(result['cmd'], result['data']) except Exception as e: print(f"回调函数执行失败: {e}") def send_heartbeat(self) -> bool: """发送心跳包""" return self.send_frame(Command.HEARTBEAT) def send_data(self, data: bytes) -> bool: """发送数据""" return self.send_frame(Command.DATA_RESPONSE, data) def send_control(self, control_code: int, params: bytes = b'') -> bool: """发送控制命令""" data = bytes([control_code]) + params return self.send_frame(Command.CONTROL, data) def send_ack(self) -> bool: """发送应答""" return self.send_frame(Command.ACK) def send_nack(self) -> bool: """发送否定应答""" return self.send_frame(Command.NACK)