first commit

This commit is contained in:
2026-03-12 17:03:56 +08:00
commit aa4d4c7d7c
48 changed files with 10958 additions and 0 deletions

298
ck/serial_protocol.py Normal file
View File

@@ -0,0 +1,298 @@
"""
串口通信协议实现
支持两台设备之间可靠的数据传输
协议帧格式:
+------+------+--------+------+--------+------+
| 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)