4 Commits
Author SHA1 Message Date
wenchunandClaude Opus 4.6 3483864a2e chore: 移除無關的模擬資料檔與 README 過時段落
刪除歷史 merge 夾帶進來的 obstacles_data.mat 與 uav_simulation_data.mat
(屬於另一個研究的資料,與本專案無關),並移除 README 末尾 0328 的
test_transform / close_loop 敘述。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 15:15:15 +08:00
wenchunandClaude Opus 4.6 ae80820419 chore: gitignore notes/ 資料夾以收納本地設計筆記
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 15:15:10 +08:00
wenchun 32e1ce8fb2 Merge remote-tracking branch 'origin/master' into wenchun 2026-04-14 13:54:08 +08:00
wenchun 33d6d59139 chore: ignore CLAUDE.md, 本地筆記與 eeprom dump 2026-04-14 13:53:40 +08:00
7 changed files with 27 additions and 136 deletions
+6
View File
@@ -26,3 +26,9 @@ Makefile
**/*.pyc
**/*.pyo
**/.cursor/
CLAUDE.md
# 本地筆記與硬體 dump
my_env_notes.md
notes/
**/eeprom.bin
-5
View File
@@ -97,8 +97,3 @@ python gui.py
N. logs 是執行時期的記錄檔
===
簡述0328
主要是透過test_transform這隻程式執行fdm_switch_example_one_with_remote_forwarding這個副函式,透過UDP將封包轉接到Matlab,並在Matlab進行演算法迭代後,打包JSON檔,並再次透過port口傳回封包,傳回來的封包會在close_loop這隻程式被解析,並提取其中有關velocity的資料儲存成變數,以mavlink的方式打入Ardupilot,以實現封閉迴路的構思。
Binary file not shown.
+20 -14
View File
@@ -602,7 +602,7 @@ class DroneMonitor(Node):
# ================================================================================
async def set_mode(self, drone_id, mode_name):
"""使用 CommandLongClient 切換無人機飛行模式(使用非阻塞的 async 方法)"""
"""使用 CommandLongClient 切換無人機飛行模式"""
try:
# 解析 drone_id 提取 sysid
parts = drone_id.split('_')
@@ -619,24 +619,30 @@ class DroneMonitor(Node):
print(f"\n📢 [SET_MODE] {drone_id} → {mode_name} (custom_mode={custom_mode})")
if not self.command_long_client:
print(f"❌ [SET_MODE] CommandLongClient 未初始化")
return False
# 在線程池中調用 CommandLongClient
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=1)
# 直接調用 async 方法,無需線程池(避免嵌套執行器衝突)
result = await self.command_long_client.change_mode_async(
target_sysid=sysid,
custom_mode=float(custom_mode),
target_compid=0,
base_mode=1.0,
timeout_sec=2.0,
)
def _call_set_mode():
client = CommandLongClient() if CommandLongClient else None
if not client:
return False
result = client.change_mode(
target_sysid=sysid,
custom_mode=float(custom_mode),
target_compid=0,
base_mode=1.0,
timeout_sec=2.0,
)
client.destroy_node()
return result.success if result else False
if result and result.success:
result = await loop.run_in_executor(executor, _call_set_mode)
if result:
print(f"✅ [SET_MODE] 模式切換成功")
return True
else:
print(f"❌ [SET_MODE] 模式切換失敗 (message={result.message if result else 'None'})")
print(f"❌ [SET_MODE] 模式切換失敗")
return False
except Exception as e:
print(f"❌ [SET_MODE] 例外錯誤: {e}")
+1 -1
View File
@@ -33,7 +33,7 @@ from mission_group import (
# ================================================================================
class ControlStationUI(QMainWindow):
VERSION = '2.0.6'
VERSION = '2.0.4'
def __init__(self):
super().__init__()
-116
View File
@@ -4,7 +4,6 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
import asyncio
import rclpy
from rclpy.node import Node
@@ -186,118 +185,3 @@ class CommandLongClient(Node):
param7=0.0,
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,
)
)
Binary file not shown.