Upload files to 'src/unitdev01/unitdev01'

chiyu
ken910606 9 months ago
parent 47a681e27d
commit 69aae9cae2

@ -53,7 +53,7 @@ class DroneMonitor(Node):
max_retries = 5
base_delay = 1.0
local = "ws://0.0.0.0:8765" # 本地 WebSocket 地址
remote = "ws://140.120.31.123:8765"
remote = "ws://192.168.50.48:8756"
while retry_count < max_retries:
try:
async with websockets.connect(local) as websocket:
@ -63,7 +63,6 @@ class DroneMonitor(Node):
async for message in websocket:
try:
data = json.loads(message)
print(f"📡 Received: {data}")
self.process_websocket_message(data)
except json.JSONDecodeError as e:
print(f"WebSocket JSON decode error: {e}")
@ -402,6 +401,13 @@ class ControlStationUI(QMainWindow):
"loss_rate": 11,
"ping": 12
}
self.socket_colors = {
'0': '#00BFFF', # DeepSkyBlue
'1': '#FFD700', # Gold
'2': '#FF69B4', # HotPink
'9': '#7CFC00', # LawnGreen
'default': '#AAAAAA'
}
self.drone_positions = {}
self.drone_headings = {}
self.map_loaded = False
@ -440,37 +446,47 @@ class ControlStationUI(QMainWindow):
bottom_control = QWidget()
bottom_layout = QVBoxLayout(bottom_control)
# 上方按鈕區域
upper_buttons = QHBoxLayout()
select_all_btn = QPushButton("全選")
select_all_btn.clicked.connect(self.handle_select_all)
select_all_btn.setStyleSheet("""
QPushButton {
background-color: #444;
color: #DDD;
border: none;
padding: 5px 10px;
border-radius: 4px;
min-width: 80px;
}
QPushButton:hover { background-color: #555; }
""")
arm_all_btn = QPushButton("批次解鎖")
arm_all_btn.clicked.connect(self.handle_arm_selected)
arm_all_btn.setStyleSheet("""
QPushButton {
background-color: #444;
color: #DDD;
border: none;
padding: 5px 10px;
border-radius: 4px;
min-width: 80px;
}
QPushButton:hover { background-color: #555; }
""")
takeoff_all_btn = QPushButton("批次起飛")
takeoff_all_btn.clicked.connect(self.handle_takeoff_selected)
takeoff_all_btn.setStyleSheet("""
for btn in [select_all_btn, arm_all_btn, takeoff_all_btn]:
btn.setStyleSheet("""
QPushButton {
background-color: #444;
color: #DDD;
border: none;
padding: 5px 10px;
border-radius: 4px;
min-width: 80px;
}
QPushButton:hover { background-color: #555; }
""")
upper_buttons.addWidget(btn)
upper_buttons.addStretch()
# --- 模式切換區域 ---
mode_layout = QHBoxLayout()
mode_layout.setContentsMargins(0, 0, 0, 0)
mode_layout.setSpacing(8)
mode_label = QLabel("模式:")
mode_label.setStyleSheet("color: #DDD; min-width: 40px;")
from PyQt6.QtWidgets import QComboBox
self.mode_combo = QComboBox()
self.mode_combo.addItems(["AUTO", "GUIDED", "LOITER", "LAND"])
self.mode_combo.setCurrentIndex(1) # 預設 GUIDED
self.mode_combo.setStyleSheet("""
QComboBox { background-color: #333; color: #DDD; border-radius: 3px; padding: 2px 10px;}
""")
batch_mode_btn = QPushButton("批次切換模式")
batch_mode_btn.clicked.connect(self.handle_batch_mode_change)
batch_mode_btn.setStyleSheet("""
QPushButton {
background-color: #444;
color: #DDD;
@ -481,6 +497,10 @@ class ControlStationUI(QMainWindow):
}
QPushButton:hover { background-color: #555; }
""")
mode_layout.addWidget(mode_label)
mode_layout.addWidget(self.mode_combo)
mode_layout.addWidget(batch_mode_btn)
mode_layout.addStretch()
# Add coordinate inputs
coord_widget = QWidget()
@ -525,29 +545,6 @@ class ControlStationUI(QMainWindow):
QPushButton:hover { background-color: #555; }
""")
# 上方按鈕區域
upper_buttons = QHBoxLayout()
select_all_btn = QPushButton("全選")
select_all_btn.clicked.connect(self.handle_select_all)
arm_all_btn = QPushButton("批次解鎖")
arm_all_btn.clicked.connect(self.handle_arm_selected)
takeoff_all_btn = QPushButton("批次起飛")
takeoff_all_btn.clicked.connect(self.handle_takeoff_selected)
for btn in [select_all_btn, arm_all_btn, takeoff_all_btn]:
btn.setStyleSheet("""
QPushButton {
background-color: #444;
color: #DDD;
border: none;
padding: 5px 10px;
border-radius: 4px;
min-width: 80px;
}
QPushButton:hover { background-color: #555; }
""")
upper_buttons.addWidget(btn)
upper_buttons.addStretch()
# 下方座標輸入區域
lower_control = QHBoxLayout()
lower_control.addWidget(coord_widget)
@ -555,8 +552,9 @@ class ControlStationUI(QMainWindow):
lower_control.addStretch()
# 將兩個區域加入底部控制區
# 加入底部控制區
bottom_layout.addLayout(upper_buttons)
bottom_layout.addLayout(mode_layout)
bottom_layout.addLayout(lower_control)
# — 分頁 2Overview Table
@ -595,9 +593,10 @@ class ControlStationUI(QMainWindow):
<body>
<div id="map"></div>
<script>
var map = L.map('map').setView([0, 0], 24);
var map = L.map('map').setView([0, 0], 20);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 20
maxZoom: 19, // OpenStreetMap 支持的最大縮放級別
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var arrowIcon = L.icon({
@ -606,15 +605,25 @@ class ControlStationUI(QMainWindow):
iconAnchor: [20, 20]
});
function getColorBySocketId(id) {
if (id.startsWith("s0_")) return "#00BFFF"; // DeepSkyBlue
if (id.startsWith("s1_")) return "#FFD700"; // Gold
if (id.startsWith("s2_")) return "#FF69B4"; // HotPink
if (id.startsWith("s9_")) return "#7CFC00"; // LawnGreen
return "#AAAAAA"; // Default
}
// 新增 ID 標籤的圖標
function createIdIcon(id) {
const color = getColorBySocketId(id);
const sysid = id.split('_')[1]; // e.g., 's3_2' '2'
return L.divIcon({
className: 'drone-id',
html: `<div style="
position: relative;
left: 2px;
background-color: #00DDDD; // 改為較深的綠色
color: white;
background-color: ${color};
color: black;
width: 16px;
height: 16px;
border-radius: 50%;
@ -623,7 +632,7 @@ class ControlStationUI(QMainWindow):
justify-content: center;
font-weight: bold;
font-size: 10px;
">${id.substring(1)}</div>`,
">${sysid}</div>`,
iconSize: [20, 20],
iconAnchor: [10, 10]
});
@ -739,7 +748,7 @@ class ControlStationUI(QMainWindow):
def create_drone_panel(self, drone_id):
panel = QWidget()
panel.setObjectName(f"panel_{drone_id}")
panel.setFixedHeight(120) # 根據需要調整高度
panel.setFixedHeight(140) # 根據需要調整高度
panel.setStyleSheet("""
QWidget#panel_%s {
background-color: #2A2A2A;
@ -891,6 +900,23 @@ class ControlStationUI(QMainWindow):
# 設置控制區域的固定寬度
control_widget.setFixedWidth(80)
mode_btn = QPushButton("切換模式")
mode_btn.setObjectName(f"{drone_id}_mode_btn")
mode_btn.clicked.connect(lambda: self.handle_mode_change(drone_id))
mode_btn.setStyleSheet("""
QPushButton {
background-color: #444;
color: #DDD;
border: none;
padding: 8px 6px;
border-radius: 4px;
font-size: 11px;
}
QPushButton:hover {
background-color: #555;
}
""")
arm_btn = QPushButton("解鎖")
arm_btn.setObjectName(f"{drone_id}_arm_btn")
arm_btn.clicked.connect(lambda: self.handle_arm(drone_id))
@ -902,7 +928,6 @@ class ControlStationUI(QMainWindow):
padding: 8px 6px;
border-radius: 4px;
font-size: 11px;
min-height: 24px;
}
QPushButton:hover {
background-color: #555;
@ -920,7 +945,6 @@ class ControlStationUI(QMainWindow):
padding: 8px 6px;
border-radius: 4px;
font-size: 11px;
min-height: 24px;
}
QPushButton:hover {
background-color: #555;
@ -938,7 +962,6 @@ class ControlStationUI(QMainWindow):
padding: 8px 6px;
border-radius: 4px;
font-size: 11px;
min-height: 24px;
}
QPushButton:hover {
background-color: #555;
@ -946,6 +969,7 @@ class ControlStationUI(QMainWindow):
""")
# 按鈕由上而下排列
control_layout.addWidget(mode_btn)
control_layout.addWidget(arm_btn)
control_layout.addWidget(takeoff_btn)
control_layout.addWidget(setpoint_btn)
@ -980,18 +1004,35 @@ class ControlStationUI(QMainWindow):
layout.setContentsMargins(10, 10, 10, 10)
layout.setSpacing(6)
# Socket 分組標題行 - 包含勾選框
title_row = QWidget()
title_layout = QHBoxLayout(title_row)
title_layout.setContentsMargins(0, 0, 0, 0)
# 分組勾選框
group_checkbox = QCheckBox()
group_checkbox.setObjectName(f"socket_{socket_id}_checkbox")
group_checkbox.setStyleSheet("QCheckBox { color: #DDD; }")
group_checkbox.stateChanged.connect(lambda state: self.handle_group_selection(socket_id, state))
# Socket 分組標題
color = self.socket_colors.get(socket_id, self.socket_colors['default'])
title_label = QLabel(f"Socket {socket_id}")
title_label.setStyleSheet("""
title_label.setStyleSheet(f"""
font-weight: bold;
font-size: 16px;
color: #FFD700;
color: {color};
margin-bottom: 8px;
padding: 4px 8px;
background-color: #333;
border-radius: 6px;
""")
layout.addWidget(title_label)
title_layout.addWidget(group_checkbox)
title_layout.addWidget(title_label)
title_layout.addStretch()
layout.addWidget(title_row)
# 創建子容器用於放置該 socket 下的所有無人機面板
drones_container = QWidget()
@ -1027,6 +1068,13 @@ class ControlStationUI(QMainWindow):
hbox.addWidget(value_label)
layout.addWidget(row)
def handle_mode_change(self, drone_id):
"""處理單個無人機的模式切換"""
mode = self.mode_combo.currentText()
loop = asyncio.get_event_loop()
future = self.monitor.set_mode(drone_id, mode)
loop.create_task(self.handle_service_response(future, f"切換模式 {mode} {drone_id}"))
def handle_arm(self, drone_id):
loop = asyncio.get_event_loop()
arm_state = not self.monitor.get_arm_state(drone_id) # Toggle arm state
@ -1202,6 +1250,30 @@ class ControlStationUI(QMainWindow):
heading = self.drone_headings[drone_id]
self.pending_map_updates[drone_id] = (lat, lon, heading)
# 新增處理分組勾選的方法
def handle_group_selection(self, socket_id, state):
"""處理 socket 分組勾選狀態變化"""
# 獲取該分組下的所有無人機
group_drones = [did for did in self.drones.keys()
if self.get_socket_id(did) == socket_id]
# 根據分組勾選狀態更新所有該分組的無人機勾選狀態
is_checked = state == Qt.CheckState.Checked.value
for drone_id in group_drones:
checkbox = self.drones[drone_id].findChild(QCheckBox, f"{drone_id}_checkbox")
if checkbox:
# 暫時斷開信號連接,避免遞迴觸發
checkbox.blockSignals(True)
checkbox.setChecked(is_checked)
checkbox.blockSignals(False)
# 手動更新選中集合
if is_checked:
self.monitor.selected_drones.add(drone_id)
else:
self.monitor.selected_drones.discard(drone_id)
def handle_drone_selection(self, drone_id, state):
"""處理個別無人機勾選狀態變化"""
if state == Qt.CheckState.Checked.value:
@ -1209,8 +1281,46 @@ class ControlStationUI(QMainWindow):
else:
self.monitor.selected_drones.discard(drone_id)
# 更新對應 socket 分組的勾選狀態
socket_id = self.get_socket_id(drone_id)
self.update_group_checkbox_state(socket_id)
# 新增更新分組勾選框狀態的方法
def update_group_checkbox_state(self, socket_id):
"""更新指定 socket 分組的勾選框狀態"""
# 獲取該分組下的所有無人機
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].findChild(QCheckBox, f"{did}_checkbox").isChecked())
# 獲取分組勾選框
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[drone_id].findChild(QCheckBox, f"{drone_id}_checkbox").isChecked()
@ -1220,12 +1330,20 @@ class ControlStationUI(QMainWindow):
# 如果全部已選中,則取消全選;否則全選
new_state = not all_selected
# 更新所有勾選框狀態
# 更新所有勾選框狀態(無人機和分組)
for drone_id in self.drones:
checkbox = self.drones[drone_id].findChild(QCheckBox, f"{drone_id}_checkbox")
if checkbox:
checkbox.setChecked(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_arm_selected(self):
"""處理批次解鎖"""
loop = asyncio.get_event_loop()
@ -1240,6 +1358,19 @@ class ControlStationUI(QMainWindow):
future = self.monitor.takeoff_drone(drone_id, 10.0)
loop.create_task(self.handle_service_response(future, f"批次起飛 {drone_id}"))
def handle_batch_mode_change(self):
mode = self.mode_combo.currentText()
loop = asyncio.get_event_loop()
for drone_id in self.monitor.selected_drones:
future = self.monitor.set_mode(drone_id, mode)
loop.create_task(self.handle_service_response(future, f"{drone_id} 切換模式 {mode}"))
def handle_single_mode_change(self, drone_id):
mode = self.mode_combo.currentText()
loop = asyncio.get_event_loop()
future = self.monitor.set_mode(drone_id, mode)
loop.create_task(self.handle_service_response(future, f"{drone_id} 切換模式 {mode}"))
def update_field(self, panel, drone_id, field, text, color=None):
"""Update a specific field in the panel - 添加變更檢查"""
if label := panel.findChild(QLabel, f"{drone_id}_{field}"):
@ -1315,6 +1446,9 @@ class ControlStationUI(QMainWindow):
# 重新排序並顯示所有 socket 分組
self.reorganize_socket_groups()
# 更新分組勾選框狀態
self.update_group_checkbox_state(socket_id)
# 更新總覽表
self.update_overview_table()

Loading…
Cancel
Save