2 Commits
Author SHA1 Message Date
ken910606 fc2c642d6c Force sync local master to remote ken 2026-04-14 14:37:56 +08:00
ken910606 2c38376f9c Update master with local 2.0.5 2026-04-14 14:10:57 +08:00
3 changed files with 131 additions and 21 deletions
+14 -20
View File
@@ -602,7 +602,7 @@ class DroneMonitor(Node):
# ================================================================================ # ================================================================================
async def set_mode(self, drone_id, mode_name): async def set_mode(self, drone_id, mode_name):
"""使用 CommandLongClient 切換無人機飛行模式""" """使用 CommandLongClient 切換無人機飛行模式(使用非阻塞的 async 方法)"""
try: try:
# 解析 drone_id 提取 sysid # 解析 drone_id 提取 sysid
parts = drone_id.split('_') parts = drone_id.split('_')
@@ -619,30 +619,24 @@ class DroneMonitor(Node):
print(f"\n📢 [SET_MODE] {drone_id} → {mode_name} (custom_mode={custom_mode})") print(f"\n📢 [SET_MODE] {drone_id} → {mode_name} (custom_mode={custom_mode})")
# 在線程池中調用 CommandLongClient if not self.command_long_client:
loop = asyncio.get_event_loop() print(f"❌ [SET_MODE] CommandLongClient 未初始化")
executor = ThreadPoolExecutor(max_workers=1) return False
def _call_set_mode(): # 直接調用 async 方法,無需線程池(避免嵌套執行器衝突)
client = CommandLongClient() if CommandLongClient else None result = await self.command_long_client.change_mode_async(
if not client: target_sysid=sysid,
return False custom_mode=float(custom_mode),
result = client.change_mode( target_compid=0,
target_sysid=sysid, base_mode=1.0,
custom_mode=float(custom_mode), timeout_sec=2.0,
target_compid=0, )
base_mode=1.0,
timeout_sec=2.0,
)
client.destroy_node()
return result.success if result else False
result = await loop.run_in_executor(executor, _call_set_mode) if result and result.success:
if result:
print(f"✅ [SET_MODE] 模式切換成功") print(f"✅ [SET_MODE] 模式切換成功")
return True return True
else: else:
print(f"❌ [SET_MODE] 模式切換失敗") print(f"❌ [SET_MODE] 模式切換失敗 (message={result.message if result else 'None'})")
return False return False
except Exception as e: except Exception as e:
print(f"❌ [SET_MODE] 例外錯誤: {e}") print(f"❌ [SET_MODE] 例外錯誤: {e}")
+1 -1
View File
@@ -33,7 +33,7 @@ from mission_group import (
# ================================================================================ # ================================================================================
class ControlStationUI(QMainWindow): class ControlStationUI(QMainWindow):
VERSION = '2.0.4' VERSION = '2.0.6'
def __init__(self): def __init__(self):
super().__init__() super().__init__()
+116
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional from typing import Optional
import asyncio
import rclpy import rclpy
from rclpy.node import Node from rclpy.node import Node
@@ -185,3 +186,118 @@ class CommandLongClient(Node):
param7=0.0, param7=0.0,
timeout_sec=timeout_sec, timeout_sec=timeout_sec,
) )
# ============================================================================
# 【新增】非阻塞 async 包裝方法(用於 GUI 的非阻塞調用)
# 這些方法在 ThreadPoolExecutor 中運行同步版本,以避免阻塞事件循環
# ============================================================================
async def change_mode_async(
self,
*,
target_sysid: int,
custom_mode: float,
target_compid: int = 0,
base_mode: float = 1.0,
confirmation: int = 0,
timeout_sec: float = DEFAULT_TIMEOUT_SEC,
) -> CommandLongResult:
"""非阻塞 async 版本的 change_mode(在 ThreadPoolExecutor 中運行)"""
from concurrent.futures import ThreadPoolExecutor
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=1)
return await loop.run_in_executor(
executor,
lambda: self.change_mode(
target_sysid=target_sysid,
custom_mode=custom_mode,
target_compid=target_compid,
base_mode=base_mode,
confirmation=confirmation,
timeout_sec=timeout_sec,
)
)
async def arm_disarm_async(
self,
*,
target_sysid: int,
arm: bool,
target_compid: int = 0,
confirmation: int = 0,
param2: float = 0.0,
timeout_sec: float = DEFAULT_TIMEOUT_SEC,
) -> CommandLongResult:
"""非阻塞 async 版本的 arm_disarm(在 ThreadPoolExecutor 中運行)"""
from concurrent.futures import ThreadPoolExecutor
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=1)
return await loop.run_in_executor(
executor,
lambda: self.arm_disarm(
target_sysid=target_sysid,
arm=arm,
target_compid=target_compid,
confirmation=confirmation,
param2=param2,
timeout_sec=timeout_sec,
)
)
async def takeoff_async(
self,
*,
target_sysid: int,
altitude_m: float,
target_compid: int = 0,
min_pitch_deg: float = 0.0,
yaw_deg: float = 0.0,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
timeout_sec: float = DEFAULT_TIMEOUT_SEC,
) -> CommandLongResult:
"""非阻塞 async 版本的 takeoff(在 ThreadPoolExecutor 中運行)"""
from concurrent.futures import ThreadPoolExecutor
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=1)
return await loop.run_in_executor(
executor,
lambda: self.takeoff(
target_sysid=target_sysid,
altitude_m=altitude_m,
target_compid=target_compid,
min_pitch_deg=min_pitch_deg,
yaw_deg=yaw_deg,
latitude=latitude,
longitude=longitude,
timeout_sec=timeout_sec,
)
)
async def land_async(
self,
*,
target_sysid: int,
target_compid: int = 0,
yaw_deg: float = 0.0,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
altitude_m: float = 0.0,
timeout_sec: float = DEFAULT_TIMEOUT_SEC,
) -> CommandLongResult:
"""非阻塞 async 版本的 land(在 ThreadPoolExecutor 中運行)"""
from concurrent.futures import ThreadPoolExecutor
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=1)
return await loop.run_in_executor(
executor,
lambda: self.land(
target_sysid=target_sysid,
target_compid=target_compid,
yaw_deg=yaw_deg,
latitude=latitude,
longitude=longitude,
altitude_m=altitude_m,
timeout_sec=timeout_sec,
)
)