diff --git a/sw/supervision/python/configuration_panel.py b/sw/supervision/python/configuration_panel.py
index 71b08a9285..d7d999bb37 100644
--- a/sw/supervision/python/configuration_panel.py
+++ b/sw/supervision/python/configuration_panel.py
@@ -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)
diff --git a/sw/supervision/python/error.svg b/sw/supervision/python/error.svg
new file mode 100644
index 0000000000..d7aae300e3
--- /dev/null
+++ b/sw/supervision/python/error.svg
@@ -0,0 +1,64 @@
+
+
+
+
diff --git a/sw/supervision/python/paparazzicenter.py b/sw/supervision/python/paparazzicenter.py
index 8cf294d618..025c5cd737 100755
--- a/sw/supervision/python/paparazzicenter.py
+++ b/sw/supervision/python/paparazzicenter.py
@@ -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)
diff --git a/sw/supervision/python/program_widget.py b/sw/supervision/python/program_widget.py
index 54ce45ec17..536028f461 100644
--- a/sw/supervision/python/program_widget.py
+++ b/sw/supervision/python/program_widget.py
@@ -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)
diff --git a/sw/supervision/python/running.svg b/sw/supervision/python/running.svg
new file mode 100644
index 0000000000..db4659e242
--- /dev/null
+++ b/sw/supervision/python/running.svg
@@ -0,0 +1,64 @@
+
+
+
+
diff --git a/sw/supervision/python/session_widget.py b/sw/supervision/python/session_widget.py
index 2bd2c55e23..1e311763ff 100644
--- a/sw/supervision/python/session_widget.py
+++ b/sw/supervision/python/session_widget.py
@@ -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():