mirror of
https://github.com/paparazzi/paparazzi.git
synced 2026-05-28 09:58:23 +08:00
[supervision] Add programs status. (#3014)
* [supervision] Add programs status. * [supervision] turn program line red on error.
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
from PyQt5.QtWidgets import *
|
from PyQt5.QtWidgets import *
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
from PyQt5.QtCore import QProcess
|
||||||
import utils
|
import utils
|
||||||
from generated.ui_configuration_panel import Ui_ConfigurationPanel
|
from generated.ui_configuration_panel import Ui_ConfigurationPanel
|
||||||
from program_widget import ProgramWidget
|
from program_widget import ProgramWidget, TabProgramsState
|
||||||
from conf import *
|
from conf import *
|
||||||
from programs_conf import parse_tools
|
from programs_conf import parse_tools
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -12,6 +13,7 @@ class ConfigurationPanel(QWidget, Ui_ConfigurationPanel):
|
|||||||
|
|
||||||
clear_error = QtCore.pyqtSignal()
|
clear_error = QtCore.pyqtSignal()
|
||||||
ac_edited = QtCore.pyqtSignal(Aircraft)
|
ac_edited = QtCore.pyqtSignal(Aircraft)
|
||||||
|
program_state_changed = QtCore.pyqtSignal(TabProgramsState)
|
||||||
|
|
||||||
def __init__(self, parent=None, *args, **kwargs):
|
def __init__(self, parent=None, *args, **kwargs):
|
||||||
QWidget.__init__(self, parent=parent, *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.console_widget.filter_widget.hide()
|
||||||
self.currentAC = None # type: Aircraft
|
self.currentAC = None # type: Aircraft
|
||||||
self.flight_plan_editor = None
|
self.flight_plan_editor = None
|
||||||
|
self.programs_state: TabProgramsState = TabProgramsState.IDLE
|
||||||
self.conf_widget.conf_changed.connect(self.handle_conf_changed)
|
self.conf_widget.conf_changed.connect(self.handle_conf_changed)
|
||||||
self.conf_widget.setting_changed.connect(self.handle_setting_changed)
|
self.conf_widget.setting_changed.connect(self.handle_setting_changed)
|
||||||
self.conf_widget.flight_plan.edit_alt.connect(self.edit_flightplan_gcs)
|
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)
|
self.programs_widget.layout().addWidget(pw)
|
||||||
pw.ready_read_stderr.connect(lambda: self.console_widget.handle_stderr(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.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:
|
if cb is not None:
|
||||||
pw.finished.connect(cb)
|
pw.finished.connect(cb)
|
||||||
pw.remove.connect(lambda: self.remove_program(pw))
|
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):
|
if not settings.value("keep_build_programs", False, bool):
|
||||||
pw.finished.connect(lambda: self.remove_program(pw))
|
pw.finished.connect(lambda: self.remove_program(pw))
|
||||||
pw.start_program()
|
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):
|
def remove_program(self, pw: ProgramWidget):
|
||||||
self.programs_widget.layout().removeWidget(pw)
|
self.programs_widget.layout().removeWidget(pw)
|
||||||
|
|||||||
@@ -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 |
@@ -13,6 +13,14 @@ from conf import Conf, Aircraft, ConfError
|
|||||||
from app_settings import AppSettings
|
from app_settings import AppSettings
|
||||||
from generated.ui_supervision_window import Ui_SupervisionWindow
|
from generated.ui_supervision_window import Ui_SupervisionWindow
|
||||||
from generated.ui_new_ac_dialog import Ui_NewACDialog
|
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):
|
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_save.connect(lambda _: self.conf.save())
|
||||||
self.header.ac_new.connect(self.handle_new_ac)
|
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.program_spawned.connect(self.header.disable_sets)
|
||||||
self.operation_panel.session.programs_all_stopped.connect(self.header.enable_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.operation_panel.session.init()
|
||||||
self.header.update_sets()
|
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):
|
def handle_set_changed(self, conf_file):
|
||||||
self.conf = Conf(conf_file)
|
self.conf = Conf(conf_file)
|
||||||
Conf.set_current_conf(conf_file)
|
Conf.set_current_conf(conf_file)
|
||||||
|
|||||||
@@ -9,12 +9,20 @@ from PyQt5.QtCore import QProcess
|
|||||||
from PyQt5.QtGui import QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
import utils
|
import utils
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class TabProgramsState(Enum):
|
||||||
|
IDLE = 0
|
||||||
|
RUNNING = 1
|
||||||
|
ERROR = 2
|
||||||
|
|
||||||
|
|
||||||
class ProgramWidget(QWidget, Ui_Program):
|
class ProgramWidget(QWidget, Ui_Program):
|
||||||
|
|
||||||
ready_read_stdout = QtCore.pyqtSignal()
|
ready_read_stdout = QtCore.pyqtSignal()
|
||||||
ready_read_stderr = QtCore.pyqtSignal()
|
ready_read_stderr = QtCore.pyqtSignal()
|
||||||
|
started = QtCore.pyqtSignal()
|
||||||
finished = QtCore.pyqtSignal(int, QProcess.ExitStatus)
|
finished = QtCore.pyqtSignal(int, QProcess.ExitStatus)
|
||||||
remove = QtCore.pyqtSignal()
|
remove = QtCore.pyqtSignal()
|
||||||
|
|
||||||
@@ -38,8 +46,10 @@ class ProgramWidget(QWidget, Ui_Program):
|
|||||||
self.icon_label.setToolTip(shortname)
|
self.icon_label.setToolTip(shortname)
|
||||||
|
|
||||||
def start_program(self):
|
def start_program(self):
|
||||||
|
self.program_lineedit.setStyleSheet("")
|
||||||
if self.process.state() == QProcess.NotRunning:
|
if self.process.state() == QProcess.NotRunning:
|
||||||
self.process.start(self.cmd[0], self.cmd[1:])
|
self.process.start(self.cmd[0], self.cmd[1:])
|
||||||
|
self.started.emit()
|
||||||
|
|
||||||
def handle_cmd_return(self):
|
def handle_cmd_return(self):
|
||||||
if self.process.state() == QProcess.NotRunning:
|
if self.process.state() == QProcess.NotRunning:
|
||||||
@@ -68,6 +78,8 @@ class ProgramWidget(QWidget, Ui_Program):
|
|||||||
self.program_lineedit.setReadOnly(True)
|
self.program_lineedit.setReadOnly(True)
|
||||||
|
|
||||||
def handle_finished(self, exit_code: int, exit_status: QProcess.ExitStatus):
|
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")
|
icon = QIcon.fromTheme("media-playback-start")
|
||||||
self.run_button.setIcon(icon)
|
self.run_button.setIcon(icon)
|
||||||
self.program_lineedit.setReadOnly(False)
|
self.program_lineedit.setReadOnly(False)
|
||||||
|
|||||||
@@ -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 |
@@ -10,7 +10,7 @@ import utils
|
|||||||
import lxml.etree as ET
|
import lxml.etree as ET
|
||||||
|
|
||||||
from typing import List, Optional, Tuple, Dict
|
from typing import List, Optional, Tuple, Dict
|
||||||
from program_widget import ProgramWidget
|
from program_widget import ProgramWidget, TabProgramsState
|
||||||
from tools_menu import ToolMenu
|
from tools_menu import ToolMenu
|
||||||
from programs_conf import *
|
from programs_conf import *
|
||||||
from conf import *
|
from conf import *
|
||||||
@@ -21,6 +21,7 @@ class SessionWidget(QWidget, Ui_Session):
|
|||||||
|
|
||||||
programs_all_stopped = QtCore.pyqtSignal()
|
programs_all_stopped = QtCore.pyqtSignal()
|
||||||
program_spawned = QtCore.pyqtSignal()
|
program_spawned = QtCore.pyqtSignal()
|
||||||
|
program_state_changed = QtCore.pyqtSignal(TabProgramsState)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWidget.__init__(self, parent=parent)
|
QWidget.__init__(self, parent=parent)
|
||||||
@@ -33,6 +34,7 @@ class SessionWidget(QWidget, Ui_Session):
|
|||||||
self.tools_menu = ToolMenu()
|
self.tools_menu = ToolMenu()
|
||||||
self.sessions_combo.addItems(["Simulation", "Replay"])
|
self.sessions_combo.addItems(["Simulation", "Replay"])
|
||||||
self.sessions_combo.insertSeparator(2)
|
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_session_action)
|
||||||
self.menu_button.addAction(self.save_as_action)
|
self.menu_button.addAction(self.save_as_action)
|
||||||
self.menu_button.addAction(self.rename_session_action)
|
self.menu_button.addAction(self.rename_session_action)
|
||||||
@@ -71,6 +73,7 @@ class SessionWidget(QWidget, Ui_Session):
|
|||||||
return self.sessions_combo.currentText()
|
return self.sessions_combo.currentText()
|
||||||
|
|
||||||
def start_session(self):
|
def start_session(self):
|
||||||
|
self.reset_programs_status()
|
||||||
combo_text = self.sessions_combo.currentText()
|
combo_text = self.sessions_combo.currentText()
|
||||||
if combo_text == "Simulation":
|
if combo_text == "Simulation":
|
||||||
self.start_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_stderr.connect(lambda: self.console.handle_stderr(pw))
|
||||||
pw.ready_read_stdout.connect(lambda: self.console.handle_stdout(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.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))
|
pw.remove.connect(lambda: self.remove_program(pw))
|
||||||
# if REMOVE_PROGRAMS_FINISHED:
|
# if REMOVE_PROGRAMS_FINISHED:
|
||||||
# pw.finished.connect(lambda: self.remove_program(pw))
|
# pw.finished.connect(lambda: self.remove_program(pw))
|
||||||
@@ -169,17 +173,34 @@ class SessionWidget(QWidget, Ui_Session):
|
|||||||
if not self.any_program_running():
|
if not self.any_program_running():
|
||||||
self.programs_all_stopped.emit()
|
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):
|
def stop_all(self):
|
||||||
for pw in self.program_widgets:
|
for pw in self.program_widgets:
|
||||||
pw.terminate()
|
pw.terminate()
|
||||||
|
self.reset_programs_status()
|
||||||
|
|
||||||
def start_all(self):
|
def start_all(self):
|
||||||
for pw in self.program_widgets:
|
for pw in self.program_widgets:
|
||||||
pw.start_program()
|
pw.start_program()
|
||||||
|
self.reset_programs_status()
|
||||||
|
|
||||||
def remove_all(self):
|
def remove_all(self):
|
||||||
for pw in list(self.program_widgets):
|
for pw in list(self.program_widgets):
|
||||||
pw.handle_remove()
|
pw.handle_remove()
|
||||||
|
self.reset_programs_status()
|
||||||
|
|
||||||
def init_tools_menu(self):
|
def init_tools_menu(self):
|
||||||
for t in self.tools.values():
|
for t in self.tools.values():
|
||||||
|
|||||||
Reference in New Issue
Block a user