|
|
|
@ -33,7 +33,7 @@ from mission_group import (
|
|
|
|
# ================================================================================
|
|
|
|
# ================================================================================
|
|
|
|
|
|
|
|
|
|
|
|
class ControlStationUI(QMainWindow):
|
|
|
|
class ControlStationUI(QMainWindow):
|
|
|
|
VERSION = '2.0.6'
|
|
|
|
VERSION = '2.0.7'
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
def __init__(self):
|
|
|
|
super().__init__()
|
|
|
|
super().__init__()
|
|
|
|
@ -450,11 +450,17 @@ class ControlStationUI(QMainWindow):
|
|
|
|
loop.create_task(self.handle_service_response(future, f"起飛 {drone_id}"))
|
|
|
|
loop.create_task(self.handle_service_response(future, f"起飛 {drone_id}"))
|
|
|
|
|
|
|
|
|
|
|
|
def handle_setpoint_selected(self):
|
|
|
|
def handle_setpoint_selected(self):
|
|
|
|
|
|
|
|
"""發送位置命令到 active group 的所有選中無人機"""
|
|
|
|
|
|
|
|
group = self._get_active_group()
|
|
|
|
|
|
|
|
if not group:
|
|
|
|
|
|
|
|
self.statusBar().showMessage("⚠ 請先建立任務群組", 3000)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
x = float(self.x_input.text() or '0')
|
|
|
|
x = float(self.x_input.text() or '0')
|
|
|
|
y = float(self.y_input.text() or '0')
|
|
|
|
y = float(self.y_input.text() or '0')
|
|
|
|
z = float(self.z_input.text() or '0')
|
|
|
|
z = float(self.z_input.text() or '0')
|
|
|
|
for drone_id in self.monitor.selected_drones:
|
|
|
|
for drone_id in group.selected_drone_ids:
|
|
|
|
if self.monitor.send_setpoint(drone_id, x, y, z):
|
|
|
|
if self.monitor.send_setpoint(drone_id, x, y, z):
|
|
|
|
self.statusBar().showMessage(f"發送位置命令到 {drone_id}: ({x}, {y}, {z})", 3000)
|
|
|
|
self.statusBar().showMessage(f"發送位置命令到 {drone_id}: ({x}, {y}, {z})", 3000)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
@ -507,8 +513,18 @@ class ControlStationUI(QMainWindow):
|
|
|
|
self.statusBar().showMessage(f"{action} 錯誤: {str(e)}", 3000)
|
|
|
|
self.statusBar().showMessage(f"{action} 錯誤: {str(e)}", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
def handle_arm_selected(self):
|
|
|
|
def handle_arm_selected(self):
|
|
|
|
|
|
|
|
"""解鎖 active group 的所有選中無人機"""
|
|
|
|
|
|
|
|
group = self._get_active_group()
|
|
|
|
|
|
|
|
if not group:
|
|
|
|
|
|
|
|
self.statusBar().showMessage("⚠ 請先建立任務群組", 3000)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
selected = list(group.selected_drone_ids)
|
|
|
|
|
|
|
|
if not selected:
|
|
|
|
|
|
|
|
self.statusBar().showMessage("⚠ 尚未選擇任何無人機", 3000)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
print(f"\n📢 [GUI] handle_arm_selected 被調用")
|
|
|
|
print(f"\n📢 [GUI] handle_arm_selected 被調用")
|
|
|
|
selected = list(self.monitor.selected_drones)
|
|
|
|
|
|
|
|
print(f" 已選擇的無人機: {selected}")
|
|
|
|
print(f" 已選擇的無人機: {selected}")
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
for drone_id in selected:
|
|
|
|
for drone_id in selected:
|
|
|
|
@ -523,8 +539,19 @@ class ControlStationUI(QMainWindow):
|
|
|
|
print(f" handle_arm_selected 完成\n")
|
|
|
|
print(f" handle_arm_selected 完成\n")
|
|
|
|
|
|
|
|
|
|
|
|
def handle_takeoff_selected(self):
|
|
|
|
def handle_takeoff_selected(self):
|
|
|
|
|
|
|
|
"""起飛 active group 的所有選中無人機"""
|
|
|
|
|
|
|
|
group = self._get_active_group()
|
|
|
|
|
|
|
|
if not group:
|
|
|
|
|
|
|
|
self.statusBar().showMessage("⚠ 請先建立任務群組", 3000)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
selected = list(group.selected_drone_ids)
|
|
|
|
|
|
|
|
if not selected:
|
|
|
|
|
|
|
|
self.statusBar().showMessage("⚠ 尚未選擇任何無人機", 3000)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
for drone_id in self.monitor.selected_drones:
|
|
|
|
for drone_id in selected:
|
|
|
|
future = self.monitor.takeoff_drone(drone_id, 10.0)
|
|
|
|
future = self.monitor.takeoff_drone(drone_id, 10.0)
|
|
|
|
loop.create_task(self.handle_service_response(future, f"批次起飛 {drone_id}"))
|
|
|
|
loop.create_task(self.handle_service_response(future, f"批次起飛 {drone_id}"))
|
|
|
|
|
|
|
|
|
|
|
|
@ -584,25 +611,34 @@ class ControlStationUI(QMainWindow):
|
|
|
|
scroll.setWidgetResizable(True)
|
|
|
|
scroll.setWidgetResizable(True)
|
|
|
|
idx = self.group_tab_widget.addTab(scroll, f"Group {gid}")
|
|
|
|
idx = self.group_tab_widget.addTab(scroll, f"Group {gid}")
|
|
|
|
self.group_tab_widget.tabBar().setTabTextColor(idx, QColor(color))
|
|
|
|
self.group_tab_widget.tabBar().setTabTextColor(idx, QColor(color))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 切換到新 group 的 tab
|
|
|
|
self.group_tab_widget.setCurrentIndex(idx)
|
|
|
|
self.group_tab_widget.setCurrentIndex(idx)
|
|
|
|
self.active_group_id = gid
|
|
|
|
|
|
|
|
self.statusBar().showMessage(f"已新增 Group {gid}", 2000)
|
|
|
|
self.statusBar().showMessage(f"已新增 Group {gid}", 2000)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新刪除按鈕的啟用/禁用狀態
|
|
|
|
# 更新刪除按鈕的啟用/禁用狀態
|
|
|
|
self._update_delete_buttons_state()
|
|
|
|
self._update_delete_buttons_state()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_group_tab_changed(self, index):
|
|
|
|
def _on_group_tab_changed(self, index):
|
|
|
|
"""Tab 切換時更新 active group 並同步地圖模式"""
|
|
|
|
"""切換 group tab — 只需切換 active group 並刷新 UI"""
|
|
|
|
if index < 0:
|
|
|
|
if index < 0:
|
|
|
|
self.active_group_id = None
|
|
|
|
self.active_group_id = None
|
|
|
|
return
|
|
|
|
return
|
|
|
|
# tab 標題是 "Group X"
|
|
|
|
|
|
|
|
tab_text = self.group_tab_widget.tabText(index)
|
|
|
|
tab_text = self.group_tab_widget.tabText(index)
|
|
|
|
gid = tab_text.replace("Group ", "")
|
|
|
|
gid = tab_text.replace("Group ", "")
|
|
|
|
if gid in self.mission_groups:
|
|
|
|
if gid not in self.mission_groups:
|
|
|
|
self.active_group_id = gid
|
|
|
|
return
|
|
|
|
group = self.mission_groups[gid]
|
|
|
|
|
|
|
|
self.drone_map.set_mission_mode(group.mission_type)
|
|
|
|
self.active_group_id = gid
|
|
|
|
|
|
|
|
group = self.mission_groups[gid]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 同步地圖的任務模式
|
|
|
|
|
|
|
|
self.drone_map.set_mission_mode(group.mission_type)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 統一刷新所有 UI
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def _get_active_group(self):
|
|
|
|
def _get_active_group(self):
|
|
|
|
"""取得當前 active 的 MissionGroup"""
|
|
|
|
"""取得當前 active 的 MissionGroup"""
|
|
|
|
@ -610,53 +646,84 @@ class ControlStationUI(QMainWindow):
|
|
|
|
return self.mission_groups[self.active_group_id]
|
|
|
|
return self.mission_groups[self.active_group_id]
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def _get_other_assigned(self, exclude_gid):
|
|
|
|
def refresh_selection_ui(self):
|
|
|
|
"""取得其他群組已佔用的無人機 {drone_id: group_id}"""
|
|
|
|
"""統一刷新所有 UI 元素:左側 drone 勾選、左側 socket 勾選、右側 group panel"""
|
|
|
|
assigned = {}
|
|
|
|
group = self._get_active_group()
|
|
|
|
for gid, group in self.mission_groups.items():
|
|
|
|
selected = group.selected_drone_ids if group else set()
|
|
|
|
if gid == exclude_gid:
|
|
|
|
|
|
|
|
continue
|
|
|
|
# 左側 drone checkbox
|
|
|
|
for did in group.drone_ids:
|
|
|
|
for drone_id, panel in self.drones.items():
|
|
|
|
assigned[did] = gid
|
|
|
|
checkbox = panel.get_checkbox()
|
|
|
|
return assigned
|
|
|
|
if checkbox:
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
checkbox.setChecked(drone_id in selected)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 左側 socket checkbox
|
|
|
|
|
|
|
|
for socket_id in self.socket_groups.keys():
|
|
|
|
|
|
|
|
self.refresh_socket_checkbox(socket_id, selected)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 右側 group panel
|
|
|
|
|
|
|
|
if group:
|
|
|
|
|
|
|
|
panel = self.group_panels.get(group.group_id)
|
|
|
|
|
|
|
|
if panel:
|
|
|
|
|
|
|
|
panel.update_drone_list()
|
|
|
|
|
|
|
|
panel.update_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def refresh_socket_checkbox(self, socket_id, selected_ids):
|
|
|
|
|
|
|
|
"""根據 selected_ids 推導並更新 socket 的勾選狀態"""
|
|
|
|
|
|
|
|
drone_ids = [did for did in self.drones.keys() if self.get_socket_id(did) == socket_id]
|
|
|
|
|
|
|
|
if not drone_ids:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
socket_widget = self.socket_groups.get(socket_id)
|
|
|
|
|
|
|
|
if not socket_widget:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checkbox = socket_widget.findChild(QCheckBox, f"socket_{socket_id}_checkbox")
|
|
|
|
|
|
|
|
if not checkbox:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checked_count = sum(1 for did in drone_ids if did in selected_ids)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
if checked_count == 0:
|
|
|
|
|
|
|
|
checkbox.setCheckState(Qt.CheckState.Unchecked)
|
|
|
|
|
|
|
|
elif checked_count == len(drone_ids):
|
|
|
|
|
|
|
|
checkbox.setCheckState(Qt.CheckState.Checked)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
checkbox.setCheckState(Qt.CheckState.PartiallyChecked)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 【已遷移至 refresh_socket_checkbox()】舊的 update_group_checkbox_state() 已廢棄
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_assign_drones(self, group_id):
|
|
|
|
def _handle_assign_drones(self, group_id):
|
|
|
|
"""開啟無人機分配對話框(已勾選的 checkbox 會預先帶入)"""
|
|
|
|
"""開啟無人機分配對話框 — 直接修改 selected_drone_ids"""
|
|
|
|
group = self.mission_groups.get(group_id)
|
|
|
|
group = self.mission_groups.get(group_id)
|
|
|
|
if not group:
|
|
|
|
if not group:
|
|
|
|
return
|
|
|
|
return
|
|
|
|
all_ids = list(self.drones.keys())
|
|
|
|
all_ids = list(self.drones.keys())
|
|
|
|
other_assigned = self._get_other_assigned(group_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 將目前 checkbox 已勾選的無人機(且未被其他群組佔用)合併進 pre-selected
|
|
|
|
# 預先選中:目前 group 的 selected_drone_ids
|
|
|
|
currently_checked = self.get_selected_drones()
|
|
|
|
pre_selected = set(group.selected_drone_ids)
|
|
|
|
pre_selected = set(group.drone_ids)
|
|
|
|
|
|
|
|
for did in currently_checked:
|
|
|
|
|
|
|
|
if did not in other_assigned:
|
|
|
|
|
|
|
|
pre_selected.add(did)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dialog = DroneAssignDialog(self, all_ids, pre_selected, other_assigned)
|
|
|
|
# 允許多 group 分配,所以不需要 other_assigned 過濾
|
|
|
|
|
|
|
|
dialog = DroneAssignDialog(self, all_ids, pre_selected, {})
|
|
|
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
|
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
|
|
group.drone_ids = dialog.get_selected()
|
|
|
|
group.selected_drone_ids = dialog.get_selected()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 只有當操作目標組是 active 組時,才更新 UI
|
|
|
|
|
|
|
|
if group_id == self.active_group_id:
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
if panel:
|
|
|
|
if panel:
|
|
|
|
panel.update_drone_list()
|
|
|
|
panel.update_drone_list()
|
|
|
|
panel.update_status()
|
|
|
|
panel.update_status()
|
|
|
|
|
|
|
|
|
|
|
|
# 同步更新左側面板的 checkbox 狀態
|
|
|
|
|
|
|
|
self.monitor.selected_drones = group.drone_ids.copy()
|
|
|
|
|
|
|
|
for drone_id in all_ids:
|
|
|
|
|
|
|
|
if drone_id in self.drones:
|
|
|
|
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
|
|
|
|
if checkbox:
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
checkbox.setChecked(drone_id in group.drone_ids)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
# 更新 socket 群組的 checkbox 狀態
|
|
|
|
|
|
|
|
self.update_group_checkbox_state(self.get_socket_id(drone_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
f"Group {group_id}: 已分配 {len(group.drone_ids)} 台無人機", 3000)
|
|
|
|
f"Group {group_id}: 已分配 {len(group.selected_drone_ids)} 台無人機", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_mission_type_changed(self, group_id, mission_type):
|
|
|
|
def _handle_mission_type_changed(self, group_id, mission_type):
|
|
|
|
"""群組任務類型切換"""
|
|
|
|
"""群組任務類型切換"""
|
|
|
|
@ -738,19 +805,19 @@ class ControlStationUI(QMainWindow):
|
|
|
|
print(f"❌ 找不到群組: {group_id}", flush=True)
|
|
|
|
print(f"❌ 找不到群組: {group_id}", flush=True)
|
|
|
|
return
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if not group.drone_ids:
|
|
|
|
if not group.selected_drone_ids:
|
|
|
|
print(f"⚠️ 群組中沒有無人機", flush=True)
|
|
|
|
print(f"⚠️ 群組中沒有無人機", flush=True)
|
|
|
|
self.statusBar().showMessage(f"群組 {group_id} 中沒有無人機", 3000)
|
|
|
|
self.statusBar().showMessage(f"群組 {group_id} 中沒有無人機", 3000)
|
|
|
|
return
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
print(f" 準備為 {len(group.drone_ids)} 台無人機切換模式...", flush=True)
|
|
|
|
print(f" 準備為 {len(group.selected_drone_ids)} 台無人機切換模式...", flush=True)
|
|
|
|
self.statusBar().showMessage(f"正在切換模式...", 1000)
|
|
|
|
self.statusBar().showMessage(f"正在切換模式...", 1000)
|
|
|
|
|
|
|
|
|
|
|
|
# 使用 asyncio 執行(通過事件循環)
|
|
|
|
# 使用 asyncio 執行(通過事件循環)
|
|
|
|
async def do_mode_changes_async():
|
|
|
|
async def do_mode_changes_async():
|
|
|
|
print(f"\n 【異步任務】開始執行模式切換", flush=True)
|
|
|
|
print(f"\n 【異步任務】開始執行模式切換", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
for drone_id in group.drone_ids:
|
|
|
|
for drone_id in group.selected_drone_ids:
|
|
|
|
print(f"\n 【切換】{drone_id} → {mode}", flush=True)
|
|
|
|
print(f"\n 【切換】{drone_id} → {mode}", flush=True)
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
result = await self.monitor.set_mode(drone_id, mode)
|
|
|
|
result = await self.monitor.set_mode(drone_id, mode)
|
|
|
|
@ -789,11 +856,11 @@ class ControlStationUI(QMainWindow):
|
|
|
|
if not group:
|
|
|
|
if not group:
|
|
|
|
print(f" ⚠️ 群組不存在,返回\n")
|
|
|
|
print(f" ⚠️ 群組不存在,返回\n")
|
|
|
|
return
|
|
|
|
return
|
|
|
|
print(f" 群組內無人機: {group.drone_ids}")
|
|
|
|
print(f" 群組內無人機: {group.selected_drone_ids}")
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
print(f" 事件循環: {loop}")
|
|
|
|
print(f" 事件循環: {loop}")
|
|
|
|
|
|
|
|
|
|
|
|
for drone_id in group.drone_ids:
|
|
|
|
for drone_id in group.selected_drone_ids:
|
|
|
|
print(f"\n ┌─ 處理無人機: {drone_id}")
|
|
|
|
print(f"\n ┌─ 處理無人機: {drone_id}")
|
|
|
|
print(f" ├─ 準備調用 arm_drone(drone_id={drone_id}, arm=True)")
|
|
|
|
print(f" ├─ 準備調用 arm_drone(drone_id={drone_id}, arm=True)")
|
|
|
|
coro = self.monitor.arm_drone(drone_id, True)
|
|
|
|
coro = self.monitor.arm_drone(drone_id, True)
|
|
|
|
@ -817,7 +884,7 @@ class ControlStationUI(QMainWindow):
|
|
|
|
if not group:
|
|
|
|
if not group:
|
|
|
|
return
|
|
|
|
return
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
for drone_id in group.drone_ids:
|
|
|
|
for drone_id in group.selected_drone_ids:
|
|
|
|
future = self.monitor.takeoff_drone(drone_id, altitude)
|
|
|
|
future = self.monitor.takeoff_drone(drone_id, altitude)
|
|
|
|
loop.create_task(self.handle_service_response(future, f"起飛 {drone_id} ({altitude}m)"))
|
|
|
|
loop.create_task(self.handle_service_response(future, f"起飛 {drone_id} ({altitude}m)"))
|
|
|
|
|
|
|
|
|
|
|
|
@ -829,7 +896,7 @@ class ControlStationUI(QMainWindow):
|
|
|
|
f"請在地圖上框選要分配到 Group {group_id} 的無人機", 5000)
|
|
|
|
f"請在地圖上框選要分配到 Group {group_id} 的無人機", 5000)
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_drone_box_selected(self, drone_ids_json):
|
|
|
|
def _handle_drone_box_selected(self, drone_ids_json):
|
|
|
|
"""地圖框選完成 — 直接分配到指定群組"""
|
|
|
|
"""地圖框選完成 — 直接分配到指定群組的 selected_drone_ids"""
|
|
|
|
group_id = self._pending_box_assign
|
|
|
|
group_id = self._pending_box_assign
|
|
|
|
self._pending_box_assign = None
|
|
|
|
self._pending_box_assign = None
|
|
|
|
if not group_id:
|
|
|
|
if not group_id:
|
|
|
|
@ -838,93 +905,72 @@ class ControlStationUI(QMainWindow):
|
|
|
|
if not group:
|
|
|
|
if not group:
|
|
|
|
return
|
|
|
|
return
|
|
|
|
drone_ids = json.loads(drone_ids_json)
|
|
|
|
drone_ids = json.loads(drone_ids_json)
|
|
|
|
other = self._get_other_assigned(group_id)
|
|
|
|
group.selected_drone_ids = set(drone_ids)
|
|
|
|
valid = {did for did in drone_ids if did not in other}
|
|
|
|
|
|
|
|
group.drone_ids = valid
|
|
|
|
# 只有當操作目標組是 active 組時,才更新 UI
|
|
|
|
|
|
|
|
if group_id == self.active_group_id:
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
if panel:
|
|
|
|
if panel:
|
|
|
|
panel.update_drone_list()
|
|
|
|
panel.update_drone_list()
|
|
|
|
panel.update_status()
|
|
|
|
panel.update_status()
|
|
|
|
|
|
|
|
|
|
|
|
# 同步更新左側面板的 checkbox 狀態
|
|
|
|
|
|
|
|
self.monitor.selected_drones = group.drone_ids.copy()
|
|
|
|
|
|
|
|
for drone_id in self.drones.keys():
|
|
|
|
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
|
|
|
|
if checkbox:
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
checkbox.setChecked(drone_id in group.drone_ids)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
self.update_group_checkbox_state(self.get_socket_id(drone_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
f"Group {group_id}: 框選分配 {len(valid)} 台無人機", 3000)
|
|
|
|
f"Group {group_id}: 框選分配 {len(drone_ids)} 台無人機", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_select_all_for_group(self, group_id):
|
|
|
|
def _handle_select_all_for_group(self, group_id):
|
|
|
|
"""全選/取消全選 - Toggle 邏輯"""
|
|
|
|
"""全選/取消全選 — Toggle 邏輯"""
|
|
|
|
group = self.mission_groups.get(group_id)
|
|
|
|
group = self.mission_groups.get(group_id)
|
|
|
|
if not group:
|
|
|
|
if not group:
|
|
|
|
return
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
other = self._get_other_assigned(group_id)
|
|
|
|
all_ids = set(self.drones.keys())
|
|
|
|
available = {did for did in self.drones.keys() if did not in other}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Toggle 邏輯:如果已全選,則清空;否則全選
|
|
|
|
# Toggle 邏輯:如果已全選,則清空;否則全選
|
|
|
|
if group.drone_ids == available:
|
|
|
|
if group.selected_drone_ids == all_ids:
|
|
|
|
# 已全選 → 清空
|
|
|
|
# 已全選 → 清空
|
|
|
|
group.drone_ids = set()
|
|
|
|
group.selected_drone_ids.clear()
|
|
|
|
self.monitor.selected_drones.clear()
|
|
|
|
|
|
|
|
msg_status = "已取消全選"
|
|
|
|
msg_status = "已取消全選"
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
# 未全選 → 全選
|
|
|
|
# 未全選 → 全選
|
|
|
|
group.drone_ids = available
|
|
|
|
group.selected_drone_ids = set(all_ids)
|
|
|
|
self.monitor.selected_drones = group.drone_ids.copy()
|
|
|
|
msg_status = f"全選分配 {len(all_ids)} 台無人機"
|
|
|
|
msg_status = f"全選分配 {len(available)} 台無人機"
|
|
|
|
|
|
|
|
|
|
|
|
# 只有當操作目標組是 active 組時,才更新 UI
|
|
|
|
|
|
|
|
if group_id == self.active_group_id:
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
if panel:
|
|
|
|
if panel:
|
|
|
|
panel.update_drone_list()
|
|
|
|
panel.update_drone_list()
|
|
|
|
panel.update_status()
|
|
|
|
panel.update_status()
|
|
|
|
# 更新按鈕文本
|
|
|
|
|
|
|
|
panel.set_all_select_state(group.drone_ids == available)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 同步更新左側面板的 checkbox 狀態
|
|
|
|
|
|
|
|
for drone_id in self.drones.keys():
|
|
|
|
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
|
|
|
|
if checkbox:
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
checkbox.setChecked(drone_id in group.drone_ids)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
self.update_group_checkbox_state(self.get_socket_id(drone_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
f"Group {group_id}: {msg_status}", 3000)
|
|
|
|
f"Group {group_id}: {msg_status}", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_clear_group(self, group_id):
|
|
|
|
def _handle_clear_group(self, group_id):
|
|
|
|
"""清除群組的無人機分配"""
|
|
|
|
"""清除群組的無人機選擇"""
|
|
|
|
group = self.mission_groups.get(group_id)
|
|
|
|
group = self.mission_groups.get(group_id)
|
|
|
|
if not group:
|
|
|
|
if not group:
|
|
|
|
return
|
|
|
|
return
|
|
|
|
group.drone_ids = set()
|
|
|
|
|
|
|
|
|
|
|
|
group.selected_drone_ids.clear()
|
|
|
|
group.planned_waypoints = None
|
|
|
|
group.planned_waypoints = None
|
|
|
|
if group.executor:
|
|
|
|
if group.executor:
|
|
|
|
group.executor.stop()
|
|
|
|
group.executor.stop()
|
|
|
|
self.drone_map.clear_mission_plan_for_group(group_id)
|
|
|
|
self.drone_map.clear_mission_plan_for_group(group_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 只有當操作目標組是 active 組時,才更新 UI
|
|
|
|
|
|
|
|
if group_id == self.active_group_id:
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
if panel:
|
|
|
|
if panel:
|
|
|
|
panel.update_drone_list()
|
|
|
|
panel.update_drone_list()
|
|
|
|
panel.update_status()
|
|
|
|
panel.update_status()
|
|
|
|
panel.clear_mission_info()
|
|
|
|
panel.clear_mission_info()
|
|
|
|
|
|
|
|
|
|
|
|
# 同步更新左側面板的 checkbox 狀態(全部取消勾選)
|
|
|
|
|
|
|
|
self.monitor.selected_drones.clear()
|
|
|
|
|
|
|
|
for drone_id in self.drones.keys():
|
|
|
|
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
|
|
|
|
if checkbox:
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
checkbox.setChecked(False)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
self.update_group_checkbox_state(self.get_socket_id(drone_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
self.statusBar().showMessage(
|
|
|
|
f"Group {group_id}: 已清除分組", 3000)
|
|
|
|
f"Group {group_id}: 已清除分組", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
@ -958,17 +1004,12 @@ class ControlStationUI(QMainWindow):
|
|
|
|
if group_id in self.group_panels:
|
|
|
|
if group_id in self.group_panels:
|
|
|
|
del self.group_panels[group_id]
|
|
|
|
del self.group_panels[group_id]
|
|
|
|
|
|
|
|
|
|
|
|
# 更新 active group
|
|
|
|
# 更新 active group — 讓 currentChanged signal 自動處理
|
|
|
|
if self.active_group_id == group_id:
|
|
|
|
if self.active_group_id == group_id:
|
|
|
|
|
|
|
|
self.active_group_id = None
|
|
|
|
if self.group_tab_widget.count() > 0:
|
|
|
|
if self.group_tab_widget.count() > 0:
|
|
|
|
self.group_tab_widget.setCurrentIndex(0)
|
|
|
|
self.group_tab_widget.setCurrentIndex(0)
|
|
|
|
# 更新 active_group_id 為當前 tab 的群組
|
|
|
|
# _on_group_tab_changed 會自動設定新的 active group
|
|
|
|
for gid, panel in self.group_panels.items():
|
|
|
|
|
|
|
|
if panel == self.group_tab_widget.currentWidget().widget():
|
|
|
|
|
|
|
|
self.active_group_id = gid
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self.active_group_id = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(f"已刪除 Group {group_id}", 3000)
|
|
|
|
self.statusBar().showMessage(f"已刪除 Group {group_id}", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
@ -1023,173 +1064,74 @@ class ControlStationUI(QMainWindow):
|
|
|
|
# ================================================================================
|
|
|
|
# ================================================================================
|
|
|
|
|
|
|
|
|
|
|
|
def handle_group_selection(self, socket_id, state):
|
|
|
|
def handle_group_selection(self, socket_id, state):
|
|
|
|
"""處理 socket 群組 checkbox 的勾選/取消勾選
|
|
|
|
"""Socket 群組勾選 — 更新該 socket 下所有 drone 的選擇狀態"""
|
|
|
|
|
|
|
|
group = self._get_active_group()
|
|
|
|
這個方法在用戶點擊 socket 群組的 checkbox 時被調用。
|
|
|
|
if not group:
|
|
|
|
需要同時更新:
|
|
|
|
return
|
|
|
|
1. 該 socket 下所有無人機的 checkbox
|
|
|
|
|
|
|
|
2. self.monitor.selected_drones(用於控制面板同步)
|
|
|
|
drone_ids = [did for did in self.drones.keys() if self.get_socket_id(did) == socket_id]
|
|
|
|
3. 右侧活躍群組的無人機分配(新增)
|
|
|
|
is_checked = (state == Qt.CheckState.Checked.value)
|
|
|
|
|
|
|
|
|
|
|
|
參數:
|
|
|
|
|
|
|
|
socket_id: socket ID (str)
|
|
|
|
|
|
|
|
state: Qt.CheckState 的整數值 (0=Unchecked, 1=PartiallyChecked, 2=Checked)
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
print(f"\n📢 [GUI] handle_group_selection 被調用", flush=True)
|
|
|
|
|
|
|
|
print(f" socket_id: {socket_id}, state: {state}", flush=True)
|
|
|
|
|
|
|
|
print(f" state 類型: {type(state)}", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 獲取該 socket 下所有無人機
|
|
|
|
|
|
|
|
group_drones = [did for did in self.drones.keys() if self.get_socket_id(did) == socket_id]
|
|
|
|
|
|
|
|
print(f" 該 socket 下的無人機: {group_drones}", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 判斷是否勾選(只有 state == 2 時才是 Checked)
|
|
|
|
|
|
|
|
is_checked = (state == 2) # Qt.CheckState.Checked.value == 2
|
|
|
|
|
|
|
|
print(f" is_checked: {is_checked}", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 更新該 socket 下所有無人機的 checkbox 狀態
|
|
|
|
|
|
|
|
for drone_id in group_drones:
|
|
|
|
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
|
|
|
|
if checkbox:
|
|
|
|
|
|
|
|
print(f" └─ 更新 {drone_id}: setChecked({is_checked})", flush=True)
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
checkbox.setChecked(is_checked)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 同時更新 monitor.selected_drones 以同步控制面板
|
|
|
|
|
|
|
|
if is_checked:
|
|
|
|
|
|
|
|
self.monitor.selected_drones.add(drone_id)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self.monitor.selected_drones.discard(drone_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 👇 新增:同步更新右侧活躍群組的無人機分配
|
|
|
|
|
|
|
|
if self.active_group_id:
|
|
|
|
|
|
|
|
group = self.mission_groups.get(self.active_group_id)
|
|
|
|
|
|
|
|
panel = self.group_panels.get(self.active_group_id)
|
|
|
|
|
|
|
|
if group and panel:
|
|
|
|
|
|
|
|
print(f" ├─ 同步右侧群組 {self.active_group_id}", flush=True)
|
|
|
|
|
|
|
|
if is_checked:
|
|
|
|
|
|
|
|
# 勾選時:將該 socket 下的無人機添加到活躍群組
|
|
|
|
|
|
|
|
for drone_id in group_drones:
|
|
|
|
|
|
|
|
group.drone_ids.add(drone_id)
|
|
|
|
|
|
|
|
print(f" │ └─ 添加到群組: {group_drones}", flush=True)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# 取消勾選時:從活躍群組移除該 socket 下的無人機
|
|
|
|
|
|
|
|
for drone_id in group_drones:
|
|
|
|
|
|
|
|
group.drone_ids.discard(drone_id)
|
|
|
|
|
|
|
|
print(f" │ └─ 從群組移除: {group_drones}", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 更新右側群組面板的顯示
|
|
|
|
|
|
|
|
panel.update_drone_list()
|
|
|
|
|
|
|
|
panel.update_status()
|
|
|
|
|
|
|
|
print(f" │ └─ 已更新右侧群組面板", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(f" 最終 selected_drones: {self.monitor.selected_drones}", flush=True)
|
|
|
|
|
|
|
|
print(f"✓ handle_group_selection 完成\n", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_drone_selection(self, drone_id, state):
|
|
|
|
|
|
|
|
is_checked = state == Qt.CheckState.Checked.value
|
|
|
|
|
|
|
|
if is_checked:
|
|
|
|
if is_checked:
|
|
|
|
self.monitor.selected_drones.add(drone_id)
|
|
|
|
group.selected_drone_ids.update(drone_ids)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
self.monitor.selected_drones.discard(drone_id)
|
|
|
|
group.selected_drone_ids.difference_update(drone_ids)
|
|
|
|
self.update_group_checkbox_state(self.get_socket_id(drone_id))
|
|
|
|
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
# 同步更新任務群組的無人機分配狀態
|
|
|
|
|
|
|
|
# 遍歷所有任務群組,更新已分配的無人機列表顯示
|
|
|
|
def handle_drone_selection(self, drone_id, state):
|
|
|
|
if not is_checked:
|
|
|
|
"""單台 drone 勾選 — 只修改 active group 的 selected_drone_ids"""
|
|
|
|
# 取消勾選時:從所有包含該無人機的群組中移除
|
|
|
|
group = self._get_active_group()
|
|
|
|
for group_id, group in self.mission_groups.items():
|
|
|
|
if not group:
|
|
|
|
if drone_id in group.drone_ids:
|
|
|
|
return
|
|
|
|
group.drone_ids.discard(drone_id)
|
|
|
|
|
|
|
|
panel = self.group_panels.get(group_id)
|
|
|
|
is_checked = (state == Qt.CheckState.Checked.value)
|
|
|
|
if panel:
|
|
|
|
|
|
|
|
panel.update_drone_list()
|
|
|
|
if is_checked:
|
|
|
|
panel.update_status()
|
|
|
|
group.selected_drone_ids.add(drone_id)
|
|
|
|
# 更新全選按鈕狀態
|
|
|
|
|
|
|
|
other = self._get_other_assigned(group_id)
|
|
|
|
|
|
|
|
available = {did for did in self.drones.keys() if did not in other}
|
|
|
|
|
|
|
|
panel.set_all_select_state(group.drone_ids == available)
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
# 勾選時:檢查該無人機是否已分配給其他群組,若未分配則添加到當前活躍群組
|
|
|
|
group.selected_drone_ids.discard(drone_id)
|
|
|
|
is_already_assigned = any(
|
|
|
|
|
|
|
|
drone_id in group.drone_ids
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
for group in self.mission_groups.values()
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if not is_already_assigned and self.active_group_id:
|
|
|
|
|
|
|
|
# 無人機未被分配給任何群組,可以添加到當前活躍群組
|
|
|
|
|
|
|
|
group = self.mission_groups.get(self.active_group_id)
|
|
|
|
|
|
|
|
panel = self.group_panels.get(self.active_group_id)
|
|
|
|
|
|
|
|
if group and panel:
|
|
|
|
|
|
|
|
group.drone_ids.add(drone_id)
|
|
|
|
|
|
|
|
panel.update_drone_list()
|
|
|
|
|
|
|
|
panel.update_status()
|
|
|
|
|
|
|
|
# 更新全選按鈕狀態
|
|
|
|
|
|
|
|
other = self._get_other_assigned(self.active_group_id)
|
|
|
|
|
|
|
|
available = {did for did in self.drones.keys() if did not in other}
|
|
|
|
|
|
|
|
panel.set_all_select_state(group.drone_ids == available)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_group_checkbox_state(self, socket_id):
|
|
|
|
|
|
|
|
group_drones = [did for did in self.drones.keys() if self.get_socket_id(did) == socket_id]
|
|
|
|
|
|
|
|
if not group_drones: return
|
|
|
|
|
|
|
|
checked_count = sum(1 for did in group_drones if self.drones[did].is_checked())
|
|
|
|
|
|
|
|
if socket_id in self.socket_groups:
|
|
|
|
|
|
|
|
group_checkbox = self.socket_groups[socket_id].findChild(QCheckBox, f"socket_{socket_id}_checkbox")
|
|
|
|
|
|
|
|
if group_checkbox:
|
|
|
|
|
|
|
|
group_checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
if checked_count == 0: group_checkbox.setCheckState(Qt.CheckState.Unchecked)
|
|
|
|
|
|
|
|
elif checked_count == len(group_drones): group_checkbox.setCheckState(Qt.CheckState.Checked)
|
|
|
|
|
|
|
|
else: group_checkbox.setCheckState(Qt.CheckState.PartiallyChecked)
|
|
|
|
|
|
|
|
group_checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_select_all(self):
|
|
|
|
|
|
|
|
all_selected = all(self.drones[did].is_checked() for did in self.drones)
|
|
|
|
|
|
|
|
new_state = not all_selected
|
|
|
|
|
|
|
|
for drone_id in self.drones:
|
|
|
|
|
|
|
|
self.drones[drone_id].set_checked(new_state)
|
|
|
|
|
|
|
|
for socket_id in self.socket_groups:
|
|
|
|
|
|
|
|
group_checkbox = self.socket_groups[socket_id].findChild(QCheckBox, f"socket_{socket_id}_checkbox")
|
|
|
|
|
|
|
|
if group_checkbox:
|
|
|
|
|
|
|
|
group_checkbox.blockSignals(True)
|
|
|
|
|
|
|
|
group_checkbox.setChecked(new_state)
|
|
|
|
|
|
|
|
group_checkbox.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_drone_clicked(self, drone_id):
|
|
|
|
def handle_drone_clicked(self, drone_id):
|
|
|
|
if drone_id in self.drones:
|
|
|
|
"""地圖上點擊 drone — 切換其選擇狀態"""
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
group = self._get_active_group()
|
|
|
|
checkbox.setChecked(not checkbox.isChecked())
|
|
|
|
if not group:
|
|
|
|
|
|
|
|
return
|
|
|
|
def handle_clear_all_drone_selection(self):
|
|
|
|
|
|
|
|
for drone_id in self.drones.keys():
|
|
|
|
if drone_id in group.selected_drone_ids:
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
group.selected_drone_ids.remove(drone_id)
|
|
|
|
if checkbox:
|
|
|
|
else:
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
group.selected_drone_ids.add(drone_id)
|
|
|
|
checkbox.setChecked(False)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
self.monitor.selected_drones.clear()
|
|
|
|
|
|
|
|
for socket_id in self.socket_groups.keys():
|
|
|
|
|
|
|
|
self.update_group_checkbox_state(socket_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_toggle_select_all_drones(self):
|
|
|
|
def handle_toggle_select_all_drones(self):
|
|
|
|
all_selected = all(self.drones[did].get_checkbox().isChecked() for did in self.drones.keys())
|
|
|
|
"""全選 / 清空 — 切換 active group 的所有 drone"""
|
|
|
|
if all_selected:
|
|
|
|
group = self._get_active_group()
|
|
|
|
for drone_id in self.drones.keys():
|
|
|
|
if not group:
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
return
|
|
|
|
if checkbox:
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
all_ids = set(self.drones.keys())
|
|
|
|
checkbox.setChecked(False)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
if group.selected_drone_ids == all_ids:
|
|
|
|
self.monitor.selected_drones.clear()
|
|
|
|
# 已全選 → 清空
|
|
|
|
|
|
|
|
group.selected_drone_ids.clear()
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
for drone_id in self.drones.keys():
|
|
|
|
# 未全選 → 全選
|
|
|
|
checkbox = self.drones[drone_id].get_checkbox()
|
|
|
|
group.selected_drone_ids = set(all_ids)
|
|
|
|
if checkbox:
|
|
|
|
|
|
|
|
checkbox.blockSignals(True)
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
checkbox.setChecked(True)
|
|
|
|
|
|
|
|
checkbox.blockSignals(False)
|
|
|
|
def handle_clear_all_drone_selection(self):
|
|
|
|
self.monitor.selected_drones.add(drone_id)
|
|
|
|
"""清除 active group 的所有無人機選擇"""
|
|
|
|
for socket_id in self.socket_groups.keys():
|
|
|
|
group = self._get_active_group()
|
|
|
|
self.update_group_checkbox_state(socket_id)
|
|
|
|
if not group:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
group.selected_drone_ids.clear()
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
self.statusBar().showMessage("已清除所有選擇", 2000)
|
|
|
|
|
|
|
|
|
|
|
|
# ================================================================================
|
|
|
|
# ================================================================================
|
|
|
|
# 任務規劃 — 點擊地圖(路由到 active group)
|
|
|
|
# 任務規劃 — 點擊地圖(路由到 active group)
|
|
|
|
@ -1197,7 +1139,7 @@ class ControlStationUI(QMainWindow):
|
|
|
|
|
|
|
|
|
|
|
|
def _get_group_drones(self, group):
|
|
|
|
def _get_group_drones(self, group):
|
|
|
|
"""取得群組的無人機 ID 列表(排序後)"""
|
|
|
|
"""取得群組的無人機 ID 列表(排序後)"""
|
|
|
|
return sorted(group.drone_ids, key=lambda x: (x.split('_')[0], int(x.split('_')[1])))
|
|
|
|
return sorted(group.selected_drone_ids, key=lambda x: (x.split('_')[0], int(x.split('_')[1])))
|
|
|
|
|
|
|
|
|
|
|
|
def handle_map_click(self, lat, lon):
|
|
|
|
def handle_map_click(self, lat, lon):
|
|
|
|
"""處理地圖點擊事件 — 根據 active group 的任務類型規劃"""
|
|
|
|
"""處理地圖點擊事件 — 根據 active group 的任務類型規劃"""
|
|
|
|
@ -1518,9 +1460,6 @@ class ControlStationUI(QMainWindow):
|
|
|
|
print(f" WP{j}: ({wp[0]:.6f}°, {wp[1]:.6f}°, {wp[2]:.1f}m)")
|
|
|
|
print(f" WP{j}: ({wp[0]:.6f}°, {wp[1]:.6f}°, {wp[2]:.1f}m)")
|
|
|
|
print(f"\n{'=' * 60}")
|
|
|
|
print(f"\n{'=' * 60}")
|
|
|
|
|
|
|
|
|
|
|
|
def get_selected_drones(self):
|
|
|
|
|
|
|
|
return [did for did, panel in self.drones.items() if hasattr(panel, 'checkbox') and panel.checkbox.isChecked()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_field(self, panel, drone_id, field, text, color=None):
|
|
|
|
def update_field(self, panel, drone_id, field, text, color=None):
|
|
|
|
if isinstance(panel, DronePanel):
|
|
|
|
if isinstance(panel, DronePanel):
|
|
|
|
panel.update_field(field, text, color)
|
|
|
|
panel.update_field(field, text, color)
|
|
|
|
@ -1544,8 +1483,9 @@ class ControlStationUI(QMainWindow):
|
|
|
|
self.socket_groups[socket_id] = self.create_socket_group_panel(socket_id)
|
|
|
|
self.socket_groups[socket_id] = self.create_socket_group_panel(socket_id)
|
|
|
|
self.socket_groups[socket_id].drones_layout.addWidget(panel)
|
|
|
|
self.socket_groups[socket_id].drones_layout.addWidget(panel)
|
|
|
|
self.reorganize_socket_groups()
|
|
|
|
self.reorganize_socket_groups()
|
|
|
|
self.update_group_checkbox_state(socket_id)
|
|
|
|
|
|
|
|
self.update_overview_table()
|
|
|
|
self.update_overview_table()
|
|
|
|
|
|
|
|
# 同步新 drone 到 UI
|
|
|
|
|
|
|
|
self.refresh_selection_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def reorganize_socket_groups(self):
|
|
|
|
def reorganize_socket_groups(self):
|
|
|
|
while self.drone_panel_layout.count():
|
|
|
|
while self.drone_panel_layout.count():
|
|
|
|
|