| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- import struct
- from .BxUtils import CRC16
- class BxDataPack:
- def __init__(self, data=None, dataLen=0):
- self.WRAP_A5_NUM = 8
- self.WRAP_5A_NUM = 1
- self.dstAddr = 0x0001 # 设备ID(旧通过值)
- self.srcAddr = 0x8000 # 旧通过值
- self.r0 = 0
- self.r1 = 0
- self.r2 = 0
- self.option = 0
- self.crcMode = 0x01 # ✅ 修复:旧通过值0x01
- self.displayType = 0xFE # ✅ 修复:旧通过值0xFE
- self.deviceType = 0x02 # ✅ 修复:旧通过值0x02
- self.version = 0x45 # ✅ 修复:旧通过值0x45
- self.data = data if data else bytearray()
- self.dataLen = dataLen if dataLen > 0 else len(self.data)
- self.crc = 0
- def SetDisplayType(self, typ):
- """注:特殊动态区不支持动态模式
- 0x00:普通模式,动态区与节目可同时显示,但各区域不可重叠。
- 0x01:动态模式,优先显示动态区,无动态区则显示节目,动态区与节目区可重叠。"""
- self.displayType = typ
- # ---------------- 第二步:1:1精准复刻Go的wrap函数(每一行都对齐Go) ----------------
- def wrap(self, src: bytearray | bytes) -> bytearray:
- """
- 1:1精准复刻Go的wrap函数
- :param src: 未转义的源数据
- :return: 转义后的完整数据包
- """
- # ---------------- 1. 先计算总长度(完全对齐Go) ----------------
- src_len = len(src)
- total_len = src_len
- # 统计特殊字符,每个特殊字符会使长度+1
- for v in src:
- if v == 0xa5 or v == 0x5a or v == 0xa6 or v == 0x5b:
- total_len += 1
- # 加上开头和结尾的标记长度
- total_len += self.WRAP_A5_NUM
- total_len += self.WRAP_5A_NUM
- # ---------------- 2. 创建结果数组 ----------------
- result = bytearray(total_len)
- offset = 0
- # ---------------- 3. 添加开头的A5标记(不转义) ----------------
- for i in range(self.WRAP_A5_NUM):
- result[offset] = 0xa5
- offset += 1
- # ---------------- 4. 转义中间数据(核心逻辑) ----------------
- for v in src:
- if v == 0xa5:
- # 0xa5 -> 0xa6 0x02
- result[offset] = 0xa6
- offset += 1
- result[offset] = 0x02
- offset += 1
- elif v == 0xa6:
- # 0xa6 -> 0xa6 0x01
- result[offset] = 0xa6
- offset += 1
- result[offset] = 0x01
- offset += 1
- elif v == 0x5a:
- # 0x5a -> 0x5b 0x02
- result[offset] = 0x5b
- offset += 1
- result[offset] = 0x02
- offset += 1
- elif v == 0x5b:
- # 0x5b -> 0x5b 0x01
- result[offset] = 0x5b
- offset += 1
- result[offset] = 0x01
- offset += 1
- else:
- # 普通字符,原样写入
- result[offset] = v
- offset += 1
- # ---------------- 5. 添加结尾的5A标记(不转义) ----------------
- for i in range(self.WRAP_5A_NUM):
- result[offset] = 0x5a
- offset += 1
- # ---------------- 6. 验证(确保offset等于总长度) ----------------
- if offset != total_len:
- raise ValueError(f"长度计算错误:offset={offset}, total_len={total_len}")
- return result
- # ---------------- 第一步:先补全辅助工具函数(避免bytearray/编码/长度错误) ----------------
- def _to_int(self, value):
- """2. 修复_to_int:强制兜底,确保0x8000能正确转换"""
- # 第一步:强制把源地址相关值转成0x8000(兜底!)
- if id(value) == id(self.srcAddr) or value in (0x8000, "0x8000", b'\x00\x80'):
- return 0x8000
- # 常规转换(补全所有边界情况)
- if value is None or value == "":
- return 0
- if isinstance(value, bytes):
- # 处理2字节的源地址字节(比如00 80)
- if len(value) == 2:
- return struct.unpack('<H', value)[0]
- return int.from_bytes(value, byteorder='little')
- elif isinstance(value, str):
- return int(value.strip(), 16) if value.startswith(('0x', '0X')) else int(value)
- else:
- # 强制转整数,避免溢出
- return int(value) & 0xFFFF # 保留16位,避免截断
- def _to_bytes(self, val) -> bytes:
- """将字符串/bytearray转为GB2312编码的bytes,避免中文乱码"""
- if isinstance(val, str):
- return val.encode('gb2312', errors='replace')
- elif isinstance(val, bytearray):
- return bytes(val)
- elif isinstance(val, bytes):
- return val
- return b""
- # ---------------- 第二步:修复版Pack方法(核心解决CRC问题) ----------------
- def Pack(self):
- # 构建数据包
- result = bytearray()
- # ---------------- 1. 基础字段写入(强制类型校验+对齐Go的包头!!!) ----------------
- # 写入目标地址(小端序,对齐Go的0x0001!!!)
- self.dstAddr = self.dstAddr # 强制设为Go的固定值
- result.extend(struct.pack('<H', self._to_int(self.dstAddr)))
- # 写入源地址(小端序,对齐Go的0x8000!!!)
- self.srcAddr = 0x8000 # 强制设为Go的固定值
- # print(f"[调试] 源地址转换后:{hex(self._to_int(self.srcAddr))}")
- result.extend(struct.pack('<H', self._to_int(self.srcAddr)))
- # 写入保留字
- result.append(self._to_int(self.r0))
- result.append(self._to_int(self.r1))
- result.append(self._to_int(self.r2))
- # 写入选项
- result.append(self._to_int(self.option))
- # 写入CRC模式
- result.append(self._to_int(self.crcMode))
- # 写入显示类型
- result.append(self._to_int(self.displayType))
- # 写入设备类型
- result.append(self._to_int(self.deviceType))
- # 写入版本
- result.append(self._to_int(self.version))
- # ---------------- 2. 数据长度(动态计算,保留) ----------------
- self.data = self._to_bytes(self.data)
- self.dataLen = len(self.data)
- result.extend(struct.pack('<H', self._to_int(self.dataLen)))
- # ---------------- 3. 写入数据(保留) ----------------
- result.extend(self.data)
- # ---------------- 4. 写入CRC占位符(初始为0,2字节小端序,保留) ----------------
- result.extend(struct.pack('<H', 0))
- # ---------------- 5. 计算CRC(100%对齐Go的调用参数!!!) ----------------
- crc_offset = 0
- crc_end_index = len(result) - 2 # 这里的length是结束索引!!!不是长度!!!
- # 调试打印1:明确标注是结束索引
- # print(f"[CRC调试] 待校验范围:offset={crc_offset}, 结束索引={crc_end_index}, 总result长度={len(result)}")
- # # 调试打印2:待校验数据(保留)
- # crc_data = result[crc_offset:crc_end_index] # 100%对齐Go的data[offset:length]!!!
- # print(f"[CRC调试] 待校验数据(十六进制):{crc_data.hex()}")
- # 调用1:1复刻的Go版CRC16函数(必须确保CRC16的切片是data[offset:length]!!!)
- crc = CRC16(result, crc_offset, crc_end_index)
- # 调试打印3:计算的CRC值(保留)
- # print(f"[CRC调试] 计算的CRC(十六进制整数):{hex(crc)}")
- # ---------------- 6. 更新CRC值(用struct.pack更直观,保留) ----------------
- crc_bytes = struct.pack('<H', crc)
- result[-2] = crc_bytes[0]
- result[-1] = crc_bytes[1]
- # 调试打印4:写入的CRC字节(保留)
- # print(f"[CRC调试] 写入的CRC字节(小端序):{crc_bytes.hex()}")
- # ---------------- 7. 转义数据(1:1复刻的Go版wrap,保留) ----------------
- return self.wrap(result)
- def dpParse(src, length):
- # 解包数据
- dst = unwrap(src, length)
- if dst is None:
- return None
-
- # 计算CRC
- crcCalculated = CRC16(dst, 0, len(dst) - 2)
- # 获取CRC
- crcGot = (dst[len(dst) - 1] << 8) | dst[len(dst) - 2]
-
- if crcCalculated != crcGot:
- return None
-
- # 解析数据
- dp = BxDataPack()
- offset = 0
-
- # 解析目标地址
- dp.dstAddr = struct.unpack_from('<H', dst, offset)[0]
- offset += 2
- # 解析源地址
- dp.srcAddr = struct.unpack_from('<H', dst, offset)[0]
- offset += 2
- # 解析保留字
- dp.r0 = dst[offset]
- offset += 1
- dp.r1 = dst[offset]
- offset += 1
- dp.r2 = dst[offset]
- offset += 1
- # 解析选项
- dp.option = dst[offset]
- offset += 1
- # 解析CRC模式
- dp.crcMode = dst[offset]
- offset += 1
- # 解析显示类型
- dp.displayType = dst[offset]
- offset += 1
- # 解析设备类型
- dp.deviceType = dst[offset]
- offset += 1
- # 解析版本
- dp.version = dst[offset]
- offset += 1
- # 解析数据长度
- dp.dataLen = struct.unpack_from('<H', dst, offset)[0]
- offset += 2
- # 解析数据
- dp.data = dst[offset:offset + dp.dataLen]
- offset += dp.dataLen
- # 解析CRC
- dp.crc = struct.unpack_from('<H', dst, offset)[0]
-
- return dp
- def unwrap(src, length):
- # 计算解包后的长度
- len_ = length
- for v in src:
- if v == 0xa5 or v == 0x5a or v == 0xa6 or v == 0x5b:
- len_ -= 1
-
- # 如果计算的帧长度为0, 说明数据不正确
- if len_ == 0:
- return None
-
- # 创建结果数组
- result = bytearray(len_)
- offset = 0
- i = 0
-
- while i < length:
- if src[i] == 0xa5 or src[i] == 0x5a:
- i += 1
- elif src[i] == 0xa6:
- if i + 1 < length:
- if src[i + 1] == 0x01:
- result[offset] = 0xa6
- offset += 1
- i += 2
- elif src[i + 1] == 0x02:
- result[offset] = 0xa5
- offset += 1
- i += 2
- else:
- return None
- else:
- return None
- elif src[i] == 0x5b:
- if i + 1 < length:
- if src[i + 1] == 0x01:
- result[offset] = 0x5b
- offset += 1
- i += 2
- elif src[i + 1] == 0x02:
- result[offset] = 0x5a
- offset += 1
- i += 2
- else:
- return None
- else:
- return None
- else:
- result[offset] = src[i]
- offset += 1
- i += 1
-
- return result
- # 创建BxDataPack实例的函数(从数据创建)
- def NewBxDataPackData(data):
- dp = BxDataPack()
- dp.data = data
- dp.dataLen = len(data)
- dp.WRAP_A5_NUM = 8
- dp.WRAP_5A_NUM = 1
- dp.dstAddr = 0x0001 # ✅ 旧通过值
- dp.srcAddr = 0x8000 # ✅ 旧通过值
- dp.crcMode = 0x01 # ✅ 旧通过值
- dp.displayType = 0xFE # ✅ 旧通过值
- dp.deviceType = 0x02 # ✅ 旧通过值
- dp.version = 0x45 # ✅ 旧通过值
- return dp
- # 创建BxDataPack实例的函数(从命令创建)
- def NewBxDataPackCmd(cmd,dst_addr):
- b = cmd.Build()
- dp = BxDataPack()
- dp.data = b
- dp.dataLen = len(b)
- dp.WRAP_A5_NUM = 8
- dp.WRAP_5A_NUM = 1
- dp.dstAddr = dst_addr # 设备ID
- dp.srcAddr = 0x8000
- dp.deviceType = 0xfe
- dp.version = 0x02
- return dp
|