diff --git a/src/GUI/BEFORE_AFTER_COMPARISON.md b/src/GUI/BEFORE_AFTER_COMPARISON.md deleted file mode 100644 index c9657de..0000000 --- a/src/GUI/BEFORE_AFTER_COMPARISON.md +++ /dev/null @@ -1,218 +0,0 @@ -# 實現前後對比 - -## 系統架構變化 - -### 舊架構(直接更新) -``` -接收執行緒 - ↓ -monitor.latest_data - ↓ -spin_ros() [10ms] - ↓ -update_ui() [100% 直接更新 UI] - ├─ GPS → 立即更新地圖和表格 - ├─ HUD → 立即更新 Panel 和地圖 - ├─ State → 立即更新 State - ├─ Battery → 立即更新 Battery - └─ ... 等等 - -❌ 問題: - - Map 更新頻率太高,可能導致 CPU 過度使用 - - 高頻率的連續更新可能造成視覺閃爍 - - 沒有批次更新機制 -``` - -### 新架構(分層更新) -``` -接收執行緒 - ↓ -monitor.latest_data - ↓ -spin_ros() [10ms] - ↓ -update_ui() - ├─ GPS/HUD → 快取到 _message_cache - ├─ State → 立即更新 - ├─ Battery → 立即更新 - └─ ... 等等 - ↓ -_update_panel_and_map() [100ms / 10Hz] - ├─ 讀取 _message_cache GPS 資料 - │ └─ 更新地圖位置和表格 - ├─ 讀取 _message_cache HUD 資料 - │ └─ 更新 Panel 和地圖方向 - └─ 使用上次快取值(如無新消息) - -✅ 優勢: - - Map 和 Panel 只在 10Hz 更新,降低 CPU 負荷 - - 批次更新確保原子性 - - 消息未更新時使用上次值,資訊連續性好 - - 分層設計允許不同組件不同更新率 -``` - -## 代碼實現對比 - -### 舊代碼片段(GPS 更新) -```python -def update_ui(self, msg_type, drone_id, data): - ... - elif msg_type == 'gps': - lat, lon = data.get('lat', 0), data.get('lon', 0) - self.drone_positions[drone_id] = (lat, lon) - alt = data.get('alt', 0) - if not hasattr(self.monitor, 'drone_gps'): - self.monitor.drone_gps = {} - self.monitor.drone_gps[drone_id] = {'lat': lat, 'lon': lon, 'alt': alt} - self.update_overview_table(drone_id, 'latitude', f"{lat:.6f}°") - self.update_overview_table(drone_id, 'longitude', f"{lon:.6f}°") - heading = self.drone_headings.get(drone_id, 0) - self.drone_map.update_drone_position(drone_id, lat, lon, heading) # ← 直接更新 Map -``` - -### 新代碼片段(GPS 快取) -```python -def update_ui(self, msg_type, drone_id, data): - ... - # 快取 GPS 和 HUD 資料用於 panel/map 的 10Hz 更新 - if msg_type in ('gps', 'hud'): - if drone_id not in self._message_cache: - self._message_cache[drone_id] = {} - self._message_cache[drone_id][msg_type] = data - # 不在這裡更新,等待 _update_panel_and_map 在 10Hz 執行 - return -``` - -### 新代碼片段(10Hz 批次更新) -```python -def _update_panel_and_map(self): - """10Hz 定時更新 panel 和 map,使用快取的 GPS 和 HUD 消息""" - for drone_id in list(self._message_cache.keys()): - cache = self._message_cache[drone_id] - - # 使用快取的 GPS 資料 - if 'gps' in cache: - gps_data = cache['gps'] - lat, lon = gps_data.get('lat', 0), gps_data.get('lon', 0) - self.drone_positions[drone_id] = (lat, lon) - # ... 更新表格 ... - - # 使用快取的 HUD 資料 - if 'hud' in cache: - # ... 更新 panel 和 map ... - self.drone_map.update_drone_position(drone_id, lat, lon, heading) # ← 10Hz 更新 -``` - -## 性能影響分析 - -### Map 更新頻率 - -| 場景 | 舊架構 | 新架構 | 改進 | -|------|--------|--------|------| -| 單架無人機 | ~50Hz | 10Hz | ↓ 80% | -| 5 架無人機 | ~250Hz | 10Hz | ↓ 96% | -| 10 架無人機 | ~500Hz | 10Hz | ↓ 98% | - -### 消息快取大小 - -``` -最大快取大小 = num_drones × 2 messages (gps + hud) - -例如:10 架無人機 - 最大快取: 10 × 2 = 20 個消息 - 內存使用: ~10KB (非常小) -``` - -## 延遲分析 - -### 位置更新延遲 - -``` -GPS 消息到達 - ↓ -快取到 _message_cache - ↓ -等待至多 100ms(下一個 10Hz 週期) - ↓ -_update_panel_and_map() 讀取並更新地圖 - ↓ -總延遲: 0-100ms - -用戶體驗:無可見延遲(100ms 對人眼不可察) -``` - -## 初始化檢查清單 - -在啟動 GUI 時確保: - -- [ ] `panel_map_timer` 已初始化並啟動 - ```python - self.panel_map_timer = QTimer() - self.panel_map_timer.timeout.connect(self._update_panel_and_map) - self.panel_map_timer.start(100) - ``` - -- [ ] `_message_cache` 已初始化為空字典 - ```python - self._message_cache = {} - ``` - -- [ ] `update_ui()` 正確快取 GPS/HUD 消息 - ```python - if msg_type in ('gps', 'hud'): - # 快取邏輯 - ``` - -- [ ] `_update_panel_and_map()` 方法存在且被連接 - ```python - self.panel_map_timer.timeout.connect(self._update_panel_and_map) - ``` - -## 監控 UI - -### 添加調試輸出(可選) - -在 `_update_panel_and_map()` 開始添加: - -```python -import time - -if not hasattr(self, '_debug_map_time'): - self._debug_map_time = time.time() - self._debug_map_count = 0 - -self._debug_map_count += 1 -if time.time() - self._debug_map_time >= 1.0: - cached_drones = len(self._message_cache) - updated_drones = sum(1 for d in self._message_cache.values() if d) - print(f"[10Hz] Cycle {self._debug_map_count} | " - f"Cached: {cached_drones} | " - f"Updated: {updated_drones}") - self._debug_map_time = time.time() - self._debug_map_count = 0 -``` - -## 故障診斷 - -如果地圖或 Panel 沒有更新: - -1. **檢查定時器是否運行** - ```python - print(f"Timer active: {self.panel_map_timer.isActive()}") - ``` - -2. **檢查快取是否有數據** - ```python - print(f"Cache: {self._message_cache}") - ``` - -3. **檢查方法是否被調用** - - 在 `_update_panel_and_map()` 開始添加 `print("_update_panel_and_map called")` - -4. **檢查 update_drone_position 是否有錯誤** - - 查看控制台輸出,是否有異常拋出 - ---- - -**版本**: 2025-03-25 -**狀態**: ✅ 生產就緒 diff --git a/src/GUI/IMPLEMENTATION_SUMMARY.md b/src/GUI/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 4e32a63..0000000 --- a/src/GUI/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,119 +0,0 @@ -# Panel 和 Map 10Hz 更新實現 - 完成總結 - -## 實現完成 ✅ - -已成功實現 **Panel(DronePanel)和 Map(DroneMap)的 10Hz 更新機制**,同時其他 UI 元素保持更快的更新速率。 - -## 關鍵改動 - -### 1. **初始化 10Hz 定時器** (`__init__`) -```python -# 初始化 panel 和 map 更新(10Hz) -self.panel_map_timer = QTimer() -self.panel_map_timer.timeout.connect(self._update_panel_and_map) -self.panel_map_timer.start(100) # 10Hz - -# 快取消息數據,以便在沒有新消息時使用上一次的值 -self._message_cache = {} -``` - -### 2. **快取 GPS 和 HUD 消息** (`update_ui`) -```python -# 快取 GPS 和 HUD 資料用於 panel/map 的 10Hz 更新 -if msg_type in ('gps', 'hud'): - if drone_id not in self._message_cache: - self._message_cache[drone_id] = {} - self._message_cache[drone_id][msg_type] = data - # 不在這裡更新,等待 _update_panel_and_map 在 10Hz 執行 - return -``` - -### 3. **實現 10Hz 批次更新方法** (`_update_panel_and_map`) -- 每 100ms 執行一次 -- 讀取快取的 GPS 資料更新位置和表格 -- 讀取快取的 HUD 資料更新標題、速度、高度等 -- 更新 Panel 顯示 -- 更新地圖位置和無人機方向 -- 如果消息未更新,使用上一次快取的值 - -### 4. **移除舊的直接 GPS/HUD 更新** -- 從 `update_ui()` 中移除了 `msg_type == 'gps'` 的直接更新邏輯 -- 從 `update_ui()` 中移除了 `msg_type == 'hud'` 的直接更新邏輯 -- 這些操作現在由 `_update_panel_and_map()` 在 10Hz 執行 - -## 更新頻率 - -| 組件 | 頻率 | 說明 | -|------|------|------| -| GPS 位置 | 10Hz | 快取並批次更新 | -| HUD (標題、速度、高度) | 10Hz | 快取並批次更新 | -| Map 更新 | 10Hz | 隨 HUD/GPS 更新 | -| Panel 顯示 | 10Hz | 隨 HUD 更新 | -| State/Battery/Altitude | 即時 | 保持快速響應 | -| Loss Rate/Ping | 即時 | 保持快速響應 | - -## 數據持久性 - -當消息未被更新時,系統使用上一次的值: -- `_message_cache` 保留最後接收的 GPS 和 HUD 數據 -- 即使沒有新消息,`_update_panel_and_map()` 仍然會使用快取值執行更新 -- 確保 Panel 和 Map 始終顯示最新已知的無人機位置和姿態 - -## 檔案修改 - -### `/home/dodo/Downloads/AirTrapMine/src/GUI/gui.py` -- ✅ 添加 `panel_map_timer` 初始化 -- ✅ 添加 `_message_cache` 初始化 -- ✅ 修改 `update_ui()` 快取 GPS/HUD 消息 -- ✅ 添加 `_update_panel_and_map()` 方法 -- ✅ 移除舊的 GPS/HUD 直接更新邏輯 -- ✅ 所有語法檢查通過 ✓ - -## 文檔 - -### 新建文檔 -- `PANEL_MAP_UPDATE.md` - 詳細的 10Hz 更新機制說明、故障排除和監控指南 - -## 驗證 - -### 語法驗證 ✅ -```bash -$ python -m py_compile gui.py -✓ Syntax check passed -``` - -### 錯誤檢查 ✅ -``` -No errors found -``` - -## 下一步驗證 - -如果需要進一步驗證,可以在代碼中添加: - -```python -# 在 _update_panel_and_map() 中添加頻率監控 -import time - -if not hasattr(self, '_map_update_time'): - self._map_update_time = time.time() - self._map_update_count = 0 - -self._map_update_count += 1 -now = time.time() -if now - self._map_update_time >= 1.0: - print(f"[Panel/Map] Update frequency: {self._map_update_count} Hz") - self._map_update_time = now - self._map_update_count = 0 -``` - -## 性能預期 - -- **Map 和 Panel 的 CPU 使用**: 降低(從 ~100Hz 降至 10Hz) -- **用戶體驗**: 流暢,無可見延遲(100ms 最大延遲) -- **數據新鮮度**: 優秀(100ms 更新週期內最新值) - ---- - -**完成日期**: 2025-03-25 -**狀態**: ✅ 實現完成,語法驗證通過 diff --git a/src/GUI/PANEL_MAP_UPDATE.md b/src/GUI/PANEL_MAP_UPDATE.md deleted file mode 100644 index cad76c4..0000000 --- a/src/GUI/PANEL_MAP_UPDATE.md +++ /dev/null @@ -1,189 +0,0 @@ -# Panel 和 Map 10Hz 更新機制 - -## 概述 - -Panel(DronePanel)和 Map(DroneMap)的更新率已優化為 **10Hz(每 100ms 更新一次)**,同時其他 UI 元素保持更快的更新速率。這確保了地圖和面板在資訊流量大時不會過度刷新,同時保持流暢的用戶體驗。 - -## 架構 - -### 資料流 - -``` -接收執行緒 (高頻) - ↓ -monitor.latest_data - ↓ -spin_ros() [10ms] - 發送信號 - ↓ -update_ui() [快速更新] - ├─ State, Battery, Altitude, etc. → 直接更新 (快速) - └─ GPS, HUD → 快取到 _message_cache - ↓ -_update_panel_and_map() [100ms / 10Hz] - ├─ 讀取 _message_cache 的 GPS 資料 - │ └─ 更新經緯度表格 - ├─ 讀取 _message_cache 的 HUD 資料 - │ ├─ 更新標題、速度、高度等 - │ └─ 更新 Panel 顯示 - └─ 更新地圖位置和無人機方向 -``` - -### 關鍵特性 - -1. **消息快取 (`_message_cache`)** - - GPS 和 HUD 消息被快取而不是立即處理 - - 如果在 10Hz 更新週期內沒有收到新消息,使用上一次的值 - - 避免因快速連續的消息導致過度刷新 - -2. **分層更新** - - **快速更新** (on-demand): State, Battery, Altitude, Loss Rate, Ping 等 - - **10Hz 更新**: GPS 位置, HUD(標題、速度、高度、爬升率), Panel 和 Map - -3. **定時器機制** - ```python - # 10Hz 定時器 - self.panel_map_timer = QTimer() - self.panel_map_timer.timeout.connect(self._update_panel_and_map) - self.panel_map_timer.start(100) # 100ms = 10Hz - ``` - -## 實現細節 - -### 步驟 1: 快取消息 - -在 `update_ui()` 中,GPS 和 HUD 消息被快取: - -```python -# 快取 GPS 和 HUD 資料用於 panel/map 的 10Hz 更新 -if msg_type in ('gps', 'hud'): - if drone_id not in self._message_cache: - self._message_cache[drone_id] = {} - self._message_cache[drone_id][msg_type] = data - # 不在這裡更新,等待 _update_panel_and_map 在 10Hz 執行 - return -``` - -### 步驟 2: 10Hz 批次更新 - -每 100ms,`_update_panel_and_map()` 被調用: - -```python -def _update_panel_and_map(self): - """10Hz 定時更新 panel 和 map,使用快取的 GPS 和 HUD 消息""" - for drone_id in list(self._message_cache.keys()): - cache = self._message_cache[drone_id] - - # 使用快取的 GPS 資料 - if 'gps' in cache: - gps_data = cache['gps'] - lat, lon = gps_data.get('lat', 0), gps_data.get('lon', 0) - self.drone_positions[drone_id] = (lat, lon) - # ... 更新表格 - - # 使用快取的 HUD 資料 - if 'hud' in cache: - hud_data = cache['hud'] - heading = hud_data.get('heading', 0) - # ... 更新 panel 和 map - self.drone_map.update_drone_position(drone_id, lat, lon, heading) -``` - -### 步驟 3: 持久化數據 - -即使沒有新消息,先前快取的值仍然存在於 `_message_cache` 中,所以 `_update_panel_and_map()` 將在下一個 10Hz 周期使用它: - -```python -# 第一個週期:GPS 消息到達 -_message_cache = { - 'drone_0': {'gps': {'lat': 23.123, 'lon': 120.456, ...}} -} - -# 第二個週期:沒有新 GPS 消息,但仍使用前一個值 -_update_panel_and_map() 會使用 'drone_0' 的上一個 GPS 位置 -``` - -## 性能影響 - -### 優點 -- **降低 Map 更新頻率**: 避免過度繪製導致 CPU 負荷 -- **更流暢的 UI**: 批次更新減少了視覺閃爍 -- **減少同步開銷**: 地圖位置和面板資訊一起批次更新 - -### 更新頻率對比 - -| 組件 | 舊速率 | 新速率 | 說明 | -|------|--------|--------|------| -| State/Battery/Altitude | 即時 | 即時 | 保持快速響應 | -| GPS/HUD 消息 | 即時 | 10Hz | 快取並批次更新 | -| Map 更新 | 即時 | 10Hz | 隨 HUD 更新 | -| Panel 顯示 | 即時 | 10Hz | 隨 HUD 更新 | - -## 故障排除 - -### Panel 或 Map 沒有更新 -1. 檢查 `panel_map_timer` 是否啟動 - ```python - print(self.panel_map_timer.isActive()) # 應該是 True - ``` - -2. 驗證 `_update_panel_and_map()` 是否被調用 - - 在方法開始添加 `print(f"Panel/Map update: {len(self._message_cache)} drones")` - -3. 檢查快取是否有數據 - ```python - print(self._message_cache) # 應該看到 drone_id 和消息 - ``` - -### 數據延遲或重複 - -如果看到數據延遲(最多 100ms),這是正常的。這就是為什麼我們使用快取 - 確保最新值始終可用。 - -如果看到重複更新,檢查是否有多個地方在調用 `update_drone_position()`。 - -## 初始化 - -在 `__init__` 中添加: - -```python -# 初始化 panel 和 map 更新(10Hz) -self.panel_map_timer = QTimer() -self.panel_map_timer.timeout.connect(self._update_panel_and_map) -self.panel_map_timer.start(100) # 10Hz - -# 快取消息數據,以便在沒有新消息時使用上一次的值 -self._message_cache = {} -``` - -## 監控與調試 - -### 列印更新頻率 - -添加到 `_update_panel_and_map()`: - -```python -def _update_panel_and_map(self): - if not hasattr(self, '_map_update_count'): - self._map_update_count = 0 - self._map_update_time = time.time() - - self._map_update_count += 1 - if time.time() - self._map_update_time >= 1.0: - print(f"Panel/Map update frequency: {self._map_update_count} Hz") - self._map_update_count = 0 - self._map_update_time = time.time() - - # ... 其餘代碼 -``` - -### 快取大小監控 - -```python -print(f"Cache size: {len(self._message_cache)} drones") -for drone_id, cache in self._message_cache.items(): - print(f" {drone_id}: {list(cache.keys())}") -``` - ---- - -**更新日期**: 2025-03-25 -**版本**: Panel/Map 10Hz 優化 v1.0 diff --git a/src/GUI/THREAD_SAFETY.md b/src/GUI/THREAD_SAFETY.md deleted file mode 100644 index 0b68514..0000000 --- a/src/GUI/THREAD_SAFETY.md +++ /dev/null @@ -1,218 +0,0 @@ -# 執行緒安全性實現 - GCS GUI - -## 架構概述 - -GCS GUI 採用 **拉取式 UI 更新架構** 以確保執行緒安全和高效的UI渲染,避免UI執行緒被資料收集堵塞。 - -## 執行緒模型 - -### 1. **主 GUI 執行緒** (Qt Event Loop) -- 負責所有 UI 操作(更新標籤、表格、地圖、ADI 等) -- 執行 `_process_ui_updates()` 每 33ms(30Hz) - -### 2. **ROS 執行緒** (Single-threaded Executor) -- 執行 `spin_ros()` 定時器每 10ms -- 只負責收集資料並快取,**不進行任何 UI 操作** - -### 3. **背景接收執行緒** (Receiver Threads) -- UDP/WebSocket/Serial 接收器在獨立執行緒上運行 -- 寫入 `monitor.latest_data` 字典(快取層) - -### 4. **任務執行執行緒** (Mission Executor) -- 運行在獨立執行緒上 -- 不直接訪問 UI 元素 - -## 資料流 - -``` -接收執行緒 - ↓ -monitor.latest_data (共享字典) - ↓ -spin_ros() [UI 執行緒, 10ms] - ↓ (快取資料,不更新 UI) -_ui_update_cache (字典) - ↓ -_process_ui_updates() [UI 執行緒, 30Hz] - ↓ (批次處理,更新 UI) -DronePanel, OverviewTable, DroneMap -``` - -## 執行緒安全機制 - -### 1. **_ui_cache_lock (布林鎖)** -```python -# 在 __init__ 中初始化 -self._ui_cache_lock = False -self._ui_update_cache = {} - -# 在 spin_ros() 中快取資料 -self._ui_update_cache[drone_id][msg_type] = data - -# 在 _process_ui_updates() 中保護快取訪問 -if self._ui_cache_lock or not self._ui_update_cache: - return -self._ui_cache_lock = True -try: - # 處理快取資料 - for drone_id in list(self._ui_update_cache.keys()): - ... -finally: - self._ui_cache_lock = False -``` - -### 2. **drone_positions 與 drone_headings 訪問** -- **寫入**: 在 `_update_gps_ui()` 和 `_update_hud_ui()` 中進行 - - 這些方法**只在 UI 執行緒**的 `_process_ui_updates()` 中調用 - -- **讀取**: 在 `_update_attitude_ui()` 和 `_update_hud_ui()` 中讀取 - - 這些方法**只在 UI 執行緒**的 `_process_ui_updates()` 中調用 - -```python -# 只在 UI 執行緒上讀寫 -heading = self.drone_headings.get(drone_id, 0) # 讀取 -self.drone_headings[drone_id] = heading # 寫入 -``` - -### 3. **資料同步屬性** - -| 屬性 | 寫入執行緒 | 讀取執行緒 | 保護機制 | -|------|----------|----------|--------| -| `_ui_update_cache` | spin_ros (UI) | _process_ui_updates (UI) | _ui_cache_lock | -| `monitor.latest_data` | 接收執行緒 | spin_ros (UI) | 直接讀取後清空 | -| `drone_positions` | _update_gps_ui (UI) | _update_hud_ui (UI) | 同一執行緒 | -| `drone_headings` | _update_hud_ui (UI) | _update_attitude_ui (UI) | 同一執行緒 | -| `self.drones[*]` | add_drone (UI) | 各 _update_*_ui (UI) | 同一執行緒 | - -## UI 更新流程 - -### 步驟 1: 資料收集 (spin_ros, 10ms) -```python -def spin_ros(self): - # 執行 ROS 收集資料 - self.executor.spin_once(timeout_sec=0.01) - - # 只快取資料,不更新 UI - for (drone_id, msg_type), data in self.monitor.latest_data.items(): - if drone_id not in self._ui_update_cache: - self._ui_update_cache[drone_id] = {} - self._ui_update_cache[drone_id][msg_type] = data - self.monitor.latest_data.clear() -``` - -### 步驟 2: 批次 UI 更新 (_process_ui_updates, 30Hz / 33ms) -```python -def _process_ui_updates(self): - # 檢查是否有資料且無鎖定 - if self._ui_cache_lock or not self._ui_update_cache: - return - - self._ui_cache_lock = True # 上鎖 - try: - # 批次處理各無人機的快取資料 - for drone_id in list(self._ui_update_cache.keys()): - data_dict = self._ui_update_cache.get(drone_id, {}) - - # 依訊息類型調用相應的 UI 更新方法 - if 'attitude' in data_dict: - self._update_attitude_ui(drone_id, data_dict['attitude']) - if 'hud' in data_dict: - self._update_hud_ui(drone_id, data_dict['hud']) - # ... 其他訊息類型 - - # 清空快取 - self._ui_update_cache.clear() - finally: - self._ui_cache_lock = False # 解鎖 -``` - -### 步驟 3: 各資料類型的 UI 更新 -```python -# 所有 _update_*_ui 方法都在 UI 執行緒上運行 -def _update_gps_ui(self, drone_id, data): - # 安全地寫入共享結構(同一執行緒) - self.drone_positions[drone_id] = (lat, lon) - - # 更新 UI 元素 - self.update_overview_table(drone_id, 'latitude', ...) - self.drone_map.update_drone_position(drone_id, lat, lon, heading) - -def _update_hud_ui(self, drone_id, data): - # 安全地寫入共享結構(同一執行緒) - self.drone_headings[drone_id] = heading - - # 讀取之前寫入的資料(同一執行緒,無競賽條件) - if drone_id in self.drone_positions: - lat, lon = self.drone_positions[drone_id] - self.drone_map.update_drone_position(drone_id, lat, lon, heading) -``` - -## 關鍵設計原則 - -### 1. **執行緒隔離** -- 所有 UI 操作都在主 GUI 執行緒上進行 -- ROS 資料收集隔離在單獨的定時器回調中 -- 背景執行緒只寫入快取,不觸及 UI - -### 2. **資料流向一致** -``` -背景執行緒 → monitor.latest_data - ↓ - 主 UI 執行緒 (spin_ros) - ↓ - _ui_update_cache - ↓ - 主 UI 執行緒 (_process_ui_updates) - ↓ - UI 元素 -``` - -### 3. **批次更新減少開銷** -- 不是每次收到訊息就更新 UI(造成主執行緒壓力) -- 而是批次收集 33ms 內的所有更新,一次性渲染 -- 結果:UI 流暢度提高,執行緒爭奪減少 - -### 4. **簡單的同步策略** -- 使用簡單的布林標誌而不是複雜的鎖 -- 避免死鎖,因為只有一個重要的臨界區 (`_ui_update_cache`) - -## 監控與驗證 - -### 態度指示器(ADI)更新頻率 -在 [drone_panel.py](drone_panel.py) 中的 `update_attitude()` 方法自動列印頻率: -``` -[drone_0] Attitude update frequency: 30.00 Hz -[drone_1] Attitude update frequency: 29.98 Hz -``` - -### 預期頻率 -- **資料收集**: 10ms = 100Hz(但不更新 UI) -- **UI 渲染**: 30Hz(批次模式) -- **ADI 更新**: 約 30Hz(受限於 `_process_ui_updates()` 頻率) - -## 故障排除 - -### 1. ADI 仍然更新頻率低 -- 檢查 `ui_update_timer` 是否啟動 -- 確認 `_process_ui_updates()` 正在被調用 -- 檢查是否有其他執行緒在嘗試直接更新 UI - -### 2. 地圖或表格更新時卡頓 -- 檢查 `drone_map.update_drone_position()` 和 `update_overview_table()` 是否有阻塞操作 -- 確認這些操作在 `_process_ui_updates()` 中被調用(主執行緒) - -### 3. 資料不同步 -- 驗證 `_ui_cache_lock` 是否正確保護快取訪問 -- 檢查是否有代碼在 spin_ros 之外寫入 `_ui_update_cache` - -## 測試建議 - -1. **頻率監控**: 執行應用程序並查看列印的 ADI 更新頻率 -2. **視覺檢查**: 觀察 ADI 指標的平滑性和應答性 -3. **壓力測試**: 同時連接多個無人機並監控 CPU 使用率 -4. **QDebug**: 在時間敏感的部分添加 `print()` 以測量執行時間 - ---- - -**更新日期**: 2025-01-20 -**版本**: 執行緒安全優化 v1.0 diff --git a/src/GUI/VERIFICATION_CHECKLIST.md b/src/GUI/VERIFICATION_CHECKLIST.md deleted file mode 100644 index 7c113c5..0000000 --- a/src/GUI/VERIFICATION_CHECKLIST.md +++ /dev/null @@ -1,171 +0,0 @@ -# 實現驗證清單 ✅ - -## 需求清單 - -- [x] **Panel 更新率設為 10Hz** - - 已在 `__init__` 初始化 `panel_map_timer` (100ms = 10Hz) - - 位置: [gui.py#L50-L52](gui.py#L50-L52) - -- [x] **Map 更新率設為 10Hz** - - 已在 `__init__` 初始化 `panel_map_timer` (100ms = 10Hz) - - 已移除直接更新地圖的舊代碼 - - 位置: [gui.py#L50-L52](gui.py#L50-L52) - -- [x] **消息未更新時讀取上一次的值** - - 已實現消息快取機制 (`_message_cache`) - - GPS 和 HUD 消息保留在快取中 - - 即使沒有新消息,舊值仍被使用 - - 位置: [gui.py#L56](gui.py#L56) - -## 代碼實現驗證 - -### 1. 初始化部分 ✅ -```python -# 初始化 panel 和 map 更新(10Hz) -self.panel_map_timer = QTimer() # ✓ 已添加 -self.panel_map_timer.timeout.connect(self._update_panel_and_map) # ✓ 已連接 -self.panel_map_timer.start(100) # 10Hz # ✓ 已設置 - -# 快取消息數據,以便在沒有新消息時使用上一次的值 -self._message_cache = {} # ✓ 已初始化 -``` - -### 2. 快取機制 ✅ -```python -# 快取 GPS 和 HUD 資料用於 panel/map 的 10Hz 更新 -if msg_type in ('gps', 'hud'): - if drone_id not in self._message_cache: - self._message_cache[drone_id] = {} - self._message_cache[drone_id][msg_type] = data - # 不在這裡更新,等待 _update_panel_and_map 在 10Hz 執行 - return -``` -- ✓ 檢查消息類型是否為 GPS 或 HUD -- ✓ 創建無人機快取字典 -- ✓ 保存消息數據 -- ✓ 返回而不直接更新 UI - -### 3. 10Hz 批次更新 ✅ -```python -def _update_panel_and_map(self): - """10Hz 定時更新 panel 和 map,使用快取的 GPS 和 HUD 消息""" - for drone_id in list(self._message_cache.keys()): - cache = self._message_cache[drone_id] - - # GPS 更新 - if 'gps' in cache: - gps_data = cache['gps'] - lat, lon = gps_data.get('lat', 0), gps_data.get('lon', 0) - self.drone_positions[drone_id] = (lat, lon) - # ... 更新表格 - - # HUD 更新 - if 'hud' in cache: - hud_data = cache['hud'] - heading = hud_data.get('heading', 0) - # ... 更新 panel 和 map - self.drone_map.update_drone_position(drone_id, lat, lon, heading) -``` -- ✓ 遍歷快取中的所有無人機 -- ✓ 檢查 GPS 消息並更新 -- ✓ 檢查 HUD 消息並更新 -- ✓ 使用快取值(即使未更新) - -### 4. 移除舊的直接更新 ✅ -- ✓ 移除了 `msg_type == 'gps'` 的舊代碼 -- ✓ 移除了 `msg_type == 'hud'` 的舊代碼 -- ✓ GPS 和 HUD 更新現在只通過 `_update_panel_and_map()` 進行 - -## 文件驗證 - -### gui.py -- [x] 語法檢查通過 ✅ -- [x] 無編譯錯誤 ✅ -- [x] 所有方法定義完整 ✅ -- [x] 所有引用方法存在 ✅ - -### 文檔 -- [x] PANEL_MAP_UPDATE.md - 10Hz 更新機制詳細說明 -- [x] IMPLEMENTATION_SUMMARY.md - 實現完成總結 -- [x] BEFORE_AFTER_COMPARISON.md - 架構對比 - -## 運行時驗證清單 - -當應用啟動時,檢查: - -### 應該看到的行為 - -1. **Panel 和 Map 更新平滑** - - 無人機位置在地圖上平滑移動(10Hz) - - Panel 顯示的標題、速度等流暢更新 - - 無視覺閃爍或抖動 - -2. **快取工作正常** - - 即使停止 GPS 消息,地圖上的位置仍保持最後已知位置 - - Panel 顯示的值保留最後已知值 - - 當消息恢復時,立即反映新值 - -3. **其他 UI 保持快速** - - State(ARMED/DISARMED)即時更新 - - Battery 電壓即時更新 - - Loss Rate 即時更新 - - Ping 即時更新 - -4. **無性能問題** - - CPU 使用率合理(相比之前應該更低) - - 無內存洩漏 - - GUI 響應靈敏 - -### 故障排除 - -| 症狀 | 原因 | 檢查項目 | -|------|------|--------| -| Panel/Map 不更新 | 定時器未啟動 | `self.panel_map_timer.isActive()` | -| 快取總是空 | GPS/HUD 消息未被快取 | 檢查 `update_ui()` 是否被調用 | -| 高 CPU 使用 | `update_drone_position()` 性能問題 | 檢查 Map 繪製邏輯 | -| 數據延遲 | 正常現象(0-100ms) | 這是預期行為 | - -## 性能預期 - -### 資源使用 ✅ - -| 指標 | 舊值 | 新值 | 改進 | -|------|------|------|------| -| Map 更新頻率 | ~100Hz+ | 10Hz | ↓ 90%+ | -| Panel 更新頻率 | ~100Hz+ | 10Hz | ↓ 90%+ | -| CPU 用於渲染 | 高 | 低-中 | ✓ 顯著 | -| 內存快取 | 無 | ~10KB | 可接受 | - -### 延遲 ✅ - -| 操作 | 延遲 | 說明 | -|------|------|------| -| GPS 消息 → 地圖更新 | 0-100ms | 可接受 | -| HUD 消息 → Panel 更新 | 0-100ms | 可接受 | -| 其他消息 → UI 更新 | 0-10ms | 保持快速 | - -## 最終確認 - -- [x] 需求已實現 -- [x] 代碼語法正確 -- [x] 文檔完整 -- [x] 無編譯錯誤 -- [x] 邏輯驗證通過 -- [x] 性能預期達成 - -## 部署檢查表 - -在部署到生產環境前: - -- [ ] 在測試環境驗證 GUI 啟動無誤 -- [ ] 驗證與多架無人機的連接 -- [ ] 檢查地圖在移動時的流暢性 -- [ ] 驗證 Panel 數據顯示正確 -- [ ] 監控 CPU 和內存使用 -- [ ] 檢查是否有任何控制台錯誤或警告 - ---- - -**驗證日期**: 2025-03-25 -**驗證狀態**: ✅ 所有項目通過 -**準備狀態**: ✅ 準備就緒