|
|
|
@ -794,20 +794,19 @@ class DroneMonitor(Node):
|
|
|
|
self.serial_receivers = []
|
|
|
|
self.serial_receivers = []
|
|
|
|
|
|
|
|
|
|
|
|
# ================================================================================
|
|
|
|
# ================================================================================
|
|
|
|
# 【新增】初始化 CommandLongClient 字典(為每個 drone 維護獨立的 client)
|
|
|
|
# 共用 command / position client(各一個節點,靠 request 的 target_sysid 路由)
|
|
|
|
# ================================================================================
|
|
|
|
# ================================================================================
|
|
|
|
# 改為為每個 drone 創建獨立的 client,避免多機並行時的競態條件
|
|
|
|
# 兩個 client 都只打到單一 service(send_command_long / pos_global_int),
|
|
|
|
self.command_long_clients = {} # {drone_id: CommandLongClient}
|
|
|
|
# 沒有任何 per-drone 狀態,故不需要 per-drone 節點。改成「啟動時各預建一個、
|
|
|
|
self.client_lock = Lock() # 保護 clients 字典的訪問
|
|
|
|
# 執行期只重用」,避免:
|
|
|
|
self.client_counter = 0 # 用於生成唯一的 client 節點名稱
|
|
|
|
# (1) 切 mode / 首次 goto 時於執行期新建 ROS2 node = 新 DDS participant,
|
|
|
|
self.executor = None # 將在 gui.py 中設置,用於添加新的 clients
|
|
|
|
# 其 discovery handshake 撞到壞掉的外來 participant → FastRTPS bad_alloc → OOM。
|
|
|
|
# ================================================================================
|
|
|
|
# (2) 從 asyncio/Qt 執行緒對「正在被 _ros_spin_thread spin 的 executor」
|
|
|
|
|
|
|
|
# 跨執行緒 add_node → wait set 損毀 → CPU 空轉 100%。
|
|
|
|
# ================================================================================
|
|
|
|
self.command_long_client = None # CommandLongClient(共用)
|
|
|
|
# PositionTargetGlobalIntClient 字典(per-drone,用於 Offboard goto)
|
|
|
|
self.position_target_client = None # PositionTargetGlobalIntClient(共用)
|
|
|
|
# ================================================================================
|
|
|
|
self.client_lock = Lock() # 保護共用 client 的建立
|
|
|
|
self.position_target_clients = {} # {drone_id: PositionTargetGlobalIntClient}
|
|
|
|
self.executor = None # 將在 gui.py 中設置,用於 add_node
|
|
|
|
self.pos_client_counter = 0
|
|
|
|
|
|
|
|
# ================================================================================
|
|
|
|
# ================================================================================
|
|
|
|
|
|
|
|
|
|
|
|
# 主题检测定时器
|
|
|
|
# 主题检测定时器
|
|
|
|
@ -876,57 +875,69 @@ class DroneMonitor(Node):
|
|
|
|
|
|
|
|
|
|
|
|
return self.socket_id_mapping[original_socket_id]
|
|
|
|
return self.socket_id_mapping[original_socket_id]
|
|
|
|
|
|
|
|
|
|
|
|
def get_or_create_client(self, drone_id):
|
|
|
|
def init_shared_command_clients(self):
|
|
|
|
"""為每個 drone 獲取或創建獨立的 CommandLongClient,避免競態條件"""
|
|
|
|
"""啟動時預建共用的 command / position client(各一個節點)。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
必須在 _ros_spin_thread 啟動「之前」於主執行緒呼叫:此時 executor 已建立
|
|
|
|
|
|
|
|
但尚未被 spin,add_node 不會與 spin_once 跨執行緒競爭。之後執行期送指令
|
|
|
|
|
|
|
|
一律重用這兩個節點,不再於飛行中新建 DDS participant。"""
|
|
|
|
with self.client_lock:
|
|
|
|
with self.client_lock:
|
|
|
|
if drone_id not in self.command_long_clients:
|
|
|
|
if self.command_long_client is None and CommandLongClient is not None:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
# 生成唯一的 client 節點名稱
|
|
|
|
self.command_long_client = CommandLongClient(node_name="cmd_long_client_shared")
|
|
|
|
self.client_counter += 1
|
|
|
|
|
|
|
|
unique_name = f"cmd_long_client_{drone_id}_{self.client_counter}"
|
|
|
|
|
|
|
|
client = CommandLongClient(node_name=unique_name)
|
|
|
|
|
|
|
|
self.command_long_clients[drone_id] = client
|
|
|
|
|
|
|
|
_log("INFO", f"已為 {drone_id} 建立 CommandLongClient (node={unique_name})")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 將新 client 添加到主執行器(這樣它的回調才能被處理)
|
|
|
|
|
|
|
|
if self.executor:
|
|
|
|
if self.executor:
|
|
|
|
self.executor.add_node(client)
|
|
|
|
self.executor.add_node(self.command_long_client)
|
|
|
|
_log("INFO", f"已將 {drone_id} 的 CommandLongClient 加入主執行器")
|
|
|
|
_log("INFO", "已預建共用 CommandLongClient (node=cmd_long_client_shared)")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
_log("WARN", f"預建 CommandLongClient 失敗: {e}")
|
|
|
|
|
|
|
|
if self.position_target_client is None and PositionTargetGlobalIntClient is not None:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
self.position_target_client = PositionTargetGlobalIntClient(node_name="pos_target_client_shared")
|
|
|
|
|
|
|
|
if self.executor:
|
|
|
|
|
|
|
|
self.executor.add_node(self.position_target_client)
|
|
|
|
|
|
|
|
_log("INFO", "已預建共用 PositionTargetGlobalIntClient (node=pos_target_client_shared)")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
_log("WARN", f"預建 PositionTargetGlobalIntClient 失敗: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
except TypeError:
|
|
|
|
def get_or_create_client(self, drone_id=None):
|
|
|
|
# 舊版 CommandLongClient 不支持 node_name 參數,使用預設
|
|
|
|
"""回傳共用的 CommandLongClient(單一節點,靠 request 的 target_sysid 路由)。
|
|
|
|
client = CommandLongClient()
|
|
|
|
|
|
|
|
self.command_long_clients[drone_id] = client
|
|
|
|
|
|
|
|
_log("INFO", f"已為 {drone_id} 建立 CommandLongClient (使用預設名稱)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
正常情況已於啟動時由 init_shared_command_clients() 預建;此處僅保留一次性
|
|
|
|
|
|
|
|
lazy fallback(例如 init 尚未被呼叫),不會在執行期重複新建 participant。
|
|
|
|
|
|
|
|
drone_id 參數保留以相容既有呼叫端,實際不影響路由(路由在 request 內)。"""
|
|
|
|
|
|
|
|
if self.command_long_client is not None:
|
|
|
|
|
|
|
|
return self.command_long_client
|
|
|
|
|
|
|
|
with self.client_lock:
|
|
|
|
|
|
|
|
if self.command_long_client is None and CommandLongClient is not None:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
self.command_long_client = CommandLongClient(node_name="cmd_long_client_shared")
|
|
|
|
if self.executor:
|
|
|
|
if self.executor:
|
|
|
|
self.executor.add_node(client)
|
|
|
|
self.executor.add_node(self.command_long_client)
|
|
|
|
_log("INFO", f"已將 {drone_id} 的 CommandLongClient 加入主執行器")
|
|
|
|
_log("INFO", "已建立共用 CommandLongClient (lazy fallback)")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
except Exception as e:
|
|
|
|
_log("WARN", f"無法為 {drone_id} 建立 CommandLongClient: {e}")
|
|
|
|
_log("WARN", f"無法建立共用 CommandLongClient: {e}")
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
return self.command_long_clients[drone_id]
|
|
|
|
return self.command_long_client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_or_create_position_client(self, drone_id=None):
|
|
|
|
|
|
|
|
"""回傳共用的 PositionTargetGlobalIntClient(單一節點,靠 target_sysid 路由)。
|
|
|
|
|
|
|
|
|
|
|
|
def get_or_create_position_client(self, drone_id):
|
|
|
|
同 get_or_create_client:正常已於啟動預建,此處僅一次性 lazy fallback。"""
|
|
|
|
"""為每個 drone 獲取或創建獨立的 PositionTargetGlobalIntClient。"""
|
|
|
|
if self.position_target_client is not None:
|
|
|
|
|
|
|
|
return self.position_target_client
|
|
|
|
if PositionTargetGlobalIntClient is None:
|
|
|
|
if PositionTargetGlobalIntClient is None:
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
with self.client_lock:
|
|
|
|
with self.client_lock:
|
|
|
|
if drone_id not in self.position_target_clients:
|
|
|
|
if self.position_target_client is None:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
self.pos_client_counter += 1
|
|
|
|
self.position_target_client = PositionTargetGlobalIntClient(node_name="pos_target_client_shared")
|
|
|
|
unique_name = f"pos_target_client_{drone_id}_{self.pos_client_counter}"
|
|
|
|
|
|
|
|
client = PositionTargetGlobalIntClient(node_name=unique_name)
|
|
|
|
|
|
|
|
self.position_target_clients[drone_id] = client
|
|
|
|
|
|
|
|
_log("INFO", f"已為 {drone_id} 建立 PositionTargetGlobalIntClient (node={unique_name})")
|
|
|
|
|
|
|
|
if self.executor:
|
|
|
|
if self.executor:
|
|
|
|
self.executor.add_node(client)
|
|
|
|
self.executor.add_node(self.position_target_client)
|
|
|
|
_log("INFO", f"已將 {drone_id} 的 PositionTargetGlobalIntClient 加入主執行器")
|
|
|
|
_log("INFO", "已建立共用 PositionTargetGlobalIntClient (lazy fallback)")
|
|
|
|
except Exception as e:
|
|
|
|
except Exception as e:
|
|
|
|
_log("WARN", f"無法為 {drone_id} 建立 PositionTargetGlobalIntClient: {e}")
|
|
|
|
_log("WARN", f"無法建立共用 PositionTargetGlobalIntClient: {e}")
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
return self.position_target_clients[drone_id]
|
|
|
|
return self.position_target_client
|
|
|
|
|
|
|
|
|
|
|
|
def scan_topics(self):
|
|
|
|
def scan_topics(self):
|
|
|
|
topics = self.get_topic_names_and_types()
|
|
|
|
topics = self.get_topic_names_and_types()
|
|
|
|
|