BxDataPack.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import struct
  2. from .BxUtils import CRC16
  3. class BxDataPack:
  4. def __init__(self, data=None, dataLen=0):
  5. self.WRAP_A5_NUM = 8
  6. self.WRAP_5A_NUM = 1
  7. self.dstAddr = 0x0001 # 设备ID(旧通过值)
  8. self.srcAddr = 0x8000 # 旧通过值
  9. self.r0 = 0
  10. self.r1 = 0
  11. self.r2 = 0
  12. self.option = 0
  13. self.crcMode = 0x01 # ✅ 修复:旧通过值0x01
  14. self.displayType = 0xFE # ✅ 修复:旧通过值0xFE
  15. self.deviceType = 0x02 # ✅ 修复:旧通过值0x02
  16. self.version = 0x45 # ✅ 修复:旧通过值0x45
  17. self.data = data if data else bytearray()
  18. self.dataLen = dataLen if dataLen > 0 else len(self.data)
  19. self.crc = 0
  20. def SetDisplayType(self, typ):
  21. """注:特殊动态区不支持动态模式
  22. 0x00:普通模式,动态区与节目可同时显示,但各区域不可重叠。
  23. 0x01:动态模式,优先显示动态区,无动态区则显示节目,动态区与节目区可重叠。"""
  24. self.displayType = typ
  25. # ---------------- 第二步:1:1精准复刻Go的wrap函数(每一行都对齐Go) ----------------
  26. def wrap(self, src: bytearray | bytes) -> bytearray:
  27. """
  28. 1:1精准复刻Go的wrap函数
  29. :param src: 未转义的源数据
  30. :return: 转义后的完整数据包
  31. """
  32. # ---------------- 1. 先计算总长度(完全对齐Go) ----------------
  33. src_len = len(src)
  34. total_len = src_len
  35. # 统计特殊字符,每个特殊字符会使长度+1
  36. for v in src:
  37. if v == 0xa5 or v == 0x5a or v == 0xa6 or v == 0x5b:
  38. total_len += 1
  39. # 加上开头和结尾的标记长度
  40. total_len += self.WRAP_A5_NUM
  41. total_len += self.WRAP_5A_NUM
  42. # ---------------- 2. 创建结果数组 ----------------
  43. result = bytearray(total_len)
  44. offset = 0
  45. # ---------------- 3. 添加开头的A5标记(不转义) ----------------
  46. for i in range(self.WRAP_A5_NUM):
  47. result[offset] = 0xa5
  48. offset += 1
  49. # ---------------- 4. 转义中间数据(核心逻辑) ----------------
  50. for v in src:
  51. if v == 0xa5:
  52. # 0xa5 -> 0xa6 0x02
  53. result[offset] = 0xa6
  54. offset += 1
  55. result[offset] = 0x02
  56. offset += 1
  57. elif v == 0xa6:
  58. # 0xa6 -> 0xa6 0x01
  59. result[offset] = 0xa6
  60. offset += 1
  61. result[offset] = 0x01
  62. offset += 1
  63. elif v == 0x5a:
  64. # 0x5a -> 0x5b 0x02
  65. result[offset] = 0x5b
  66. offset += 1
  67. result[offset] = 0x02
  68. offset += 1
  69. elif v == 0x5b:
  70. # 0x5b -> 0x5b 0x01
  71. result[offset] = 0x5b
  72. offset += 1
  73. result[offset] = 0x01
  74. offset += 1
  75. else:
  76. # 普通字符,原样写入
  77. result[offset] = v
  78. offset += 1
  79. # ---------------- 5. 添加结尾的5A标记(不转义) ----------------
  80. for i in range(self.WRAP_5A_NUM):
  81. result[offset] = 0x5a
  82. offset += 1
  83. # ---------------- 6. 验证(确保offset等于总长度) ----------------
  84. if offset != total_len:
  85. raise ValueError(f"长度计算错误:offset={offset}, total_len={total_len}")
  86. return result
  87. # ---------------- 第一步:先补全辅助工具函数(避免bytearray/编码/长度错误) ----------------
  88. def _to_int(self, value):
  89. """2. 修复_to_int:强制兜底,确保0x8000能正确转换"""
  90. # 第一步:强制把源地址相关值转成0x8000(兜底!)
  91. if id(value) == id(self.srcAddr) or value in (0x8000, "0x8000", b'\x00\x80'):
  92. return 0x8000
  93. # 常规转换(补全所有边界情况)
  94. if value is None or value == "":
  95. return 0
  96. if isinstance(value, bytes):
  97. # 处理2字节的源地址字节(比如00 80)
  98. if len(value) == 2:
  99. return struct.unpack('<H', value)[0]
  100. return int.from_bytes(value, byteorder='little')
  101. elif isinstance(value, str):
  102. return int(value.strip(), 16) if value.startswith(('0x', '0X')) else int(value)
  103. else:
  104. # 强制转整数,避免溢出
  105. return int(value) & 0xFFFF # 保留16位,避免截断
  106. def _to_bytes(self, val) -> bytes:
  107. """将字符串/bytearray转为GB2312编码的bytes,避免中文乱码"""
  108. if isinstance(val, str):
  109. return val.encode('gb2312', errors='replace')
  110. elif isinstance(val, bytearray):
  111. return bytes(val)
  112. elif isinstance(val, bytes):
  113. return val
  114. return b""
  115. # ---------------- 第二步:修复版Pack方法(核心解决CRC问题) ----------------
  116. def Pack(self):
  117. # 构建数据包
  118. result = bytearray()
  119. # ---------------- 1. 基础字段写入(强制类型校验+对齐Go的包头!!!) ----------------
  120. # 写入目标地址(小端序,对齐Go的0x0001!!!)
  121. self.dstAddr = self.dstAddr # 强制设为Go的固定值
  122. result.extend(struct.pack('<H', self._to_int(self.dstAddr)))
  123. # 写入源地址(小端序,对齐Go的0x8000!!!)
  124. self.srcAddr = 0x8000 # 强制设为Go的固定值
  125. # print(f"[调试] 源地址转换后:{hex(self._to_int(self.srcAddr))}")
  126. result.extend(struct.pack('<H', self._to_int(self.srcAddr)))
  127. # 写入保留字
  128. result.append(self._to_int(self.r0))
  129. result.append(self._to_int(self.r1))
  130. result.append(self._to_int(self.r2))
  131. # 写入选项
  132. result.append(self._to_int(self.option))
  133. # 写入CRC模式
  134. result.append(self._to_int(self.crcMode))
  135. # 写入显示类型
  136. result.append(self._to_int(self.displayType))
  137. # 写入设备类型
  138. result.append(self._to_int(self.deviceType))
  139. # 写入版本
  140. result.append(self._to_int(self.version))
  141. # ---------------- 2. 数据长度(动态计算,保留) ----------------
  142. self.data = self._to_bytes(self.data)
  143. self.dataLen = len(self.data)
  144. result.extend(struct.pack('<H', self._to_int(self.dataLen)))
  145. # ---------------- 3. 写入数据(保留) ----------------
  146. result.extend(self.data)
  147. # ---------------- 4. 写入CRC占位符(初始为0,2字节小端序,保留) ----------------
  148. result.extend(struct.pack('<H', 0))
  149. # ---------------- 5. 计算CRC(100%对齐Go的调用参数!!!) ----------------
  150. crc_offset = 0
  151. crc_end_index = len(result) - 2 # 这里的length是结束索引!!!不是长度!!!
  152. # 调试打印1:明确标注是结束索引
  153. # print(f"[CRC调试] 待校验范围:offset={crc_offset}, 结束索引={crc_end_index}, 总result长度={len(result)}")
  154. # # 调试打印2:待校验数据(保留)
  155. # crc_data = result[crc_offset:crc_end_index] # 100%对齐Go的data[offset:length]!!!
  156. # print(f"[CRC调试] 待校验数据(十六进制):{crc_data.hex()}")
  157. # 调用1:1复刻的Go版CRC16函数(必须确保CRC16的切片是data[offset:length]!!!)
  158. crc = CRC16(result, crc_offset, crc_end_index)
  159. # 调试打印3:计算的CRC值(保留)
  160. # print(f"[CRC调试] 计算的CRC(十六进制整数):{hex(crc)}")
  161. # ---------------- 6. 更新CRC值(用struct.pack更直观,保留) ----------------
  162. crc_bytes = struct.pack('<H', crc)
  163. result[-2] = crc_bytes[0]
  164. result[-1] = crc_bytes[1]
  165. # 调试打印4:写入的CRC字节(保留)
  166. # print(f"[CRC调试] 写入的CRC字节(小端序):{crc_bytes.hex()}")
  167. # ---------------- 7. 转义数据(1:1复刻的Go版wrap,保留) ----------------
  168. return self.wrap(result)
  169. def dpParse(src, length):
  170. # 解包数据
  171. dst = unwrap(src, length)
  172. if dst is None:
  173. return None
  174. # 计算CRC
  175. crcCalculated = CRC16(dst, 0, len(dst) - 2)
  176. # 获取CRC
  177. crcGot = (dst[len(dst) - 1] << 8) | dst[len(dst) - 2]
  178. if crcCalculated != crcGot:
  179. return None
  180. # 解析数据
  181. dp = BxDataPack()
  182. offset = 0
  183. # 解析目标地址
  184. dp.dstAddr = struct.unpack_from('<H', dst, offset)[0]
  185. offset += 2
  186. # 解析源地址
  187. dp.srcAddr = struct.unpack_from('<H', dst, offset)[0]
  188. offset += 2
  189. # 解析保留字
  190. dp.r0 = dst[offset]
  191. offset += 1
  192. dp.r1 = dst[offset]
  193. offset += 1
  194. dp.r2 = dst[offset]
  195. offset += 1
  196. # 解析选项
  197. dp.option = dst[offset]
  198. offset += 1
  199. # 解析CRC模式
  200. dp.crcMode = dst[offset]
  201. offset += 1
  202. # 解析显示类型
  203. dp.displayType = dst[offset]
  204. offset += 1
  205. # 解析设备类型
  206. dp.deviceType = dst[offset]
  207. offset += 1
  208. # 解析版本
  209. dp.version = dst[offset]
  210. offset += 1
  211. # 解析数据长度
  212. dp.dataLen = struct.unpack_from('<H', dst, offset)[0]
  213. offset += 2
  214. # 解析数据
  215. dp.data = dst[offset:offset + dp.dataLen]
  216. offset += dp.dataLen
  217. # 解析CRC
  218. dp.crc = struct.unpack_from('<H', dst, offset)[0]
  219. return dp
  220. def unwrap(src, length):
  221. # 计算解包后的长度
  222. len_ = length
  223. for v in src:
  224. if v == 0xa5 or v == 0x5a or v == 0xa6 or v == 0x5b:
  225. len_ -= 1
  226. # 如果计算的帧长度为0, 说明数据不正确
  227. if len_ == 0:
  228. return None
  229. # 创建结果数组
  230. result = bytearray(len_)
  231. offset = 0
  232. i = 0
  233. while i < length:
  234. if src[i] == 0xa5 or src[i] == 0x5a:
  235. i += 1
  236. elif src[i] == 0xa6:
  237. if i + 1 < length:
  238. if src[i + 1] == 0x01:
  239. result[offset] = 0xa6
  240. offset += 1
  241. i += 2
  242. elif src[i + 1] == 0x02:
  243. result[offset] = 0xa5
  244. offset += 1
  245. i += 2
  246. else:
  247. return None
  248. else:
  249. return None
  250. elif src[i] == 0x5b:
  251. if i + 1 < length:
  252. if src[i + 1] == 0x01:
  253. result[offset] = 0x5b
  254. offset += 1
  255. i += 2
  256. elif src[i + 1] == 0x02:
  257. result[offset] = 0x5a
  258. offset += 1
  259. i += 2
  260. else:
  261. return None
  262. else:
  263. return None
  264. else:
  265. result[offset] = src[i]
  266. offset += 1
  267. i += 1
  268. return result
  269. # 创建BxDataPack实例的函数(从数据创建)
  270. def NewBxDataPackData(data):
  271. dp = BxDataPack()
  272. dp.data = data
  273. dp.dataLen = len(data)
  274. dp.WRAP_A5_NUM = 8
  275. dp.WRAP_5A_NUM = 1
  276. dp.dstAddr = 0x0001 # ✅ 旧通过值
  277. dp.srcAddr = 0x8000 # ✅ 旧通过值
  278. dp.crcMode = 0x01 # ✅ 旧通过值
  279. dp.displayType = 0xFE # ✅ 旧通过值
  280. dp.deviceType = 0x02 # ✅ 旧通过值
  281. dp.version = 0x45 # ✅ 旧通过值
  282. return dp
  283. # 创建BxDataPack实例的函数(从命令创建)
  284. def NewBxDataPackCmd(cmd,dst_addr):
  285. b = cmd.Build()
  286. dp = BxDataPack()
  287. dp.data = b
  288. dp.dataLen = len(b)
  289. dp.WRAP_A5_NUM = 8
  290. dp.WRAP_5A_NUM = 1
  291. dp.dstAddr = dst_addr # 设备ID
  292. dp.srcAddr = 0x8000
  293. dp.deviceType = 0xfe
  294. dp.version = 0x02
  295. return dp