forked from chiyu1468/AirTrapMine
Update GUI 2.5.0: Drone reboot
This commit is contained in:
@@ -1061,6 +1061,39 @@ class DroneMonitor(Node):
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
async def reboot_drone(self, drone_id):
|
||||
"""使用 CommandLongClient 執行飛控重啟。"""
|
||||
try:
|
||||
parts = drone_id.split('_')
|
||||
if len(parts) < 2:
|
||||
_log("ERROR", f"[REBOOT] 無效的 drone_id 格式: {drone_id}")
|
||||
return False
|
||||
sysid = int(parts[-1])
|
||||
|
||||
_log("INFO", f"[REBOOT] {drone_id} -> 飛控重啟")
|
||||
|
||||
client = self.get_or_create_client(drone_id)
|
||||
if not client:
|
||||
_log("ERROR", "[REBOOT] CommandLongClient 無法初始化")
|
||||
return False
|
||||
|
||||
result = await client.reboot_autopilot_async(
|
||||
target_sysid=sysid,
|
||||
target_compid=0,
|
||||
timeout_sec=5.0,
|
||||
)
|
||||
|
||||
if result and result.success:
|
||||
_log("INFO", f"[REBOOT] {drone_id} 重啟命令已送出")
|
||||
return True
|
||||
|
||||
_log("ERROR", f"[REBOOT] 重啟失敗 (message={result.message if result else 'None'})")
|
||||
return False
|
||||
except Exception as e:
|
||||
_log("ERROR", f"[REBOOT] 例外錯誤: {e}")
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def send_setpoint(self, drone_id, x, y, z):
|
||||
"""Send setpoint position command"""
|
||||
if drone_id not in self.setpoint_pubs:
|
||||
|
||||
+37
-2
@@ -4,7 +4,8 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout
|
||||
QWidget, QLabel, QSplitter, QScrollArea,
|
||||
QSizePolicy, QTabWidget, QTableWidget, QTableWidgetItem,
|
||||
QHeaderView, QPushButton, QCheckBox, QLineEdit,
|
||||
QComboBox, QDialog, QPlainTextEdit, QSlider)
|
||||
QComboBox, QDialog, QPlainTextEdit, QSlider,
|
||||
QMessageBox)
|
||||
from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSignal, QEvent, QPropertyAnimation, pyqtProperty, QEasingCurve
|
||||
from PyQt6.QtGui import QColor, QFont, QPainter, QPen
|
||||
import sys
|
||||
@@ -147,7 +148,7 @@ class ToggleSwitch(QWidget):
|
||||
class ControlStationUI(QMainWindow):
|
||||
planning_finished = pyqtSignal(object)
|
||||
|
||||
VERSION = '2.4.1'
|
||||
VERSION = '2.5.0'
|
||||
FONT_SCALE_MIN = 70
|
||||
FONT_SCALE_MAX = 180
|
||||
FONT_SCALE_DEFAULT = 100
|
||||
@@ -1102,6 +1103,7 @@ class ControlStationUI(QMainWindow):
|
||||
panel.mode_change_requested.connect(self._handle_group_mode_change)
|
||||
panel.arm_requested.connect(self._handle_group_arm)
|
||||
panel.takeoff_requested.connect(self._handle_group_takeoff)
|
||||
panel.reboot_requested.connect(self._handle_group_reboot)
|
||||
panel.box_select_requested.connect(self._handle_box_select)
|
||||
panel.select_all_requested.connect(self._handle_select_all_for_group)
|
||||
panel.clear_group_requested.connect(self._handle_clear_group)
|
||||
@@ -1426,6 +1428,39 @@ class ControlStationUI(QMainWindow):
|
||||
future = self.monitor.takeoff_drone(drone_id, altitude)
|
||||
loop.create_task(self.handle_service_response(future, f"起飛 {drone_id} ({altitude}m)"))
|
||||
|
||||
def _handle_group_reboot(self, group_id):
|
||||
"""重啟群組內所有選中的飛控。"""
|
||||
group = self.mission_groups.get(group_id)
|
||||
if not group:
|
||||
_log("WARN", f"找不到群組: {group_id}")
|
||||
return
|
||||
|
||||
selected = list(group.selected_drone_ids)
|
||||
if not selected:
|
||||
self.statusBar().showMessage(f"群組 {group_id} 中沒有無人機", 3000)
|
||||
return
|
||||
|
||||
reply = QMessageBox.warning(
|
||||
self,
|
||||
"確認重啟飛控",
|
||||
f"即將透過 command long 重啟 Group {group_id} 的 {len(selected)} 台飛控:\n"
|
||||
f"{', '.join(selected)}\n\n"
|
||||
"這會中斷飛控連線,請確認無人機已在安全狀態。",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply != QMessageBox.StandardButton.Yes:
|
||||
_log("WARN", f"Group {group_id} 飛控重啟已取消")
|
||||
return
|
||||
|
||||
_log("INFO", f"Group {group_id} 批次重啟飛控: {', '.join(selected)}")
|
||||
self.statusBar().showMessage(f"Group {group_id}: 正在送出重啟命令", 3000)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
for drone_id in selected:
|
||||
future = self.monitor.reboot_drone(drone_id)
|
||||
loop.create_task(self.handle_service_response(future, f"重啟飛控 {drone_id}"))
|
||||
|
||||
def _handle_box_select(self, group_id):
|
||||
"""觸發地圖框選 → 框選完成後直接分配到該群組"""
|
||||
self._pending_box_assign = group_id
|
||||
|
||||
@@ -369,6 +369,16 @@ class DroneMap:
|
||||
var drawStartPoint = null;
|
||||
var mapDragStarted = false;
|
||||
var clickStartPos = null;
|
||||
var lastManualMapMoveAt = 0;
|
||||
var autoCenteringMap = false;
|
||||
|
||||
function markManualMapMove() {
|
||||
if (!autoCenteringMap) {
|
||||
lastManualMapMoveAt = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
map.on('dragstart drag dragend zoomstart zoomend', markManualMapMove);
|
||||
|
||||
// 路徑標記變量 (跟隨模式用)
|
||||
var routePoints = [];
|
||||
@@ -715,13 +725,20 @@ class DroneMap:
|
||||
if (!markers[id]) return;
|
||||
focusedId = id;
|
||||
var latlng = markers[id].getLatLng();
|
||||
autoCenteringMap = true;
|
||||
map.flyTo(latlng, map.getZoom());
|
||||
setTimeout(() => { autoCenteringMap = false; }, 500);
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
if (focusedId && markers[focusedId]) {
|
||||
if (Date.now() - lastManualMapMoveAt < 1000) {
|
||||
return;
|
||||
}
|
||||
var latlng = markers[focusedId].getLatLng();
|
||||
autoCenteringMap = true;
|
||||
map.panTo(latlng);
|
||||
setTimeout(() => { autoCenteringMap = false; }, 300);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@ class GroupPanel(QWidget):
|
||||
mode_change_requested = pyqtSignal(str, str) # group_id, mode
|
||||
arm_requested = pyqtSignal(str) # group_id
|
||||
takeoff_requested = pyqtSignal(str, float) # group_id, altitude
|
||||
reboot_requested = pyqtSignal(str) # group_id
|
||||
box_select_requested = pyqtSignal(str) # group_id — 框選直接分配
|
||||
select_all_requested = pyqtSignal(str) # group_id — 全選直接分配
|
||||
clear_group_requested = pyqtSignal(str) # group_id — 清除分組
|
||||
@@ -370,6 +371,23 @@ class GroupPanel(QWidget):
|
||||
|
||||
right.addStretch()
|
||||
|
||||
# ============================
|
||||
# 飛控操作
|
||||
# ============================
|
||||
reboot_col = QVBoxLayout()
|
||||
reboot_col.setSpacing(3)
|
||||
|
||||
reboot_title = QLabel("飛控操作")
|
||||
reboot_title.setStyleSheet(TITLE)
|
||||
reboot_col.addWidget(reboot_title)
|
||||
|
||||
reboot_btn = QPushButton("重啟飛控")
|
||||
reboot_btn.setStyleSheet(BTN.format(bg='#8A3A3A', fg='#FFF', hover='#A84646'))
|
||||
reboot_btn.clicked.connect(
|
||||
lambda: self.reboot_requested.emit(self.group.group_id))
|
||||
reboot_col.addWidget(reboot_btn)
|
||||
reboot_col.addStretch()
|
||||
|
||||
# ============================
|
||||
# 第四欄:任務參數
|
||||
# ============================
|
||||
@@ -453,7 +471,7 @@ class GroupPanel(QWidget):
|
||||
# 當任務類型切換時更新參數顯示
|
||||
self.type_combo.currentTextChanged.connect(self._update_param_visibility)
|
||||
|
||||
# ── 組裝四欄:控制指令 > 任務規劃 > 任務參數 > 選取與分組 ──
|
||||
# ── 組裝欄位:控制指令 > 任務規劃 > 任務參數 > 選取與分組 > 飛控操作 ──
|
||||
# 使用伸展因子 0 讓列根據內容自動調整寬度,而不是均等分配
|
||||
cols.addLayout(left, 0)
|
||||
cols.addWidget(self._make_sep())
|
||||
@@ -462,7 +480,9 @@ class GroupPanel(QWidget):
|
||||
cols.addLayout(param_col, 0)
|
||||
cols.addWidget(self._make_sep())
|
||||
cols.addLayout(right, 0)
|
||||
cols.addStretch() # 填充剩餘空間,使四列置左
|
||||
cols.addWidget(self._make_sep())
|
||||
cols.addLayout(reboot_col, 0)
|
||||
cols.addStretch() # 填充剩餘空間,使欄位置左
|
||||
|
||||
layout.addLayout(cols)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user