radar.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. import math
  2. import time
  3. from typing import List, Optional
  4. import serial
  5. from modules.audio.speaker import IpCast
  6. from modules.display.screen import Screen, FlashFile
  7. class RadarData:
  8. """对应Go的RadarData结构体"""
  9. def __init__(self):
  10. self.Valid: bool = False # 数据是否有效
  11. self.Direction: str = "" # 方向:in(来向)/out(去向)
  12. self.Speed: float = 0.0 # 速度(xxx.x格式)
  13. class DeviceInitData:
  14. """模拟Go的DeviceInitData,存储速度阈值(需根据实际业务赋值)"""
  15. LowSpeed: float = 0.0 # 速度阈值,比如5.0(速度>5触发回调)
  16. # ========== 新增1:超时清空状态管理(修复初始化+超大时间差) ==========
  17. class ScreenClearManager:
  18. """屏幕超时清空管理器(修复初始化问题)"""
  19. def __init__(self, timeout_seconds: float = 2.0):
  20. self.timeout_seconds = timeout_seconds # 超时时间(秒)
  21. # 初始化:使用当前时间,避免超大时间差
  22. self.last_valid_time = time.time()
  23. self.screen_cleared = False # 屏幕是否已清空
  24. def update_valid_time(self):
  25. """更新有效数据时间"""
  26. self.last_valid_time = time.time()
  27. self.screen_cleared = False
  28. def check_and_clear(self, radar_screen: Optional[Screen]) -> bool:
  29. """
  30. 检查是否需要清空屏幕(修复清空逻辑+错误处理)
  31. :return: 是否清空了屏幕
  32. """
  33. # 1. 屏幕实例不存在/未连接,直接返回
  34. if radar_screen is None or not hasattr(radar_screen, 'get_live_state') or not radar_screen.get_live_state():
  35. return False
  36. current_time = time.time()
  37. time_since_last_valid = current_time - self.last_valid_time
  38. # 2. 仅在超时且未清空时执行(修复超大时间差显示)
  39. if time_since_last_valid > self.timeout_seconds and not self.screen_cleared:
  40. # 限制最大显示时间差为timeout_seconds*2,避免超大数值
  41. display_seconds = min(time_since_last_valid, self.timeout_seconds * 2)
  42. # 获取屏幕地址(兼容不同Screen类实现)
  43. screen_addr = ""
  44. try:
  45. screen_addr = radar_screen.conn.getpeername() if hasattr(radar_screen, 'conn') else (
  46. radar_screen.ip, radar_screen.port)
  47. except:
  48. screen_addr = "未知地址"
  49. print(f"雷达超时:{display_seconds:.1f}秒无有效数据,清空屏幕 {screen_addr}")
  50. try:
  51. # 修复清空逻辑:优先使用安全的del_ram_text,兼容不同Screen实现
  52. if hasattr(radar_screen, 'del_ram_text'):
  53. radar_screen.del_ram_text(0)
  54. # 备选方案:显示空内容(兼容无del_ram_text的情况)
  55. elif hasattr(radar_screen, 'text_ram'):
  56. ff = FlashFile()
  57. ff.set_msg("", 0)
  58. ff.set_mode(4, 2)
  59. ff.set_origin(0, True, 0)
  60. ff.set_area(128, True, 96)
  61. radar_screen.text_ram(ff, False)
  62. self.screen_cleared = True
  63. return True
  64. except Exception as e:
  65. # 精准捕获错误,避免'dyna_area_num'报错刷屏
  66. if 'dyna_area_num' in str(e):
  67. print(f"清空屏幕失败:屏幕实例无'dyna_area_num'属性(非致命错误)")
  68. else:
  69. print(f"清空屏幕失败:{str(e)[:50]}") # 限制错误信息长度
  70. return False
  71. # 3. 未超时但已清空(恢复数据后重置)
  72. elif time_since_last_valid <= self.timeout_seconds and self.screen_cleared:
  73. self.screen_cleared = False
  74. return False
  75. # 初始化全局的超时清空管理器(确保启动时就初始化)
  76. screen_clear_manager = ScreenClearManager(timeout_seconds=2.0)
  77. # ========== 核心:解析单帧雷达数据(完全复刻原Go逻辑) ==========
  78. def parse_radar_frame(frame: bytes) -> RadarData:
  79. """
  80. 解析单帧雷达数据(兼容前缀空白、帧尾不完整)
  81. :param frame: 原始帧字节数据
  82. :return: 解析后的RadarData对象
  83. """
  84. data = RadarData()
  85. # ========== 1. 预处理帧数据:清理前缀空白字符 ==========
  86. trim_prefix_chars = {0x00, 0x0A, 0x0D, 0x20, 0x09}
  87. trimmed_frame = bytearray(frame)
  88. while len(trimmed_frame) > 0:
  89. if trimmed_frame[0] in trim_prefix_chars:
  90. trimmed_frame.pop(0)
  91. else:
  92. break
  93. # ========== 2. 基础校验:清理后至少保留7字节(V+001.9) ==========
  94. if len(trimmed_frame) < 7:
  95. return data
  96. # ========== 3. 校验帧头(首字节必须是ASCII的'V',0x56) ==========
  97. if trimmed_frame[0] != ord('V'):
  98. return data
  99. # ========== 4. 适配帧尾:兼容仅含\r(0x0D)或完整\r\n(0x0D+0x0A) ==========
  100. frame_tail_valid = False
  101. if len(trimmed_frame) >= 8 and trimmed_frame[7] == 0x0D:
  102. frame_tail_valid = True
  103. elif len(trimmed_frame) >= 9 and trimmed_frame[7] == 0x0D and trimmed_frame[8] == 0x0A:
  104. frame_tail_valid = True
  105. if not frame_tail_valid:
  106. return data
  107. # ========== 5. 解析方向(第2字节:+ 来向/in / - 去向/out) ==========
  108. direction_char = chr(trimmed_frame[1])
  109. if direction_char == '+':
  110. data.Direction = "in"
  111. elif direction_char == '-':
  112. data.Direction = "out"
  113. else:
  114. return data
  115. # ========== 6. 校验小数点位置(第6字节必须是'.',0x2E) ==========
  116. if trimmed_frame[5] != ord('.'):
  117. return data
  118. # ========== 7. ASCII字符转数字(如'1'→1) ==========
  119. try:
  120. hundreds = int(chr(trimmed_frame[2]))
  121. tens = int(chr(trimmed_frame[3]))
  122. units = int(chr(trimmed_frame[4]))
  123. decimal = int(chr(trimmed_frame[6]))
  124. except ValueError:
  125. return data
  126. # ========== 8. 计算最终速度值(xxx.x格式) ==========
  127. data.Speed = float(hundreds * 100 + tens * 10 + units) + float(decimal) / 10.0
  128. data.Valid = True
  129. # ========== 调试用:打印解析到的原始数据 ==========
  130. # print(f"📡 雷达解析成功:方向={data.Direction},速度={data.Speed:.1f},原始帧={frame!r}")
  131. return data
  132. def callback_with_data(
  133. radar_data: RadarData,
  134. radar_serial=None,
  135. speaker: Optional[IpCast] = None,
  136. radar_screen: Optional[Screen] = None
  137. ):
  138. """
  139. 雷达数据回调(新增超时清空逻辑)
  140. """
  141. if radar_serial is None:
  142. import detect_plate
  143. radar_serial = detect_plate.radar_serial
  144. # 速度阈值判断(触发条件)
  145. if radar_data.Valid and radar_data.Speed > DeviceInitData.LowSpeed:
  146. color = 1
  147. rounded_speed = str(round(radar_data.Speed))
  148. if radar_data.Speed < 20:
  149. color = 2
  150. speak_text = f"注意来车"
  151. # 语音播报
  152. if speaker:
  153. try:
  154. speaker.Speak(speak_text)
  155. except Exception as e:
  156. print(f"语音播报失败:{e}")
  157. # 屏幕显示
  158. if radar_screen and hasattr(radar_screen, 'get_live_state') and radar_screen.get_live_state():
  159. try:
  160. ff = FlashFile()
  161. ff.set_msg(rounded_speed, color)
  162. ff.set_mode(4, 2)
  163. ff.set_origin(0, True, 0)
  164. ff.set_area(128, True, 96)
  165. radar_screen.text_ram(ff, False)
  166. # ========== 更新有效数据时间 ==========
  167. screen_clear_manager.update_valid_time()
  168. # print(f"雷达速度{rounded_speed}已发送到屏幕")
  169. except Exception as e:
  170. print(f"雷达屏幕显示失败:{e}")
  171. def open_serial(
  172. port_name: str,
  173. speaker: Optional[IpCast] = None,
  174. radar_screen: Optional[Screen] = None,
  175. radar_serial_ref=None
  176. ):
  177. """打开雷达串口并监听(彻底解决残留数据+初始化问题)"""
  178. radar_serial = radar_serial_ref
  179. serial_options = {
  180. "port": port_name,
  181. "baudrate": 9600,
  182. "bytesize": serial.EIGHTBITS,
  183. "stopbits": serial.STOPBITS_ONE,
  184. "parity": serial.PARITY_NONE,
  185. "timeout": 0.1,
  186. }
  187. while True:
  188. try:
  189. radar_serial = serial.Serial(**serial_options)
  190. print(f"雷达串口 {port_name} 已打开")
  191. # 发送雷达配置指令
  192. radar_cmd = bytes([0x43, 0x46, 0x02, 0x01, 0x03, 0x00, 0x0d, 0x0a])
  193. send_len = radar_serial.write(radar_cmd)
  194. if send_len != len(radar_cmd):
  195. print(f"雷达配置指令发送不完整")
  196. time.sleep(0.1)
  197. read_buf: List[int] = []
  198. buf = bytearray(1024)
  199. last_clear_check = time.time()
  200. # 初始化有效数据时间(双重保障)
  201. last_valid_data_time = time.time()
  202. # 重置屏幕清空管理器(确保每次重连都初始化)
  203. screen_clear_manager.last_valid_time = time.time()
  204. screen_clear_manager.screen_cleared = False
  205. while True:
  206. try:
  207. n = radar_serial.readinto(buf)
  208. # 定期检查超时清空(0.2秒一次,避免高频检查)
  209. current_time = time.time()
  210. if current_time - last_clear_check > 0.2:
  211. screen_clear_manager.check_and_clear(radar_screen)
  212. last_clear_check = current_time
  213. if n == 0:
  214. continue
  215. read_buf.extend(buf[:n])
  216. while len(read_buf) > 0:
  217. frame_end_idx = -1
  218. for i, b in enumerate(read_buf):
  219. if b == 0x0D or b == 0x0A:
  220. frame_end_idx = i
  221. break
  222. if frame_end_idx == -1:
  223. if len(read_buf) > 32:
  224. read_buf = read_buf[-16:]
  225. break
  226. # 解析雷达帧
  227. frame = bytes(read_buf[:frame_end_idx + 1])
  228. read_buf = read_buf[frame_end_idx + 1:]
  229. radar_data = parse_radar_frame(frame)
  230. if radar_data.Valid:
  231. data_time = time.time()
  232. time_since_last = data_time - last_valid_data_time
  233. # 更新时间
  234. last_valid_data_time = data_time
  235. last_clear_check = current_time
  236. # 更新清空管理器的时间(核心修复:有数据时同步)
  237. screen_clear_manager.update_valid_time()
  238. # 日志显示
  239. # local_time = time.strftime("%H:%M:%S", time.localtime(data_time))
  240. # print(f"[{local_time}] 速度={radar_data.Speed:.1f} (距上次{time_since_last:.2f}秒)")
  241. if radar_data.Speed < 5:
  242. continue
  243. # 触发回调
  244. callback_with_data(radar_data, radar_serial, speaker, radar_screen)
  245. read_buf = []
  246. break # 退出当前的帧处理循环
  247. except Exception as read_err:
  248. print(f"雷达读取错误:{read_err},3秒后重连")
  249. time.sleep(3)
  250. break
  251. except Exception as e:
  252. print(f"雷达串口打开失败:{e},3秒后重试")
  253. time.sleep(3)
  254. finally:
  255. if 'radar_serial' in locals() and radar_serial and radar_serial.is_open:
  256. radar_serial.close()
  257. print(f"雷达串口 {port_name} 已关闭")