Skip to content

Commit

Permalink
[supervision] Adds documentation tab. (paparazzi#2972)
Browse files Browse the repository at this point in the history
* [supervision] Adds documentation tab.

* [generator] add a target option to dump modules by target with load type

* [supervision] doc: handle modules type.

* [supervision] modules doc: add filter.

* [supervision] Use internet doc by default.

* [supervision] Move the header out of the configuration tab.

* [supervision] Add target label.

* [supervision] Gracefully handle Ctrl-C.

* [supervision] Add new session to combobox.

* Update sw/supervision/python/doc_panel.py

---------

Co-authored-by: Gautier Hattenberger <gautier.hattenberger@enac.fr>
  • Loading branch information
Fabien-B and gautierhattenberger committed Feb 7, 2023
1 parent 902f9ad commit 8833bb4
Show file tree
Hide file tree
Showing 38 changed files with 1,025 additions and 372 deletions.
38 changes: 22 additions & 16 deletions sw/supervision/python/Makefile
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
# Copyright (C) 2008-2022 The Paparazzi Team
# released under GNU GPLv2 or later. See COPYING file.
all:
mkdir -p generated
pyuic5 ui/conf_header.ui -o generated/ui_conf_header.py
pyuic5 ui/conf_item.ui -o generated/ui_conf_item.py
pyuic5 ui/build.ui -o generated/ui_build.py
pyuic5 ui/configuration_panel.ui -o generated/ui_configuration_panel.py
pyuic5 ui/new_ac_dialog.ui -o generated/ui_new_ac_dialog.py
pyuic5 ui/session.ui -o generated/ui_session.py
pyuic5 ui/program.ui -o generated/ui_program.py
pyuic5 ui/operation_panel.ui -o generated/ui_operation_panel.py
pyuic5 ui/console.ui -o generated/ui_console.py
pyuic5 ui/tools_list.ui -o generated/ui_tools_list.py
pyuic5 ui/app_settings.ui -o generated/ui_app_settings.py
pyuic5 ui/conf_settings.ui -o generated/ui_conf_settings.py

clean_ui:

SOURCEDIR = ui
BUILDDIR = generated

SOURCES = $(wildcard $(SOURCEDIR)/*.ui)
OBJECTS = $(patsubst $(SOURCEDIR)/%.ui,$(BUILDDIR)/%.py,$(SOURCES))

CC = pyuic5

all: $(BUILDDIR) $(OBJECTS)


$(OBJECTS): $(BUILDDIR)/%.py : $(SOURCEDIR)/%.ui
$(CC) -o $@ $<;

$(BUILDDIR):
mkdir -p $@

clean:
rm -r generated


.PHONY: build_dir
10 changes: 4 additions & 6 deletions sw/supervision/python/conf.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Copyright (C) 2008-2022 The Paparazzi Team
# released under GNU GPLv2 or later. See COPYING file.
from dataclasses import dataclass, field
from typing import List, Dict
import lxml.etree as ET
Expand Down Expand Up @@ -65,11 +67,6 @@ def update_settings(self):
completed = subprocess.run([MOD_DEP, "-ac", self.name, "-af", self.airframe, "-fp", self.flight_plan],
capture_output=True)
if completed.returncode == 0:
def remove_prefix(s):
if s.startswith(utils.CONF_DIR):
return s[len(utils.CONF_DIR):]
else:
return s

def make_setting(m):
setting = Setting(m, True)
Expand All @@ -80,7 +77,7 @@ def make_setting(m):

new_settings_modules = []
for module_path in completed.stdout.decode().strip().split():
module = remove_prefix(module_path)
module = utils.remove_prefix(module_path, utils.CONF_DIR)
xml = ET.parse(module_path)
for xml_setting in xml.getroot().findall("settings"):
name = xml_setting.get("name")
Expand Down Expand Up @@ -215,6 +212,7 @@ def save(self, refresh_orig=True):
self.tree_orig = self.to_xml_tree()

def restore_conf(self):
print("conf restored.")
self.parse(self.tree_orig)

def to_string(self):
Expand Down
2 changes: 1 addition & 1 deletion sw/supervision/python/conf_settings_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def open_setting(self):
base_settings_path = os.path.join(utils.CONF_DIR, "settings")
filenames, _ = QFileDialog.getOpenFileNames(self, "Add settings", base_settings_path, "Settings (*.xml)")
for filename in filenames:
name = utils.removeprefix(filename, utils.CONF_DIR)
name = utils.remove_prefix(filename, utils.CONF_DIR)
print(name)
item = QListWidgetItem(name)
item.setCheckState(QtCore.Qt.Checked)
Expand Down
224 changes: 15 additions & 209 deletions sw/supervision/python/configuration_panel.py
Original file line number Diff line number Diff line change
@@ -1,114 +1,44 @@
import os, sys, copy
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtWidgets
import utils
from generated.ui_configuration_panel import Ui_ConfigurationPanel
from generated.ui_new_ac_dialog import Ui_Dialog
from program_widget import ProgramWidget
from conf import *
from programs_conf import parse_tools
import subprocess

PAPARAZZI_SRC = os.getenv("PAPARAZZI_SRC")
PAPARAZZI_HOME = os.getenv("PAPARAZZI_HOME", PAPARAZZI_SRC)
lib_path = os.path.normpath(os.path.join(PAPARAZZI_SRC, 'sw', 'lib', 'python'))
sys.path.append(lib_path)
import paparazzi

# TODO make a setting ?
REMOVE_PROGRAMS_FINISHED = True


class ConfigurationPanel(QWidget, Ui_ConfigurationPanel):

msg_error = QtCore.pyqtSignal(str)
clear_error = QtCore.pyqtSignal()
ac_changed = QtCore.pyqtSignal(Aircraft)
ac_edited = QtCore.pyqtSignal(Aircraft)

def __init__(self, parent=None, *args, **kwargs):
QWidget.__init__(self, parent=parent, *args, **kwargs)
self.setupUi(self)
self.console_widget.filter_widget.hide()
self.conf = None # type: conf.Conf
self.currentAC = None # type: str
self.currentAC = None # type: Aircraft
self.flight_plan_editor = None
self.header.set_changed.connect(self.handle_set_changed)
self.header.ac_changed.connect(self.update_ac)
self.header.id_changed.connect(self.handle_id_changed)
self.header.refresh_button.clicked.connect(self.refresh_ac)
self.header.color_button.clicked.connect(self.change_color)
self.header.save_button.clicked.connect(lambda: self.conf.save())
self.addAction(self.save_conf_action)
self.save_conf_action.triggered.connect(lambda: self.conf.save())
self.conf_widget.conf_changed.connect(self.handle_conf_changed)
self.conf_widget.setting_changed.connect(self.handle_setting_changed)
self.conf_widget.flight_plan.edit_alt.connect(self.edit_flightplan_gcs)
self.header.rename_action.triggered.connect(self.rename_ac)
self.header.new_ac_action.triggered.connect(self.new_ac)
self.header.duplicate_action.triggered.connect(self.duplicate_ac)
self.header.remove_ac_action.triggered.connect(self.remove_ac)
self.build_widget.spawn_program.connect(self.launch_program)

def init(self):
sets = paparazzi.get_list_of_conf_files()
settings = utils.get_settings()
self.header.set_sets(sets, conf_init=Conf.get_current_conf())
last_ac: QtCore.QVariant = settings.value("ui/last_AC", None, str)
last_target: QtCore.QVariant = settings.value("ui/last_target", None, str)
if last_ac is not None:
self.header.ac_combo.setCurrentText(last_ac)
if last_target is not None:
self.build_widget.target_combo.setCurrentText(last_target)
window_size: QtCore.QSize = settings.value("ui/window_size", QtCore.QSize(1000, 600), QtCore.QSize)
lpw = settings.value("ui/left_pane_width", 100, int)
self.splitter.setSizes([lpw, window_size.width() - lpw])

def handle_set_changed(self, conf_file):
self.conf = Conf(conf_file)
Conf.set_current_conf(conf_file)
self.build_widget.set_conf(self.conf)
acs = [ac.name for ac in self.conf.aircrafts]
self.header.set_acs(acs)

def disable_sets(self):
self.header.set_combo.setDisabled(True)

def enable_sets(self):
self.header.set_combo.setDisabled(False)

def update_ac(self, ac_name):
ac = self.conf[ac_name]
if ac_name != "" and ac is not None:
self.conf_widget.setDisabled(False)
self.currentAC = ac_name
status, stderr = ac.update()
if status != 0:
self.msg_error.emit(stderr.decode().strip())
else:
self.clear_error.emit()
self.header.set_ac(ac)
self.conf_widget.set_ac(ac)
self.build_widget.update_targets(self.conf[ac_name])
self.ac_changed.emit(self.conf[ac_name])
else:
# self.conf_widget.reset()
def set_ac(self, ac: Aircraft):
if ac is None:
self.conf_widget.setDisabled(True)

def get_current_ac(self) -> str:
"""
:return: name of the current AC
"""
return self.currentAC

def refresh_ac(self):
self.update_ac(self.currentAC)

def handle_id_changed(self, id):
self.conf[self.currentAC].ac_id = id
if len(self.conf.get_all(id)) > 1:
self.header.id_spinBox.setStyleSheet("background-color: red;")
else:
self.header.id_spinBox.setStyleSheet("background-color: white;")
self.currentAC = ac
self.conf_widget.set_ac(ac)
self.build_widget.update_targets(ac)

def handle_setting_changed(self):
def make_setting(item: QListWidgetItem):
Expand All @@ -125,142 +55,18 @@ def make_setting(item: QListWidgetItem):
else:
settings.append(s)

self.conf[self.currentAC].settings_modules = modules
self.conf[self.currentAC].settings = settings
self.currentAC.settings_modules = modules
self.currentAC.settings = settings
self.ac_edited.emit(self.currentAC)
# should we save each time a tiny change is made ? very inefficient !
# self.conf.save()

def handle_conf_changed(self):
self.conf[self.currentAC].airframe = self.conf_widget.airframe.path
self.conf[self.currentAC].flight_plan = self.conf_widget.flight_plan.path
self.conf[self.currentAC].radio = self.conf_widget.radio.path
self.conf[self.currentAC].telemetry = self.conf_widget.telemetry.path
# reload settings modules, and update UI
self.update_ac(self.currentAC)

def add_ac(self, ac: Aircraft):
self.conf.append(ac)
self.header.add_ac(ac.name)

def new_ac(self):
orig = Aircraft()
self.create_ac(orig)

def remove_ac(self):
button = QMessageBox.question(self, "Remove AC", "Remove AC <strong>{}</strong>?".format(self.currentAC))
if button == QMessageBox.Yes:
self.conf.remove(self.conf[self.currentAC])
self.header.remove_current()

def duplicate_ac(self):
orig = self.conf[self.currentAC]
self.create_ac(orig)

def create_ac(self, orig):
ui_dialog = Ui_Dialog()
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.add_ac(new_ac)
self.header.set_current(name)

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 rename_ac(self):
orig = self.conf[self.currentAC]
ui_dialog = Ui_Dialog()
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 change_color(self):
ac = self.conf[self.currentAC]
initial = QtGui.QColor(ac.get_color())
color = QColorDialog.getColor(initial, self, "AC color")
if color.isValid():
color_name = color.name()
ac.set_color(color_name)
self.header.set_color(color_name)
self.currentAC.airframe = self.conf_widget.airframe.path
self.currentAC.flight_plan = self.conf_widget.flight_plan.path
self.currentAC.radio = self.conf_widget.radio.path
self.currentAC.telemetry = self.conf_widget.telemetry.path
self.ac_edited.emit(self.currentAC)

def edit_flightplan_gcs(self, path):
if self.flight_plan_editor is None:
Expand Down
Loading

0 comments on commit 8833bb4

Please sign in to comment.