[supervision] Add programs status. (#3014)

* [supervision] Add programs status.

* [supervision] turn program line red on error.
This commit is contained in:
Fabien-B
2023-03-23 21:18:38 +01:00
committed by GitHub
parent 767333a02c
commit de8fb8b8fa
6 changed files with 192 additions and 3 deletions
+16 -2
View File
@@ -1,8 +1,9 @@
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QProcess
import utils
from generated.ui_configuration_panel import Ui_ConfigurationPanel
from program_widget import ProgramWidget
from program_widget import ProgramWidget, TabProgramsState
from conf import *
from programs_conf import parse_tools
import subprocess
@@ -12,6 +13,7 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel):
clear_error = QtCore.pyqtSignal()
ac_edited = QtCore.pyqtSignal(Aircraft)
program_state_changed = QtCore.pyqtSignal(TabProgramsState)
def __init__(self, parent=None, *args, **kwargs):
QWidget.__init__(self, parent=parent, *args, **kwargs)
@@ -19,6 +21,7 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel):
self.console_widget.filter_widget.hide()
self.currentAC = None # type: Aircraft
self.flight_plan_editor = None
self.programs_state: TabProgramsState = TabProgramsState.IDLE
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)
@@ -84,7 +87,7 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel):
self.programs_widget.layout().addWidget(pw)
pw.ready_read_stderr.connect(lambda: self.console_widget.handle_stderr(pw))
pw.ready_read_stdout.connect(lambda: self.console_widget.handle_stdout(pw))
pw.finished.connect(lambda c, s: self.console_widget.handle_program_finished(pw, c, s))
pw.finished.connect(lambda c, s: self.handle_program_finished(pw, c, s))
if cb is not None:
pw.finished.connect(cb)
pw.remove.connect(lambda: self.remove_program(pw))
@@ -92,6 +95,17 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel):
if not settings.value("keep_build_programs", False, bool):
pw.finished.connect(lambda: self.remove_program(pw))
pw.start_program()
self.programs_state = TabProgramsState.RUNNING
self.program_state_changed.emit(self.programs_state)
def handle_program_finished(self, pw: ProgramWidget, exit_code: int, exit_status: QProcess.ExitStatus):
self.console_widget.handle_program_finished(pw, exit_code, exit_status)
if exit_code != 0 and exit_code != 15:
self.programs_state = TabProgramsState.ERROR
else:
if len(self.programs_widget.layout().children()) == 0 and self.programs_state != TabProgramsState.ERROR:
self.programs_state = TabProgramsState.IDLE
self.program_state_changed.emit(self.programs_state)
def remove_program(self, pw: ProgramWidget):
self.programs_widget.layout().removeWidget(pw)
+64
View File
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="32"
viewBox="0 0 8.4666665 8.4666669"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="error.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.9195959"
inkscape:cx="18.390889"
inkscape:cy="24.391595"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1848"
inkscape:window-height="1016"
inkscape:window-x="72"
inkscape:window-y="1107"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-288.53331)">
<circle
style="fill:#ff4444;fill-opacity:1;stroke:none;stroke-width:0.25604835;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path815"
cx="4.2333331"
cy="292.76663"
r="3.8407259" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+14
View File
@@ -13,6 +13,14 @@ 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(os.path.join(dirname, "running.svg")),
TabProgramsState.ERROR: QtGui.QIcon(os.path.join(dirname, "error.svg"))}
class PprzCenter(QMainWindow, Ui_SupervisionWindow):
@@ -40,6 +48,9 @@ class PprzCenter(QMainWindow, Ui_SupervisionWindow):
self.header.ac_save.connect(lambda _: self.conf.save())
self.header.ac_new.connect(self.handle_new_ac)
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)
@@ -51,6 +62,9 @@ class PprzCenter(QMainWindow, Ui_SupervisionWindow):
self.operation_panel.session.init()
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)
+12
View File
@@ -9,12 +9,20 @@ from PyQt5.QtCore import QProcess
from PyQt5.QtGui import QIcon
import utils
from typing import List
from enum import Enum
class TabProgramsState(Enum):
IDLE = 0
RUNNING = 1
ERROR = 2
class ProgramWidget(QWidget, Ui_Program):
ready_read_stdout = QtCore.pyqtSignal()
ready_read_stderr = QtCore.pyqtSignal()
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal(int, QProcess.ExitStatus)
remove = QtCore.pyqtSignal()
@@ -38,8 +46,10 @@ class ProgramWidget(QWidget, Ui_Program):
self.icon_label.setToolTip(shortname)
def start_program(self):
self.program_lineedit.setStyleSheet("")
if self.process.state() == QProcess.NotRunning:
self.process.start(self.cmd[0], self.cmd[1:])
self.started.emit()
def handle_cmd_return(self):
if self.process.state() == QProcess.NotRunning:
@@ -68,6 +78,8 @@ class ProgramWidget(QWidget, Ui_Program):
self.program_lineedit.setReadOnly(True)
def handle_finished(self, exit_code: int, exit_status: QProcess.ExitStatus):
if exit_code not in (0, 15):
self.program_lineedit.setStyleSheet("background: #f56464")
icon = QIcon.fromTheme("media-playback-start")
self.run_button.setIcon(icon)
self.program_lineedit.setReadOnly(False)
+64
View File
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="32"
viewBox="0 0 8.4666665 8.4666669"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="running.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.9195959"
inkscape:cx="18.390889"
inkscape:cy="24.391595"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1848"
inkscape:window-height="1016"
inkscape:window-x="72"
inkscape:window-y="1107"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-288.53331)">
<circle
style="fill:#277eff;fill-opacity:1;stroke:none;stroke-width:0.25604835;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path815"
cx="4.2333331"
cy="292.76663"
r="3.8407259" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+22 -1
View File
@@ -10,7 +10,7 @@ import utils
import lxml.etree as ET
from typing import List, Optional, Tuple, Dict
from program_widget import ProgramWidget
from program_widget import ProgramWidget, TabProgramsState
from tools_menu import ToolMenu
from programs_conf import *
from conf import *
@@ -21,6 +21,7 @@ class SessionWidget(QWidget, Ui_Session):
programs_all_stopped = QtCore.pyqtSignal()
program_spawned = QtCore.pyqtSignal()
program_state_changed = QtCore.pyqtSignal(TabProgramsState)
def __init__(self, parent=None):
QWidget.__init__(self, parent=parent)
@@ -33,6 +34,7 @@ class SessionWidget(QWidget, Ui_Session):
self.tools_menu = ToolMenu()
self.sessions_combo.addItems(["Simulation", "Replay"])
self.sessions_combo.insertSeparator(2)
self.programs_state: TabProgramsState = TabProgramsState.IDLE
self.menu_button.addAction(self.save_session_action)
self.menu_button.addAction(self.save_as_action)
self.menu_button.addAction(self.rename_session_action)
@@ -71,6 +73,7 @@ class SessionWidget(QWidget, Ui_Session):
return self.sessions_combo.currentText()
def start_session(self):
self.reset_programs_status()
combo_text = self.sessions_combo.currentText()
if combo_text == "Simulation":
self.start_simulation()
@@ -145,6 +148,7 @@ class SessionWidget(QWidget, Ui_Session):
pw.ready_read_stderr.connect(lambda: self.console.handle_stderr(pw))
pw.ready_read_stdout.connect(lambda: self.console.handle_stdout(pw))
pw.finished.connect(lambda c, s: self.handle_program_finished(pw, c, s))
pw.started.connect(self.handle_program_started)
pw.remove.connect(lambda: self.remove_program(pw))
# if REMOVE_PROGRAMS_FINISHED:
# pw.finished.connect(lambda: self.remove_program(pw))
@@ -169,17 +173,34 @@ class SessionWidget(QWidget, Ui_Session):
if not self.any_program_running():
self.programs_all_stopped.emit()
if c != 0 and c != 15:
self.programs_state = TabProgramsState.ERROR
else:
if not self.any_program_running() and self.programs_state != TabProgramsState.ERROR:
self.programs_state = TabProgramsState.IDLE
self.program_state_changed.emit(self.programs_state)
def handle_program_started(self):
self.programs_state = TabProgramsState.RUNNING
self.program_state_changed.emit(self.programs_state)
def reset_programs_status(self):
self.programs_state = TabProgramsState.IDLE
self.program_state_changed.emit(self.programs_state)
def stop_all(self):
for pw in self.program_widgets:
pw.terminate()
self.reset_programs_status()
def start_all(self):
for pw in self.program_widgets:
pw.start_program()
self.reset_programs_status()
def remove_all(self):
for pw in list(self.program_widgets):
pw.handle_remove()
self.reset_programs_status()
def init_tools_menu(self):
for t in self.tools.values():