refactor(GUI): 群組切mode/套用速度改並發(asyncio.gather),清per-drone client過期註解

- gui.py: _handle_group_mode_change 與 _on_engagement_apply_speed 由
  單一 async 函式內 for 迴圈逐台 await(序列化,最壞 N×timeout)改為
  asyncio.gather 並發,各自等各自 ACK;行為語意不變。
- communication.py: set_mode/arm_drone/set_speed 前 3 處過期註解
  「避免多機並行時的競態條件」更正為共用 client + target_sysid 路由
  (OOM 修復後已非 per-drone、無 per-drone 狀態)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wenchun
wenchun 1 month ago
parent 6ef7d3a7a6
commit 1db3445e86

@ -1163,7 +1163,8 @@ class DroneMonitor(Node):
_log("INFO", f"[SET_MODE] {drone_id} -> {mode_name} (custom_mode={custom_mode})") _log("INFO", f"[SET_MODE] {drone_id} -> {mode_name} (custom_mode={custom_mode})")
# 獲取或創建該 drone 專用的 client避免多機並行時的競態條件 # 取得共用 command_long clientOOM 修復後已非 per-drone靠 request 的
# target_sysid 路由drone_id 僅用於一次性 lazy fallback無 per-drone 狀態)
client = self.get_or_create_client(drone_id) client = self.get_or_create_client(drone_id)
if not client: if not client:
_log("ERROR", "[SET_MODE] CommandLongClient 無法初始化") _log("ERROR", "[SET_MODE] CommandLongClient 無法初始化")
@ -1246,7 +1247,8 @@ class DroneMonitor(Node):
action_name = "解鎖" if arm else "上鎖" action_name = "解鎖" if arm else "上鎖"
_log("INFO", f"[ARM] {drone_id} -> {action_name}") _log("INFO", f"[ARM] {drone_id} -> {action_name}")
# 獲取或創建該 drone 專用的 client避免多機並行時的競態條件 # 取得共用 command_long clientOOM 修復後已非 per-drone靠 request 的
# target_sysid 路由drone_id 僅用於一次性 lazy fallback無 per-drone 狀態)
client = self.get_or_create_client(drone_id) client = self.get_or_create_client(drone_id)
if not client: if not client:
_log("ERROR", "[ARM] CommandLongClient 無法初始化") _log("ERROR", "[ARM] CommandLongClient 無法初始化")
@ -1283,7 +1285,8 @@ class DroneMonitor(Node):
_log("INFO", f"[TAKEOFF] {drone_id} -> 起飛 (高度={altitude}m)") _log("INFO", f"[TAKEOFF] {drone_id} -> 起飛 (高度={altitude}m)")
# 獲取或創建該 drone 專用的 client避免多機並行時的競態條件 # 取得共用 command_long clientOOM 修復後已非 per-drone靠 request 的
# target_sysid 路由drone_id 僅用於一次性 lazy fallback無 per-drone 狀態)
client = self.get_or_create_client(drone_id) client = self.get_or_create_client(drone_id)
if not client: if not client:
_log("ERROR", "[TAKEOFF] CommandLongClient 無法初始化") _log("ERROR", "[TAKEOFF] CommandLongClient 無法初始化")

@ -1573,26 +1573,29 @@ class ControlStationUI(QMainWindow):
_log("INFO", f"Group {group_id} 準備切換模式為 {mode}") _log("INFO", f"Group {group_id} 準備切換模式為 {mode}")
self.statusBar().showMessage("正在切換模式", 1000) 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(): async def do_mode_changes_async():
for drone_id in group.selected_drone_ids: await asyncio.gather(
try: *(_change_one(did) for did in group.selected_drone_ids)
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))
# 通過事件循環提交異步任務 # 通過事件循環提交異步任務
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -2584,17 +2587,18 @@ class ControlStationUI(QMainWindow):
if hasattr(self, 'orchestrator'): if hasattr(self, 'orchestrator'):
self.orchestrator.friendly_speed = float(friendly_speed) 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(): async def do_apply():
for did in friendly_ids: # 並發:友機各自 + 敵機,用 gather 同時發,各自等各自 ACK
try: tasks = [_apply_one(did, friendly_speed, "友機") for did in friendly_ids]
await self.monitor.set_speed(did, friendly_speed)
except Exception as e:
_log("ERROR", f"[套用速度] 友機 {did} 失敗: {e}")
if enemy_id: if enemy_id:
try: tasks.append(_apply_one(enemy_id, enemy_speed, "敵機"))
await self.monitor.set_speed(enemy_id, enemy_speed) await asyncio.gather(*tasks)
except Exception as e:
_log("ERROR", f"[套用速度] 敵機 {enemy_id} 失敗: {e}")
asyncio.run_coroutine_threadsafe(do_apply(), asyncio.get_event_loop()) asyncio.run_coroutine_threadsafe(do_apply(), asyncio.get_event_loop())
self.statusBar().showMessage( self.statusBar().showMessage(

Loading…
Cancel
Save