diff --git a/src/GUI/communication.py b/src/GUI/communication.py index b6c2e88..83d65f3 100644 --- a/src/GUI/communication.py +++ b/src/GUI/communication.py @@ -1163,7 +1163,8 @@ class DroneMonitor(Node): _log("INFO", f"[SET_MODE] {drone_id} -> {mode_name} (custom_mode={custom_mode})") - # 獲取或創建該 drone 專用的 client(避免多機並行時的競態條件) + # 取得共用 command_long client(OOM 修復後已非 per-drone,靠 request 的 + # target_sysid 路由;drone_id 僅用於一次性 lazy fallback,無 per-drone 狀態) client = self.get_or_create_client(drone_id) if not client: _log("ERROR", "[SET_MODE] CommandLongClient 無法初始化") @@ -1246,7 +1247,8 @@ class DroneMonitor(Node): action_name = "解鎖" if arm else "上鎖" _log("INFO", f"[ARM] {drone_id} -> {action_name}") - # 獲取或創建該 drone 專用的 client(避免多機並行時的競態條件) + # 取得共用 command_long client(OOM 修復後已非 per-drone,靠 request 的 + # target_sysid 路由;drone_id 僅用於一次性 lazy fallback,無 per-drone 狀態) client = self.get_or_create_client(drone_id) if not client: _log("ERROR", "[ARM] CommandLongClient 無法初始化") @@ -1283,7 +1285,8 @@ class DroneMonitor(Node): _log("INFO", f"[TAKEOFF] {drone_id} -> 起飛 (高度={altitude}m)") - # 獲取或創建該 drone 專用的 client(避免多機並行時的競態條件) + # 取得共用 command_long client(OOM 修復後已非 per-drone,靠 request 的 + # target_sysid 路由;drone_id 僅用於一次性 lazy fallback,無 per-drone 狀態) client = self.get_or_create_client(drone_id) if not client: _log("ERROR", "[TAKEOFF] CommandLongClient 無法初始化") diff --git a/src/GUI/gui.py b/src/GUI/gui.py index 17d177d..1a5896e 100644 --- a/src/GUI/gui.py +++ b/src/GUI/gui.py @@ -1573,27 +1573,30 @@ class ControlStationUI(QMainWindow): _log("INFO", f"Group {group_id} 準備切換模式為 {mode}") self.statusBar().showMessage("正在切換模式", 1000) - # 使用 asyncio 執行(通過事件循環) + # 使用 asyncio 執行(通過事件循環)。 + # 並發:每台各自 set_mode + 各自等自己的 ACK,用 gather 同時發, + # 避免逐台 await 造成序列化(最壞情況 N×timeout 一路卡著)。 + async def _change_one(drone_id): + try: + result = await self.monitor.set_mode(drone_id, mode) + if result: + msg = f"{drone_id} 切換成功" + _log("INFO", msg) + else: + msg = f"{drone_id} 切換失敗" + _log("WARN", msg) + self.message_queue.put((msg, 2000)) + except Exception as e: + msg = f"{drone_id} 錯誤: {str(e)}" + _log("ERROR", msg) + traceback.print_exc() + self.message_queue.put((msg, 2000)) + async def do_mode_changes_async(): - for drone_id in group.selected_drone_ids: - try: - result = await self.monitor.set_mode(drone_id, mode) - - if result: - msg = f"{drone_id} 切換成功" - _log("INFO", msg) - self.message_queue.put((msg, 2000)) - else: - msg = f"{drone_id} 切換失敗" - _log("WARN", msg) - self.message_queue.put((msg, 2000)) - - except Exception as e: - msg = f"{drone_id} 錯誤: {str(e)}" - _log("ERROR", msg) - traceback.print_exc() - self.message_queue.put((msg, 2000)) - + await asyncio.gather( + *(_change_one(did) for did in group.selected_drone_ids) + ) + # 通過事件循環提交異步任務 loop = asyncio.get_event_loop() asyncio.run_coroutine_threadsafe( @@ -2584,17 +2587,18 @@ class ControlStationUI(QMainWindow): if hasattr(self, 'orchestrator'): self.orchestrator.friendly_speed = float(friendly_speed) + async def _apply_one(did, speed, label): + try: + await self.monitor.set_speed(did, speed) + except Exception as e: + _log("ERROR", f"[套用速度] {label} {did} 失敗: {e}") + async def do_apply(): - for did in friendly_ids: - try: - await self.monitor.set_speed(did, friendly_speed) - except Exception as e: - _log("ERROR", f"[套用速度] 友機 {did} 失敗: {e}") + # 並發:友機各自 + 敵機,用 gather 同時發,各自等各自 ACK + tasks = [_apply_one(did, friendly_speed, "友機") for did in friendly_ids] if enemy_id: - try: - await self.monitor.set_speed(enemy_id, enemy_speed) - except Exception as e: - _log("ERROR", f"[套用速度] 敵機 {enemy_id} 失敗: {e}") + tasks.append(_apply_one(enemy_id, enemy_speed, "敵機")) + await asyncio.gather(*tasks) asyncio.run_coroutine_threadsafe(do_apply(), asyncio.get_event_loop()) self.statusBar().showMessage(