radar.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. self.last_valid_time = 0.0 # 最后一次收到有效数据的时间
  22. self.screen_cleared = False # 屏幕是否已清空
  23. def update_valid_time(self):
  24. """更新有效数据时间"""
  25. self.last_valid_time = time.time()
  26. self.screen_cleared = False
  27. def check_and_clear(self, radar_screen: Optional[Screen]) -> bool:
  28. """
  29. 检查是否需要清空屏幕
  30. :return: 是否清空了屏幕
  31. """
  32. if radar_screen is None or not radar_screen.get_live_state():
  33. return False
  34. current_time = time.time()
  35. time_since_last_valid = current_time - self.last_valid_time
  36. if time_since_last_valid > self.timeout_seconds and not self.screen_cleared:
  37. # 超时了,清空屏幕
  38. print(f"⏱️ 雷达超时:{time_since_last_valid:.1f}秒无有效数据,清空屏幕 {radar_screen.conn.getpeername()}")
  39. try:
  40. ff = FlashFile()
  41. ff.set_msg("00", 0) # 发送空内容
  42. ff.set_mode(4, 2)
  43. ff.set_origin(0, True, 0)
  44. ff.set_area(128, True, 96)
  45. radar_screen.text_ram(ff, False)
  46. self.screen_cleared = True
  47. return True
  48. except Exception as e:
  49. print(f"❌ 清空屏幕失败:{e}")
  50. return False
  51. # 初始化全局的超时清空管理器
  52. screen_clear_manager = ScreenClearManager(timeout_seconds=2.0)
  53. # ========== 核心:解析单帧雷达数据(完全复刻原Go逻辑) ==========
  54. def parse_radar_frame(frame: bytes) -> RadarData:
  55. """
  56. 解析单帧雷达数据(兼容前缀空白、帧尾不完整)
  57. :param frame: 原始帧字节数据
  58. :return: 解析后的RadarData对象
  59. """
  60. data = RadarData()
  61. # ========== 1. 预处理帧数据:清理前缀空白字符 ==========
  62. trim_prefix_chars = {0x00, 0x0A, 0x0D, 0x20, 0x09}
  63. trimmed_frame = bytearray(frame)
  64. while len(trimmed_frame) > 0:
  65. if trimmed_frame[0] in trim_prefix_chars:
  66. trimmed_frame.pop(0)
  67. else:
  68. break
  69. # ========== 2. 基础校验:清理后至少保留7字节(V+001.9) ==========
  70. if len(trimmed_frame) < 7:
  71. return data
  72. # ========== 3. 校验帧头(首字节必须是ASCII的'V',0x56) ==========
  73. if trimmed_frame[0] != ord('V'):
  74. return data
  75. # ========== 4. 适配帧尾:兼容仅含\r(0x0D)或完整\r\n(0x0D+0x0A) ==========
  76. frame_tail_valid = False
  77. if len(trimmed_frame) >= 8 and trimmed_frame[7] == 0x0D:
  78. frame_tail_valid = True
  79. elif len(trimmed_frame) >= 9 and trimmed_frame[7] == 0x0D and trimmed_frame[8] == 0x0A:
  80. frame_tail_valid = True
  81. if not frame_tail_valid:
  82. return data
  83. # ========== 5. 解析方向(第2字节:+ 来向/in / - 去向/out) ==========
  84. direction_char = chr(trimmed_frame[1])
  85. if direction_char == '+':
  86. data.Direction = "in"
  87. elif direction_char == '-':
  88. data.Direction = "out"
  89. else:
  90. return data
  91. # ========== 6. 校验小数点位置(第6字节必须是'.',0x2E) ==========
  92. if trimmed_frame[5] != ord('.'):
  93. return data
  94. # ========== 7. ASCII字符转数字(如'1'→1) ==========
  95. try:
  96. hundreds = int(chr(trimmed_frame[2]))
  97. tens = int(chr(trimmed_frame[3]))
  98. units = int(chr(trimmed_frame[4]))
  99. decimal = int(chr(trimmed_frame[6]))
  100. except ValueError:
  101. return data
  102. # ========== 8. 计算最终速度值(xxx.x格式) ==========
  103. data.Speed = float(hundreds * 100 + tens * 10 + units) + float(decimal) / 10.0
  104. data.Valid = True
  105. # ========== 新增2:打印解析到的原始数据(调试用) ==========
  106. print(f"📡 雷达解析成功:方向={data.Direction},速度={data.Speed:.1f},原始帧={frame!r}")
  107. return data
  108. def callback_with_data(
  109. radar_data: RadarData,
  110. radar_serial=None,
  111. speaker: Optional[IpCast] = None,
  112. radar_screen: Optional[Screen] = None
  113. ):
  114. """
  115. 雷达数据回调(新增超时清空逻辑)
  116. """
  117. if radar_serial is None:
  118. import detect_plate
  119. radar_serial = detect_plate.radar_serial
  120. # 速度阈值判断(触发条件)
  121. if radar_data.Valid and radar_data.Speed > DeviceInitData.LowSpeed:
  122. color = 1
  123. rounded_speed = str(round(radar_data.Speed))
  124. if radar_data.Speed < 20:
  125. color = 2
  126. speak_text = f"注意来车"
  127. # 语音播报
  128. if speaker:
  129. try:
  130. speaker.Speak(speak_text)
  131. except Exception as e:
  132. print(f"语音播报失败:{e}")
  133. # 屏幕显示
  134. if radar_screen and radar_screen.get_live_state():
  135. try:
  136. ff = FlashFile()
  137. ff.set_msg(rounded_speed, color)
  138. ff.set_mode(4, 2)
  139. ff.set_origin(0, True, 0)
  140. ff.set_area(128, True, 96)
  141. radar_screen.text_ram(ff, False)
  142. # ========== 新增3:更新有效数据时间 ==========
  143. screen_clear_manager.update_valid_time()
  144. print(f"✅ 雷达速度{rounded_speed}已发送到屏幕")
  145. except Exception as e:
  146. print(f"雷达屏幕显示失败:{e}")
  147. def open_serial(
  148. port_name: str,
  149. speaker: Optional[IpCast] = None,
  150. radar_screen: Optional[Screen] = None,
  151. radar_serial_ref=None
  152. ):
  153. """打开雷达串口并监听(彻底解决残留数据问题)"""
  154. radar_serial = radar_serial_ref
  155. serial_options = {
  156. "port": port_name,
  157. "baudrate": 9600,
  158. "bytesize": serial.EIGHTBITS,
  159. "stopbits": serial.STOPBITS_ONE,
  160. "parity": serial.PARITY_NONE,
  161. "timeout": 0.1,
  162. }
  163. while True:
  164. try:
  165. radar_serial = serial.Serial(**serial_options)
  166. print(f"雷达串口 {port_name} 已打开")
  167. # 发送雷达配置指令
  168. radar_cmd = bytes([0x43, 0x46, 0x02, 0x01, 0x03, 0x00, 0x0d, 0x0a])
  169. send_len = radar_serial.write(radar_cmd)
  170. if send_len != len(radar_cmd):
  171. print(f"雷达配置指令发送不完整")
  172. time.sleep(0.1)
  173. read_buf: List[int] = []
  174. buf = bytearray(1024)
  175. last_clear_check = time.time()
  176. last_valid_data_time = time.time()
  177. while True:
  178. try:
  179. n = radar_serial.readinto(buf)
  180. # 定期检查超时清空
  181. current_time = time.time()
  182. if current_time - last_clear_check > 0.2:
  183. screen_clear_manager.check_and_clear(radar_screen)
  184. last_clear_check = current_time
  185. if n == 0:
  186. continue
  187. read_buf.extend(buf[:n])
  188. while len(read_buf) > 0:
  189. frame_end_idx = -1
  190. for i, b in enumerate(read_buf):
  191. if b == 0x0D or b == 0x0A:
  192. frame_end_idx = i
  193. break
  194. if frame_end_idx == -1:
  195. if len(read_buf) > 32:
  196. read_buf = read_buf[-16:]
  197. break
  198. # 解析雷达帧
  199. frame = bytes(read_buf[:frame_end_idx + 1])
  200. read_buf = read_buf[frame_end_idx + 1:]
  201. radar_data = parse_radar_frame(frame)
  202. if radar_data.Valid:
  203. data_time = time.time()
  204. time_since_last = data_time - last_valid_data_time
  205. # 更新时间
  206. last_valid_data_time = data_time
  207. last_clear_check = current_time
  208. # 日志显示
  209. # local_time = time.strftime("%H:%M:%S", time.localtime(data_time))
  210. # print(f"📡 [{local_time}] 速度={radar_data.Speed:.1f} (距上次{time_since_last:.2f}秒)")
  211. if radar_data.Speed < 5:
  212. continue
  213. # 触发回调
  214. callback_with_data(radar_data, radar_serial, speaker, radar_screen)
  215. read_buf = []
  216. break # 退出当前的帧处理循环
  217. except Exception as read_err:
  218. print(f"雷达读取错误:{read_err},3秒后重连")
  219. time.sleep(3)
  220. break
  221. except Exception as e:
  222. print(f"雷达串口打开失败:{e},3秒后重试")
  223. time.sleep(3)
  224. finally:
  225. if 'radar_serial' in locals() and radar_serial and radar_serial.is_open:
  226. radar_serial.close()
  227. print(f"雷达串口 {port_name} 已关闭")