diff --git a/src/GUI/gui.py b/src/GUI/gui.py index 4181593..83e84f7 100644 --- a/src/GUI/gui.py +++ b/src/GUI/gui.py @@ -147,7 +147,7 @@ class ToggleSwitch(QWidget): class ControlStationUI(QMainWindow): planning_finished = pyqtSignal(object) - VERSION = '2.4.0' + VERSION = '2.4.1' FONT_SCALE_MIN = 70 FONT_SCALE_MAX = 180 FONT_SCALE_DEFAULT = 100 @@ -194,6 +194,11 @@ class ControlStationUI(QMainWindow): self.panel_map_timer = QTimer() self.panel_map_timer.timeout.connect(self._update_panel_and_map) self.panel_map_timer.start(100) # 10Hz + + # Attitude 顯示器獨立高頻更新(30Hz),避免被 panel/table/map 節奏拖住 + self.attitude_timer = QTimer() + self.attitude_timer.timeout.connect(self._update_attitude_only) + self.attitude_timer.start(33) # 消息隊列處理定時器(來自線程的 GUI 更新) self.msg_queue_timer = QTimer() @@ -202,7 +207,9 @@ class ControlStationUI(QMainWindow): # 快取消息數據,以便在沒有新消息時使用上一次的值 self._message_cache = {} + self._attitude_cache = {} self._overview_cache = {} + self._map_dirty_drones = set() self.message_history = [] self.max_message_history = 500 @@ -230,10 +237,10 @@ class ControlStationUI(QMainWindow): # 初始化地圖 self.drone_map = DroneMap() - # 地圖更新獨立降頻(5Hz),避免 WebEngine / JS map 拖慢 panel 更新 + # 地圖更新獨立節流(10Hz),避免 WebEngine / JS map 拖慢 panel 更新 self.map_timer = QTimer() self.map_timer.timeout.connect(self._update_map_only) - self.map_timer.start(200) + self.map_timer.start(100) # Overview table 獨立批次更新(5Hz),避免每筆資料都重繪 Qt table self.overview_timer = QTimer() @@ -1603,6 +1610,8 @@ class ControlStationUI(QMainWindow): self._message_cache[drone_id] = {} self._message_cache[drone_id][msg_type] = data + if msg_type == 'attitude': + self._attitude_cache[drone_id] = data # ================================================================================ @@ -2242,14 +2251,15 @@ class ControlStationUI(QMainWindow): try: start_time = time.time() + pending_messages = self._message_cache + self._message_cache = {} - # ✅ 步驟 2: 遍歷快取中最新的資料來更新 UI - for drone_id in list(self._message_cache.keys()): + # ✅ 步驟 2: 只處理本批新資料,避免每個 tick 重跑舊資料造成週期性卡頓 + for drone_id, cached_data in pending_messages.items(): if drone_id not in self.drones: continue panel = self.drones[drone_id] - cached_data = self._message_cache[drone_id] # 處理所有快取的消息類型 for msg_type, data in cached_data.items(): @@ -2312,16 +2322,12 @@ class ControlStationUI(QMainWindow): self.queue_overview_update(drone_id, 'roll', f"{roll:.1f}°") self.queue_overview_update(drone_id, 'pitch', f"{pitch:.1f}°") self.queue_overview_update(drone_id, 'yaw', f"{yaw:.1f}°") - panel._last_roll = roll - panel._last_pitch = pitch - if hasattr(panel, 'update_attitude'): - heading = self.drone_headings.get(drone_id, yaw) - panel.update_attitude(heading, roll, pitch) elif msg_type == 'gps': gps_data = data lat, lon = gps_data.get('lat', 0), gps_data.get('lon', 0) self.drone_positions[drone_id] = (lat, lon) + self._map_dirty_drones.add(drone_id) alt = gps_data.get('alt', 0) if not hasattr(self.monitor, 'drone_gps'): self.monitor.drone_gps = {} @@ -2360,6 +2366,7 @@ class ControlStationUI(QMainWindow): hud_data = data heading = hud_data.get('heading', 0) self.drone_headings[drone_id] = heading + self._map_dirty_drones.add(drone_id) groundspeed = hud_data.get('groundspeed', 0) airspeed = hud_data.get('airspeed', 0) throttle = hud_data.get('throttle', 0) @@ -2387,11 +2394,43 @@ class ControlStationUI(QMainWindow): def _update_map_only(self): """獨立降頻更新地圖,避免地圖 JS 呼叫拖住 panel / table。""" - for drone_id, pos in list(self.drone_positions.items()): + dirty_drones = getattr(self, '_map_dirty_drones', set()) + if not dirty_drones: + return + + pending_drones = list(dirty_drones) + self._map_dirty_drones = set() + + for drone_id in pending_drones: + pos = self.drone_positions.get(drone_id) + if not pos: + continue heading = self.drone_headings.get(drone_id, 0) lat, lon = pos self.drone_map.update_drone_position(drone_id, lat, lon, heading) + def _update_attitude_only(self): + """高頻更新 drone panel 的 attitude 顯示器。""" + if not getattr(self, '_attitude_cache', None): + return + + latest_attitudes = self._attitude_cache + self._attitude_cache = {} + + for drone_id, data in latest_attitudes.items(): + panel = self.drones.get(drone_id) + if not panel or not hasattr(panel, 'update_attitude'): + continue + + roll = data.get('roll', 0) + pitch = data.get('pitch', 0) + yaw = data.get('yaw', 0) + heading = self.drone_headings.get(drone_id, yaw) + + panel._last_roll = roll + panel._last_pitch = pitch + panel.update_attitude(heading, roll, pitch) + def _process_message_queue(self): diff --git a/src/GUI/map_layout.py b/src/GUI/map_layout.py index c0239ff..9585e07 100644 --- a/src/GUI/map_layout.py +++ b/src/GUI/map_layout.py @@ -340,6 +340,7 @@ class DroneMap: var markers = {}; var idLabels = {}; var trajectories = {}; + const maxTrajectoryPoints = 300; var trajectoryLines = {}; var focusedId = null; var initialized = false; @@ -702,11 +703,12 @@ class DroneMap: const point = [lat, lon]; trajectories[id].push(point); - if (trajectories[id].length > 1000) { + if (trajectories[id].length > maxTrajectoryPoints) { trajectories[id].shift(); + trajectoryLines[id].setLatLngs(trajectories[id]); + } else { + trajectoryLines[id].addLatLng(point); } - - trajectoryLines[id].setLatLngs([...trajectories[id]]); } function focusOn(id) { @@ -878,7 +880,7 @@ class DroneMap: # 設置地圖更新計時器 self.map_update_timer = QTimer() self.map_update_timer.timeout.connect(self.update_map_positions) - self.map_update_timer.start(200) # 每 200ms 更新一次 + self.map_update_timer.start(100) # 每 100ms 更新一次(10Hz) def _on_map_loaded(self, ok: bool): """地圖加載完成回調"""