Files
paparazzi/sw/supervision/python/paparazzicenter.py
Fabien-B cde6ad8471
Some checks failed
Issues due date / Add labels to issues (push) Has been cancelled
Doxygen / build (push) Has been cancelled
[supervision] improve start time (#3559)
---------

Co-authored-by: Fabien-B <fabien.b@github.com>
2025-11-07 09:09:20 +01:00

326 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (C) 2008-2022 The Paparazzi Team
# released under GNU GPLv2 or later. See COPYING file.
import os
import sys
import utils
lib_path = os.path.normpath(os.path.join(utils.PAPARAZZI_SRC, 'sw', 'lib', 'python'))
sys.path.append(lib_path)
import signal
import copy
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from lxml import etree as ET
from conf import Conf, Aircraft, ConfError
from app_settings import AppSettings
from generated.ui_supervision_window import Ui_SupervisionWindow
from generated.ui_new_ac_dialog import Ui_NewACDialog
from program_widget import TabProgramsState
dirname = os.path.dirname(os.path.abspath(__file__))
TAB_ICONS = {TabProgramsState.IDLE: QtGui.QIcon(),
TabProgramsState.RUNNING: QtGui.QIcon(":/icons/icons/running.svg"),
TabProgramsState.ERROR: QtGui.QIcon(":/icons/icons/error.svg")}
class PprzCenter(QMainWindow, Ui_SupervisionWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent=parent)
self.setupUi(self)
self.conf: Conf = None
self.currentAc: Aircraft = None
icon = QtGui.QIcon(os.path.join(utils.PAPARAZZI_HOME, "data", "pictures", "penguin_logo.svg"))
self.setWindowIcon(icon)
self.settings_action.triggered.connect(self.edit_settings)
self.about_action.triggered.connect(lambda: QMessageBox.about(self, "About Paparazzi", utils.ABOUT_TEXT))
self.status_msg = QLabel()
self.statusBar().addWidget(self.status_msg)
self.fill_status_bar()
self.header.set_changed.connect(self.handle_set_changed)
self.header.ac_changed.connect(self.handle_ac_changed)
self.header.ac_edited.connect(self.handle_ac_edited)
self.header.ac_rename.connect(self.handle_rename_ac)
self.header.ac_delete.connect(self.handle_remove_ac)
self.header.ac_duplicate.connect(self.handle_new_ac)
self.header.ac_save.connect(lambda _: self.conf.save())
self.header.ac_new.connect(self.handle_new_ac)
self.configuration_panel.build_widget.refresh_ac.connect(self.handle_ac_edited)
self.configuration_panel.program_state_changed.connect(lambda state: self.programs_state_changed(state, 0))
self.operation_panel.session.program_state_changed.connect(lambda state: self.programs_state_changed(state, 1))
self.operation_panel.session.program_spawned.connect(self.header.disable_sets)
self.operation_panel.session.programs_all_stopped.connect(self.header.enable_sets)
self.operation_panel.session.tools_changed.connect(self.configuration_panel.handle_tools_changed)
self.configuration_panel.splitter.splitterMoved.connect(self.update_left_pane_width)
settings = utils.get_settings()
window_size = settings.value("ui/window_size", QtCore.QSize(1000, 600), QtCore.QSize)
self.resize(window_size)
self.configuration_panel.init()
self.operation_panel.session.init()
QtCore.QTimer.singleShot(100, self.header.update_sets)
def programs_state_changed(self, state: TabProgramsState, tab_index):
self.tabwidget.setTabIcon(tab_index, TAB_ICONS[state])
def handle_set_changed(self, conf_file):
self.conf = Conf(conf_file)
Conf.set_current_conf(conf_file)
self.configuration_panel.build_widget.set_conf(self.conf)
self.log_widget.set_conf(self.conf)
acs = [ac.name for ac in self.conf.aircrafts]
self.header.set_acs(acs)
# set last AC as current if it exits in the current conf
settings = utils.get_settings()
last_ac: QtCore.QVariant = settings.value("ui/last_AC", None, str)
if last_ac in acs:
self.handle_ac_changed(last_ac)
last_target: QtCore.QVariant = settings.value("ui/last_target", None, str)
if last_target:
self.configuration_panel.build_widget.target_combo.setCurrentText(last_target)
def handle_ac_edited(self, ac: Aircraft):
# check AC ID
if len(self.conf.get_all(ac.ac_id)) > 1:
self.header.id_spinBox.setStyleSheet("background-color: red;")
else:
self.header.id_spinBox.setStyleSheet("background-color: white;")
# update ac, then update all widgets
try:
ac.update()
self.clear_error()
except ConfError as e:
self.handle_error(e.__str__())
self.change_ac(ac)
def handle_ac_changed(self, ac_name):
ac = self.conf[ac_name]
if ac is not None:
# self.handle_ac_edited(ac) # update AC ?
try:
ac.update_targets()
self.clear_error()
except ConfError as e:
self.handle_error(e.__str__())
self.change_ac(ac)
def handle_remove_ac(self, ac: Aircraft):
button = QMessageBox.question(self, "Remove AC", "Remove AC <strong>{}</strong>?".format(ac.name))
if button == QMessageBox.Yes:
self.conf.remove(ac)
self.header.remove_ac(ac)
def handle_new_ac(self, orig: Aircraft = None):
if orig is None:
orig = Aircraft()
ui_dialog = Ui_NewACDialog()
dialog = QDialog(parent=self)
ui_dialog.setupUi(dialog)
def verify():
ok = True
id = ui_dialog.id_spinbox.value()
name = ui_dialog.name_edit.text()
if self.conf[id] is not None or id == 0:
ui_dialog.id_spinbox.setStyleSheet("background-color: red;")
ok = False
else:
ui_dialog.id_spinbox.setStyleSheet("")
if self.conf[name] is not None or name == "":
ui_dialog.name_edit.setStyleSheet("background-color: red;")
ok = False
else:
ui_dialog.name_edit.setStyleSheet("")
return ok
def accept():
if verify():
dialog.accept()
def reject():
dialog.reject()
def duplicate(result):
if result:
new_ac = copy.deepcopy(orig)
name = ui_dialog.name_edit.text()
ac_id = ui_dialog.id_spinbox.value()
new_ac.name = name
new_ac.ac_id = ac_id
self.conf.append(new_ac)
self.header.add_ac(new_ac)
self.change_ac(new_ac)
ui_dialog.id_spinbox.setValue(self.conf.get_free_id())
ui_dialog.buttonBox.accepted.connect(accept)
ui_dialog.buttonBox.rejected.connect(reject)
ui_dialog.id_spinbox.valueChanged.connect(verify)
ui_dialog.name_edit.textChanged.connect(verify)
dialog.finished.connect(duplicate)
dialog.open()
def change_ac(self, ac):
self.currentAc = ac
self.header.set_ac(ac)
self.configuration_panel.set_ac(ac)
self.operation_panel.session.set_aircraft(ac)
self.doc_panel.set_aircraft(ac)
def handle_rename_ac(self, orig: Aircraft):
ui_dialog = Ui_NewACDialog()
dialog = QDialog(parent=self)
ui_dialog.setupUi(dialog)
ui_dialog.name_edit.setText(orig.name)
ui_dialog.id_spinbox.setValue(orig.ac_id)
def verify():
ok = True
id = ui_dialog.id_spinbox.value()
name = ui_dialog.name_edit.text()
acs_name = self.conf.get_all(name)
if len(acs_name) > 1 or (len(acs_name) == 1 and acs_name[0] != orig):
ui_dialog.name_edit.setStyleSheet("background-color: red;")
ok = False
else:
ui_dialog.name_edit.setStyleSheet("")
acs_id = self.conf.get_all(id)
if len(acs_id) > 1 or (len(acs_id) == 1 and acs_id[0] != orig):
ui_dialog.id_spinbox.setStyleSheet("background-color: red;")
ok = False
else:
ui_dialog.id_spinbox.setStyleSheet("")
return ok
def accept():
if verify():
dialog.accept()
def reject():
dialog.reject()
def rename(result):
if result:
orig.name = ui_dialog.name_edit.text()
orig.ac_id = ui_dialog.id_spinbox.value()
self.header.rename_ac(orig.name)
ui_dialog.buttonBox.accepted.connect(accept)
ui_dialog.buttonBox.rejected.connect(reject)
ui_dialog.id_spinbox.valueChanged.connect(verify)
ui_dialog.name_edit.textChanged.connect(verify)
dialog.finished.connect(rename)
dialog.open()
def edit_settings(self):
settings_dialog = AppSettings(self)
settings_dialog.show()
def quit(self, interactive=True):
self.magviewer_widget.stop()
quit_accepted = True
if self.operation_panel.session.any_program_running():
quit_accepted = False
self.operation_panel.session.programs_all_stopped.connect(self.close)
self.operation_panel.session.stop_all()
self.operation_panel.session.programs_all_stopped.connect(self.close)
else:
restore_conf = True
if utils.get_settings().value("always_keep_changes", False, bool):
restore_conf = False
else:
conf_tree_orig = self.conf.tree_orig
conf_tree = self.conf.to_xml_tree()
if ET.tostring(conf_tree) != ET.tostring(conf_tree_orig) and interactive:
buttons = QMessageBox.question(self, "Save configuration?",
"The configuration has changed, do you want to save it?")
if buttons == QMessageBox.Yes:
restore_conf = False
if restore_conf:
self.conf.restore_conf()
self.conf.save()
self.save_gconf()
return quit_accepted
def closeEvent(self, e: QtGui.QCloseEvent) -> None:
if self.quit():
e.accept()
else:
e.ignore()
def save_gconf(self):
settings = utils.get_settings()
settings.setValue("ui/window_size", self.size())
settings.setValue("ui/last_AC", self.currentAc.name)
settings.setValue("ui/last_session", self.operation_panel.session.get_current_session())
settings.setValue("ui/last_control_panel", self.operation_panel.session.get_current_control_panel())
def update_left_pane_width(self, pos, index):
utils.get_settings().setValue("ui/left_pane_width", pos)
def fill_status_bar(self):
home_widget = QWidget()
home_lay = QHBoxLayout(home_widget)
home_lay.addWidget(QLabel("HOME: ", home_widget))
home_button = QPushButton(utils.PAPARAZZI_HOME, home_widget)
home_lay.addWidget(home_button)
home_button.clicked.connect(lambda: utils.open_terminal(utils.PAPARAZZI_HOME))
self.statusBar().addPermanentWidget(home_widget)
self.statusBar().addPermanentWidget(utils.make_line(None, True))
label_version = QLabel("Version={}".format(utils.get_version()))
self.statusBar().addPermanentWidget(label_version)
self.statusBar().addPermanentWidget(utils.make_line(None, True))
label_build = QLabel("Build={}".format(utils.get_build_version()))
self.statusBar().addPermanentWidget(label_build)
def handle_error(self, msg):
self.status_msg.setText(msg)
self.statusBar().setStyleSheet("background-color: red;")
# self.statusBar().showMessage(msg)
def clear_error(self):
self.status_msg.setText("")
self.statusBar().setStyleSheet("")
if __name__ == "__main__":
# argument needed for the webviewer to work (documentation)
# https://stackoverflow.com/questions/75922410/pyqt5-qwebengineview-blank-window
app = QApplication(sys.argv + ["--disable-seccomp-filter-sandbox"])
timer = QtCore.QTimer()
timer.start(100)
timer.timeout.connect(lambda: None) # Let the interpreter run each 100 ms.
main_window = PprzCenter()
main_window.show()
# qApp.aboutToQuit.connect(main_window.quit)
def sigint_handler(*args):
"""Handler for the SIGINT signal."""
print("catched SIGINT")
sys.stderr.write('\r')
if main_window.quit(False):
QApplication.quit()
signal.signal(signal.SIGINT, sigint_handler)
sys.exit(app.exec_())