drone_manager.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. import os
  2. import cv2
  3. import numpy as np
  4. import random
  5. from datetime import datetime
  6. from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
  7. QPushButton, QComboBox, QToolBar, QAction,
  8. QGridLayout, QFrame, QSplitter, QFileDialog,
  9. QTableWidget, QTableWidgetItem, QHeaderView,
  10. QAbstractItemView, QGroupBox, QTabWidget)
  11. from PyQt5.QtCore import Qt, QTimer, pyqtSlot, pyqtSignal, QSize, QRect, QThread
  12. from PyQt5.QtGui import QImage, QPixmap, QIcon, QPainter, QPen, QColor, QFont, QBrush
  13. class DroneSimulator(QThread):
  14. """无人机模拟器,用于模拟无人机状态和视频流"""
  15. update_frame = pyqtSignal(int, np.ndarray) # 发送无人机ID和视频帧
  16. update_status = pyqtSignal(int, dict) # 发送无人机ID和状态信息
  17. update_detection = pyqtSignal(int, list) # 发送无人机ID和检测结果
  18. def __init__(self, drone_id, drone_type="DJI Mavic Air 2"):
  19. super().__init__()
  20. self.drone_id = drone_id
  21. self.drone_type = drone_type
  22. self.running = False
  23. self.battery = 100
  24. self.altitude = 120 # 初始高度,米
  25. self.speed = 0
  26. self.gps = {"lat": 39.916527 + random.uniform(-0.01, 0.01),
  27. "lng": 116.397128 + random.uniform(-0.01, 0.01)}
  28. self.signal = 95
  29. self.status = "待命"
  30. # 选择一个示例视频作为无人机视频源
  31. self.video_files = [
  32. "resources/videos/drone_forest_1.mp4",
  33. "resources/videos/drone_forest_2.mp4",
  34. "resources/videos/drone_forest_3.mp4"
  35. ]
  36. # 使用无人机ID作为随机种子选择视频源,确保每个无人机有不同的视频
  37. random.seed(drone_id)
  38. self.video_source = "resources/videos/forest_fire.mp4" # 默认使用一个通用视频
  39. # 初始化帧计数
  40. self.frame_count = 0
  41. def run(self):
  42. """运行无人机模拟器"""
  43. self.running = True
  44. cap = cv2.VideoCapture(self.video_source)
  45. if not cap.isOpened():
  46. # 无法打开视频,使用生成的图像
  47. print(f"无法打开视频源,使用生成图像: {self.video_source}")
  48. while self.running:
  49. # 生成模拟画面
  50. frame = np.zeros((480, 640, 3), dtype=np.uint8)
  51. # 添加一些背景
  52. frame[:] = (30, 50, 30) # 深绿色背景
  53. # 添加一些文字
  54. text = f"无人机 #{self.drone_id} - 信号丢失"
  55. cv2.putText(frame, text, (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
  56. # 添加当前状态信息
  57. info_text = f"电池: {self.battery}% | 高度: {self.altitude}m | 信号: {self.signal}%"
  58. cv2.putText(frame, info_text, (50, 280), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (200, 200, 200), 1)
  59. # 更新无人机状态
  60. self.update_drone_status()
  61. # 发送帧和状态
  62. self.update_frame.emit(self.drone_id, frame)
  63. self.update_status.emit(self.drone_id, self.get_status())
  64. # 控制帧率
  65. self.msleep(100)
  66. else:
  67. while self.running:
  68. ret, frame = cap.read()
  69. if not ret:
  70. # 视频结束,从头开始
  71. cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
  72. continue
  73. # 添加无人机信息叠加层
  74. frame = self.add_drone_info(frame)
  75. # 每隔一段时间生成模拟检测结果
  76. self.frame_count += 1
  77. if self.frame_count % 30 == 0: # 每30帧生成一次检测结果
  78. detections = self.generate_mock_detection(frame)
  79. self.update_detection.emit(self.drone_id, detections)
  80. # 将检测结果绘制在画面上
  81. frame = self.draw_detections(frame, detections)
  82. # 更新无人机状态
  83. self.update_drone_status()
  84. # 发送帧和状态
  85. self.update_frame.emit(self.drone_id, frame)
  86. self.update_status.emit(self.drone_id, self.get_status())
  87. # 控制帧率
  88. self.msleep(50)
  89. cap.release()
  90. def stop(self):
  91. """停止无人机模拟器"""
  92. self.running = False
  93. self.wait()
  94. def update_drone_status(self):
  95. """更新无人机状态"""
  96. # 模拟电池消耗
  97. self.battery = max(0, self.battery - random.uniform(0.01, 0.05))
  98. # 模拟高度变化
  99. if random.random() < 0.3:
  100. self.altitude += random.uniform(-1, 1)
  101. self.altitude = max(30, min(200, self.altitude))
  102. # 模拟速度变化
  103. self.speed = random.uniform(0, 8)
  104. # 模拟GPS位置变化
  105. self.gps["lat"] += random.uniform(-0.0001, 0.0001)
  106. self.gps["lng"] += random.uniform(-0.0001, 0.0001)
  107. # 模拟信号强度变化
  108. if random.random() < 0.2:
  109. self.signal += random.uniform(-2, 1)
  110. self.signal = max(60, min(100, self.signal))
  111. def get_status(self):
  112. """获取无人机状态信息"""
  113. return {
  114. "id": self.drone_id,
  115. "type": self.drone_type,
  116. "battery": self.battery,
  117. "altitude": self.altitude,
  118. "speed": self.speed,
  119. "gps": self.gps,
  120. "signal": self.signal,
  121. "status": self.status,
  122. "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  123. }
  124. def add_drone_info(self, frame):
  125. """在视频帧上添加无人机信息"""
  126. height, width = frame.shape[:2]
  127. # 添加半透明的顶部信息栏
  128. overlay = frame.copy()
  129. cv2.rectangle(overlay, (0, 0), (width, 40), (0, 0, 0), -1)
  130. cv2.addWeighted(overlay, 0.7, frame, 0.3, 0, frame, 0)
  131. # 添加无人机ID和类型
  132. cv2.putText(frame, f"无人机 #{self.drone_id} | {self.drone_type}",
  133. (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)
  134. # 添加电池和GPS信息
  135. battery_text = f"电池: {int(self.battery)}%"
  136. battery_color = (0, 255, 0) if self.battery > 30 else (0, 165, 255) if self.battery > 15 else (0, 0, 255)
  137. cv2.putText(frame, battery_text, (width - 300, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, battery_color, 1)
  138. gps_text = f"GPS: {self.gps['lat']:.4f}, {self.gps['lng']:.4f}"
  139. cv2.putText(frame, gps_text, (width - 180, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1)
  140. # 添加时间戳
  141. timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  142. cv2.putText(frame, timestamp, (width - 180, height - 10),
  143. cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
  144. # 添加高度和速度信息
  145. altitude_text = f"高度: {int(self.altitude)}m"
  146. cv2.putText(frame, altitude_text, (10, height - 30),
  147. cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
  148. speed_text = f"速度: {self.speed:.1f}m/s"
  149. cv2.putText(frame, speed_text, (10, height - 10),
  150. cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
  151. return frame
  152. def generate_mock_detection(self, frame):
  153. """生成模拟检测结果"""
  154. height, width = frame.shape[:2]
  155. # 随机决定是否生成检测结果
  156. if random.random() < 0.3: # 30%的概率生成检测
  157. detection_type = random.choice(['fire', 'animal', 'landslide', 'pest'])
  158. # 根据检测类型设置标签
  159. label_map = {
  160. 'fire': '火灾',
  161. 'animal': '野生动物',
  162. 'landslide': '滑坡',
  163. 'pest': '病虫害'
  164. }
  165. # 对于病虫害类型,随机选择具体的病虫害种类
  166. pest_types = ['松毛虫', '美国白蛾', '落叶松毛虫', '杨树食叶害虫', '松材线虫病']
  167. pest_subtypes = ['轻度', '中度', '重度']
  168. # 随机位置
  169. x1 = random.randint(50, width - 150)
  170. y1 = random.randint(50, height - 150)
  171. w = random.randint(50, 150)
  172. h = random.randint(50, 150)
  173. x2 = x1 + w
  174. y2 = y1 + h
  175. # 随机置信度
  176. confidence = random.uniform(0.65, 0.95)
  177. # 如果是病虫害类型,创建更详细的标签
  178. if detection_type == 'pest':
  179. pest_type = random.choice(pest_types)
  180. pest_subtype = random.choice(pest_subtypes)
  181. return [{
  182. 'task': detection_type,
  183. 'class': 0,
  184. 'label': f"{label_map[detection_type]}-{pest_type}({pest_subtype})",
  185. 'confidence': confidence,
  186. 'bbox': [x1, y1, x2, y2],
  187. 'subtype': pest_type,
  188. 'severity': pest_subtype
  189. }]
  190. else:
  191. return [{
  192. 'task': detection_type,
  193. 'class': 0,
  194. 'label': label_map[detection_type],
  195. 'confidence': confidence,
  196. 'bbox': [x1, y1, x2, y2]
  197. }]
  198. else:
  199. return [] # 没有检测结果
  200. def draw_detections(self, frame, detections):
  201. """在帧上绘制检测结果"""
  202. for det in detections:
  203. # 获取边界框和标签
  204. x1, y1, x2, y2 = [int(c) for c in det['bbox']]
  205. label = f"{det['label']} {det['confidence']:.2f}"
  206. # 根据任务选择颜色
  207. if det['task'] == 'fire':
  208. color = (0, 0, 255) # 红色
  209. elif det['task'] == 'animal':
  210. color = (0, 255, 0) # 绿色
  211. elif det['task'] == 'landslide':
  212. color = (255, 0, 0) # 蓝色
  213. elif det['task'] == 'pest':
  214. color = (128, 0, 128) # 紫色
  215. else:
  216. color = (255, 255, 0) # 青色
  217. # 绘制边界框
  218. cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
  219. # 绘制标签背景
  220. text_size, _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)
  221. cv2.rectangle(frame, (x1, y1 - text_size[1] - 5), (x1 + text_size[0], y1), color, -1)
  222. # 绘制标签
  223. cv2.putText(frame, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
  224. # 如果是病虫害,绘制额外信息
  225. if det['task'] == 'pest' and 'severity' in det:
  226. # 在框的上方显示严重程度
  227. severity_text = f"严重程度: {det['severity']}"
  228. cv2.putText(frame, severity_text, (x1, y1 - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
  229. return frame
  230. class DroneManager(QWidget):
  231. """无人机管理组件,用于管理多个无人机,显示视频流和状态"""
  232. def __init__(self, config):
  233. super().__init__()
  234. self.config = config
  235. self.drones = {} # 存储无人机模拟器,key为无人机ID
  236. self.drone_frames = {} # 存储无人机视频帧
  237. self.drone_status = {} # 存储无人机状态
  238. self.drone_detections = {} # 存储无人机检测结果
  239. self.init_ui()
  240. # 创建自动更新定时器
  241. self.timer = QTimer(self)
  242. self.timer.timeout.connect(self.update_drone_display)
  243. self.timer.start(200) # 每200毫秒更新一次显示
  244. def init_ui(self):
  245. """初始化UI"""
  246. # 创建主布局
  247. main_layout = QVBoxLayout(self)
  248. main_layout.setContentsMargins(0, 0, 0, 0)
  249. # 创建工具栏
  250. toolbar_layout = QHBoxLayout()
  251. # 添加标题
  252. title_label = QLabel("无人机管理")
  253. title_label.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
  254. title_label.setStyleSheet("color: white; margin: 5px;")
  255. toolbar_layout.addWidget(title_label)
  256. # 添加空白占位
  257. toolbar_layout.addStretch(1)
  258. # 添加无人机类型选择
  259. self.drone_type_combo = QComboBox()
  260. self.drone_type_combo.addItem("DJI Mavic Air 2")
  261. self.drone_type_combo.addItem("DJI Phantom 4")
  262. self.drone_type_combo.addItem("DJI Inspire 2")
  263. self.drone_type_combo.addItem("Autel EVO II")
  264. toolbar_layout.addWidget(QLabel("无人机类型:"))
  265. toolbar_layout.addWidget(self.drone_type_combo)
  266. # 添加添加无人机按钮
  267. self.add_drone_btn = QPushButton("添加无人机")
  268. self.add_drone_btn.setIcon(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'add.png')))
  269. self.add_drone_btn.clicked.connect(self.add_drone)
  270. toolbar_layout.addWidget(self.add_drone_btn)
  271. # 添加删除无人机按钮
  272. self.remove_drone_btn = QPushButton("删除无人机")
  273. self.remove_drone_btn.setIcon(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'remove.png')))
  274. self.remove_drone_btn.clicked.connect(self.remove_drone)
  275. toolbar_layout.addWidget(self.remove_drone_btn)
  276. main_layout.addLayout(toolbar_layout)
  277. # 创建分割器
  278. splitter = QSplitter(Qt.Vertical)
  279. main_layout.addWidget(splitter, 1)
  280. # 创建无人机视图区域 - 使用标签页
  281. self.tab_widget = QTabWidget()
  282. self.tab_widget.setTabPosition(QTabWidget.North)
  283. self.tab_widget.setStyleSheet("QTabWidget::pane { border: 0; } QTabBar::tab { background-color: #102040; color: white; padding: 6px 12px; margin-right: 2px; } QTabBar::tab:selected { background-color: #1a3a5a; }")
  284. splitter.addWidget(self.tab_widget)
  285. # 创建无人机控制面板
  286. control_panel = QWidget()
  287. control_layout = QVBoxLayout(control_panel)
  288. # 添加无人机状态表格
  289. self.status_table = QTableWidget()
  290. self.status_table.setColumnCount(9)
  291. self.status_table.setHorizontalHeaderLabels(["ID", "类型", "电池", "高度", "速度", "经度", "纬度", "信号", "状态"])
  292. self.status_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
  293. self.status_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
  294. self.status_table.setSelectionBehavior(QAbstractItemView.SelectRows)
  295. self.status_table.setAlternatingRowColors(True)
  296. self.status_table.setStyleSheet("alternate-background-color: #0c1e32; background-color: #081a2e; color: white; "
  297. "QHeaderView::section { background-color: #15253a; color: white; padding: 4px; "
  298. "border: 1px solid #1e3a5a; font-weight: bold; }")
  299. control_layout.addWidget(self.status_table)
  300. # 添加按钮栏
  301. btn_layout = QHBoxLayout()
  302. # 起飞按钮
  303. self.takeoff_btn = QPushButton("起飞")
  304. self.takeoff_btn.clicked.connect(self.takeoff_drone)
  305. btn_layout.addWidget(self.takeoff_btn)
  306. # 降落按钮
  307. self.land_btn = QPushButton("降落")
  308. self.land_btn.clicked.connect(self.land_drone)
  309. btn_layout.addWidget(self.land_btn)
  310. # 返航按钮
  311. self.return_btn = QPushButton("返航")
  312. self.return_btn.clicked.connect(self.return_drone)
  313. btn_layout.addWidget(self.return_btn)
  314. # 停止按钮
  315. self.stop_btn = QPushButton("紧急停止")
  316. self.stop_btn.setStyleSheet("background-color: #8b0000; color: white;")
  317. self.stop_btn.clicked.connect(self.emergency_stop_drone)
  318. btn_layout.addWidget(self.stop_btn)
  319. control_layout.addLayout(btn_layout)
  320. splitter.addWidget(control_panel)
  321. # 设置分割器比例
  322. splitter.setSizes([int(self.height() * 0.7), int(self.height() * 0.3)])
  323. # 添加一些初始无人机
  324. QTimer.singleShot(500, self.add_initial_drones)
  325. def add_initial_drones(self):
  326. """添加初始无人机"""
  327. for i in range(3): # 添加3个初始无人机
  328. self.add_drone()
  329. def add_drone(self):
  330. """添加一个无人机"""
  331. # 生成新的无人机ID
  332. drone_id = len(self.drones) + 1
  333. drone_type = self.drone_type_combo.currentText()
  334. # 创建无人机模拟器
  335. drone = DroneSimulator(drone_id, drone_type)
  336. drone.update_frame.connect(self.update_drone_frame)
  337. drone.update_status.connect(self.update_drone_status)
  338. drone.update_detection.connect(self.update_drone_detection)
  339. drone.start()
  340. # 存储无人机
  341. self.drones[drone_id] = drone
  342. # 创建视频显示标签页
  343. drone_tab = QWidget()
  344. tab_layout = QVBoxLayout(drone_tab)
  345. tab_layout.setContentsMargins(0, 0, 0, 0)
  346. # 创建视频帧标签
  347. frame_label = QLabel()
  348. frame_label.setAlignment(Qt.AlignCenter)
  349. frame_label.setMinimumSize(640, 480)
  350. frame_label.setStyleSheet("background-color: black;")
  351. tab_layout.addWidget(frame_label)
  352. # 添加标签页
  353. self.tab_widget.addTab(drone_tab, f"无人机 #{drone_id}")
  354. # 切换到新标签页
  355. self.tab_widget.setCurrentIndex(self.tab_widget.count() - 1)
  356. # 初始化视频帧
  357. self.drone_frames[drone_id] = frame_label
  358. # 更新状态表格
  359. self.update_status_table()
  360. def remove_drone(self):
  361. """移除选中的无人机"""
  362. # 获取当前选中的标签页
  363. current_index = self.tab_widget.currentIndex()
  364. if current_index >= 0:
  365. # 获取无人机ID
  366. drone_id = int(self.tab_widget.tabText(current_index).split("#")[1])
  367. # 停止无人机模拟器
  368. if drone_id in self.drones:
  369. self.drones[drone_id].stop()
  370. del self.drones[drone_id]
  371. # 移除视频帧
  372. if drone_id in self.drone_frames:
  373. del self.drone_frames[drone_id]
  374. # 移除状态
  375. if drone_id in self.drone_status:
  376. del self.drone_status[drone_id]
  377. # 移除检测结果
  378. if drone_id in self.drone_detections:
  379. del self.drone_detections[drone_id]
  380. # 移除标签页
  381. self.tab_widget.removeTab(current_index)
  382. # 更新状态表格
  383. self.update_status_table()
  384. def update_drone_frame(self, drone_id, frame):
  385. """更新无人机视频帧"""
  386. if drone_id in self.drone_frames:
  387. # 转换为QImage并显示
  388. height, width, channels = frame.shape
  389. bytes_per_line = channels * width
  390. q_image = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped()
  391. self.drone_frames[drone_id].setPixmap(QPixmap.fromImage(q_image).scaled(
  392. self.drone_frames[drone_id].width(),
  393. self.drone_frames[drone_id].height(),
  394. Qt.KeepAspectRatio,
  395. Qt.SmoothTransformation
  396. ))
  397. def update_drone_status(self, drone_id, status):
  398. """更新无人机状态"""
  399. self.drone_status[drone_id] = status
  400. def update_drone_detection(self, drone_id, detections):
  401. """更新无人机检测结果"""
  402. self.drone_detections[drone_id] = detections
  403. # 如果有检测结果,可以向主窗口发送告警
  404. if detections and hasattr(self.parent(), 'alert_panel'):
  405. for det in detections:
  406. # 构造告警信息
  407. alert = {
  408. 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  409. 'type': det['task'],
  410. 'location': f"无人机 #{drone_id} 位置",
  411. 'detail': f"检测到{det['label']},置信度: {det['confidence']:.2f}",
  412. 'level': 'high' if det['confidence'] > 0.85 else 'medium'
  413. }
  414. # 向告警面板添加告警
  415. self.parent().alert_panel.add_alert(alert)
  416. def update_drone_display(self):
  417. """更新无人机显示和状态表格"""
  418. self.update_status_table()
  419. def update_status_table(self):
  420. """更新状态表格"""
  421. self.status_table.setRowCount(0)
  422. for drone_id, status in self.drone_status.items():
  423. row = self.status_table.rowCount()
  424. self.status_table.insertRow(row)
  425. # 设置ID
  426. self.status_table.setItem(row, 0, QTableWidgetItem(str(drone_id)))
  427. # 设置类型
  428. self.status_table.setItem(row, 1, QTableWidgetItem(status['type']))
  429. # 设置电池
  430. battery_item = QTableWidgetItem(f"{int(status['battery'])}%")
  431. if status['battery'] > 30:
  432. battery_item.setForeground(QBrush(QColor(0, 255, 0)))
  433. elif status['battery'] > 15:
  434. battery_item.setForeground(QBrush(QColor(255, 165, 0)))
  435. else:
  436. battery_item.setForeground(QBrush(QColor(255, 0, 0)))
  437. self.status_table.setItem(row, 2, battery_item)
  438. # 设置高度
  439. self.status_table.setItem(row, 3, QTableWidgetItem(f"{int(status['altitude'])}m"))
  440. # 设置速度
  441. self.status_table.setItem(row, 4, QTableWidgetItem(f"{status['speed']:.1f}m/s"))
  442. # 设置经度
  443. self.status_table.setItem(row, 5, QTableWidgetItem(f"{status['gps']['lng']:.6f}"))
  444. # 设置纬度
  445. self.status_table.setItem(row, 6, QTableWidgetItem(f"{status['gps']['lat']:.6f}"))
  446. # 设置信号
  447. signal_item = QTableWidgetItem(f"{int(status['signal'])}%")
  448. if status['signal'] > 80:
  449. signal_item.setForeground(QBrush(QColor(0, 255, 0)))
  450. elif status['signal'] > 60:
  451. signal_item.setForeground(QBrush(QColor(255, 165, 0)))
  452. else:
  453. signal_item.setForeground(QBrush(QColor(255, 0, 0)))
  454. self.status_table.setItem(row, 7, signal_item)
  455. # 设置状态
  456. self.status_table.setItem(row, 8, QTableWidgetItem(status['status']))
  457. def takeoff_drone(self):
  458. """起飞选中的无人机"""
  459. selected_rows = self.status_table.selectionModel().selectedRows()
  460. for index in selected_rows:
  461. drone_id = int(self.status_table.item(index.row(), 0).text())
  462. if drone_id in self.drones:
  463. self.drones[drone_id].status = "已起飞"
  464. def land_drone(self):
  465. """降落选中的无人机"""
  466. selected_rows = self.status_table.selectionModel().selectedRows()
  467. for index in selected_rows:
  468. drone_id = int(self.status_table.item(index.row(), 0).text())
  469. if drone_id in self.drones:
  470. self.drones[drone_id].status = "正在降落"
  471. def return_drone(self):
  472. """返航选中的无人机"""
  473. selected_rows = self.status_table.selectionModel().selectedRows()
  474. for index in selected_rows:
  475. drone_id = int(self.status_table.item(index.row(), 0).text())
  476. if drone_id in self.drones:
  477. self.drones[drone_id].status = "返航中"
  478. def emergency_stop_drone(self):
  479. """紧急停止选中的无人机"""
  480. selected_rows = self.status_table.selectionModel().selectedRows()
  481. for index in selected_rows:
  482. drone_id = int(self.status_table.item(index.row(), 0).text())
  483. if drone_id in self.drones:
  484. self.drones[drone_id].status = "紧急停止"
  485. def closeEvent(self, event):
  486. """窗口关闭时停止所有无人机"""
  487. for drone in self.drones.values():
  488. drone.stop()
  489. event.accept()