BxDataPack.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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, val) -> int:
  89. """将bytearray/bytes/str转为0-255整数,避免append报错"""
  90. if isinstance(val, (bytearray, bytes)):
  91. return val[0] if len(val) > 0 else 0
  92. elif isinstance(val, str):
  93. return int(val) if val.isdigit() else 0
  94. elif isinstance(val, int):
  95. return val & 0xFF # 限制0-255
  96. return 0
  97. def _to_bytes(self, val) -> bytes:
  98. """将字符串/bytearray转为GB2312编码的bytes,避免中文乱码"""
  99. if isinstance(val, str):
  100. return val.encode('gb2312', errors='replace')
  101. elif isinstance(val, bytearray):
  102. return bytes(val)
  103. elif isinstance(val, bytes):
  104. return val
  105. return b""
  106. # ---------------- 第二步:修复版Pack方法(核心解决CRC问题) ----------------
  107. def Pack(self):
  108. # 构建数据包
  109. result = bytearray()
  110. # ---------------- 1. 基础字段写入(强制类型校验+对齐Go的包头!!!) ----------------
  111. # 写入目标地址(小端序,对齐Go的0x0001!!!)
  112. self.dstAddr = self.dstAddr # 强制设为Go的固定值
  113. result.extend(struct.pack('<H', self._to_int(self.dstAddr)))
  114. # 写入源地址(小端序,对齐Go的0x8000!!!)
  115. self.srcAddr = 0x8000 # 强制设为Go的固定值
  116. result.extend(struct.pack('<H', self._to_int(self.srcAddr)))
  117. # 写入保留字
  118. result.append(self._to_int(self.r0))
  119. result.append(self._to_int(self.r1))
  120. result.append(self._to_int(self.r2))
  121. # 写入选项
  122. result.append(self._to_int(self.option))
  123. # 写入CRC模式
  124. result.append(self._to_int(self.crcMode))
  125. # 写入显示类型
  126. result.append(self._to_int(self.displayType))
  127. # 写入设备类型
  128. result.append(self._to_int(self.deviceType))
  129. # 写入版本
  130. result.append(self._to_int(self.version))
  131. # ---------------- 2. 数据长度(动态计算,保留) ----------------
  132. self.data = self._to_bytes(self.data)
  133. self.dataLen = len(self.data)
  134. result.extend(struct.pack('<H', self._to_int(self.dataLen)))
  135. # ---------------- 3. 写入数据(保留) ----------------
  136. result.extend(self.data)
  137. # ---------------- 4. 写入CRC占位符(初始为0,2字节小端序,保留) ----------------
  138. result.extend(struct.pack('<H', 0))
  139. # ---------------- 5. 计算CRC(100%对齐Go的调用参数!!!) ----------------
  140. crc_offset = 0
  141. crc_end_index = len(result) - 2 # 这里的length是结束索引!!!不是长度!!!
  142. # 调试打印1:明确标注是结束索引
  143. # print(f"[CRC调试] 待校验范围:offset={crc_offset}, 结束索引={crc_end_index}, 总result长度={len(result)}")
  144. # # 调试打印2:待校验数据(保留)
  145. # crc_data = result[crc_offset:crc_end_index] # 100%对齐Go的data[offset:length]!!!
  146. # print(f"[CRC调试] 待校验数据(十六进制):{crc_data.hex()}")
  147. # 调用1:1复刻的Go版CRC16函数(必须确保CRC16的切片是data[offset:length]!!!)
  148. crc = CRC16(result, crc_offset, crc_end_index)
  149. # 调试打印3:计算的CRC值(保留)
  150. # print(f"[CRC调试] 计算的CRC(十六进制整数):{hex(crc)}")
  151. # ---------------- 6. 更新CRC值(用struct.pack更直观,保留) ----------------
  152. crc_bytes = struct.pack('<H', crc)
  153. result[-2] = crc_bytes[0]
  154. result[-1] = crc_bytes[1]
  155. # 调试打印4:写入的CRC字节(保留)
  156. # print(f"[CRC调试] 写入的CRC字节(小端序):{crc_bytes.hex()}")
  157. # ---------------- 7. 转义数据(1:1复刻的Go版wrap,保留) ----------------
  158. return self.wrap(result)
  159. def dpParse(src, length):
  160. # 解包数据
  161. dst = unwrap(src, length)
  162. if dst is None:
  163. return None
  164. # 计算CRC
  165. crcCalculated = CRC16(dst, 0, len(dst) - 2)
  166. # 获取CRC
  167. crcGot = (dst[len(dst) - 1] << 8) | dst[len(dst) - 2]
  168. if crcCalculated != crcGot:
  169. return None
  170. # 解析数据
  171. dp = BxDataPack()
  172. offset = 0
  173. # 解析目标地址
  174. dp.dstAddr = struct.unpack_from('<H', dst, offset)[0]
  175. offset += 2
  176. # 解析源地址
  177. dp.srcAddr = struct.unpack_from('<H', dst, offset)[0]
  178. offset += 2
  179. # 解析保留字
  180. dp.r0 = dst[offset]
  181. offset += 1
  182. dp.r1 = dst[offset]
  183. offset += 1
  184. dp.r2 = dst[offset]
  185. offset += 1
  186. # 解析选项
  187. dp.option = dst[offset]
  188. offset += 1
  189. # 解析CRC模式
  190. dp.crcMode = dst[offset]
  191. offset += 1
  192. # 解析显示类型
  193. dp.displayType = dst[offset]
  194. offset += 1
  195. # 解析设备类型
  196. dp.deviceType = dst[offset]
  197. offset += 1
  198. # 解析版本
  199. dp.version = dst[offset]
  200. offset += 1
  201. # 解析数据长度
  202. dp.dataLen = struct.unpack_from('<H', dst, offset)[0]
  203. offset += 2
  204. # 解析数据
  205. dp.data = dst[offset:offset + dp.dataLen]
  206. offset += dp.dataLen
  207. # 解析CRC
  208. dp.crc = struct.unpack_from('<H', dst, offset)[0]
  209. return dp
  210. def unwrap(src, length):
  211. # 计算解包后的长度
  212. len_ = length
  213. for v in src:
  214. if v == 0xa5 or v == 0x5a or v == 0xa6 or v == 0x5b:
  215. len_ -= 1
  216. # 如果计算的帧长度为0, 说明数据不正确
  217. if len_ == 0:
  218. return None
  219. # 创建结果数组
  220. result = bytearray(len_)
  221. offset = 0
  222. i = 0
  223. while i < length:
  224. if src[i] == 0xa5 or src[i] == 0x5a:
  225. i += 1
  226. elif src[i] == 0xa6:
  227. if i + 1 < length:
  228. if src[i + 1] == 0x01:
  229. result[offset] = 0xa6
  230. offset += 1
  231. i += 2
  232. elif src[i + 1] == 0x02:
  233. result[offset] = 0xa5
  234. offset += 1
  235. i += 2
  236. else:
  237. return None
  238. else:
  239. return None
  240. elif src[i] == 0x5b:
  241. if i + 1 < length:
  242. if src[i + 1] == 0x01:
  243. result[offset] = 0x5b
  244. offset += 1
  245. i += 2
  246. elif src[i + 1] == 0x02:
  247. result[offset] = 0x5a
  248. offset += 1
  249. i += 2
  250. else:
  251. return None
  252. else:
  253. return None
  254. else:
  255. result[offset] = src[i]
  256. offset += 1
  257. i += 1
  258. return result
  259. # 创建BxDataPack实例的函数(从数据创建)
  260. def NewBxDataPackData(data):
  261. dp = BxDataPack()
  262. dp.data = data
  263. dp.dataLen = len(data)
  264. dp.WRAP_A5_NUM = 8
  265. dp.WRAP_5A_NUM = 1
  266. dp.dstAddr = 0x0001 # ✅ 旧通过值
  267. dp.srcAddr = 0x8000 # ✅ 旧通过值
  268. dp.crcMode = 0x01 # ✅ 旧通过值
  269. dp.displayType = 0xFE # ✅ 旧通过值
  270. dp.deviceType = 0x02 # ✅ 旧通过值
  271. dp.version = 0x45 # ✅ 旧通过值
  272. return dp
  273. # 创建BxDataPack实例的函数(从命令创建)
  274. def NewBxDataPackCmd(cmd,dst_addr):
  275. b = cmd.Build()
  276. dp = BxDataPack()
  277. dp.data = b
  278. dp.dataLen = len(b)
  279. dp.WRAP_A5_NUM = 8
  280. dp.WRAP_5A_NUM = 1
  281. dp.dstAddr = dst_addr # 设备ID
  282. dp.srcAddr = 0x8000
  283. dp.deviceType = 0xfe
  284. dp.version = 0x02
  285. return dp