Skip to content

Commit

Permalink
[supervision] Add log extractor. (#3186)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabien-B committed Nov 27, 2023
1 parent b0b127d commit 9c1e21d
Show file tree
Hide file tree
Showing 15 changed files with 1,392 additions and 621 deletions.
3 changes: 3 additions & 0 deletions sw/supervision/python/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def __init__(self, parent=None):
self.terminal_emulator_edit.setText(settings.value("terminal_emulator", "", str))
self.keep_changes_checkbox.setChecked(settings.value("always_keep_changes", False, bool))
self.keep_build_programs_checkbox.setChecked(settings.value("keep_build_programs", False, bool))
self.log_path_edit.setText(settings.value("default_log_path", "$PAPARAZZI_HOME/var/logs/$YY-$MM/$DD/$AIRCRAFT", str))
self.finished.connect(self.handle_finished)

def handle_finished(self, result):
Expand All @@ -29,3 +30,5 @@ def handle_finished(self, result):
settings.setValue("always_keep_changes", keep_changes)
keep_build_programs = self.keep_build_programs_checkbox.isChecked()
settings.setValue("keep_build_programs", keep_build_programs)
default_log_path = self.log_path_edit.text()
settings.setValue("default_log_path", default_log_path)
2 changes: 1 addition & 1 deletion sw/supervision/python/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def make_setting(txt):
gui_color, settings, settings_modules)
self.aircrafts.append(ac)

def __getitem__(self, item):
def __getitem__(self, item) -> Aircraft:
for ac in self.aircrafts:
if (isinstance(item, int) and item == ac.ac_id) or (isinstance(item, str) and item == ac.name):
return ac
Expand Down
1,480 changes: 904 additions & 576 deletions sw/supervision/python/generated/resources_rc.py

Large diffs are not rendered by default.

54 changes: 33 additions & 21 deletions sw/supervision/python/generated/ui_app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Form implementation generated from reading ui file 'ui/ui_app_settings.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
Expand All @@ -14,41 +14,52 @@
class Ui_AppSettingsDialog(object):
def setupUi(self, AppSettingsDialog):
AppSettingsDialog.setObjectName("AppSettingsDialog")
AppSettingsDialog.resize(381, 131)
AppSettingsDialog.resize(364, 212)
self.gridLayout = QtWidgets.QGridLayout(AppSettingsDialog)
self.gridLayout.setObjectName("gridLayout")
self.label = QtWidgets.QLabel(AppSettingsDialog)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.keep_build_programs_checkbox = QtWidgets.QCheckBox(AppSettingsDialog)
self.keep_build_programs_checkbox.setText("")
self.keep_build_programs_checkbox.setObjectName("keep_build_programs_checkbox")
self.gridLayout.addWidget(self.keep_build_programs_checkbox, 3, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(AppSettingsDialog)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
self.text_editor_edit = QtWidgets.QLineEdit(AppSettingsDialog)
self.text_editor_edit.setObjectName("text_editor_edit")
self.gridLayout.addWidget(self.text_editor_edit, 0, 1, 1, 1)
self.label_3 = QtWidgets.QLabel(AppSettingsDialog)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(AppSettingsDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 5, 0, 1, 2)
self.gridLayout.addWidget(self.buttonBox, 6, 0, 1, 2)
self.terminal_emulator_edit = QtWidgets.QLineEdit(AppSettingsDialog)
self.terminal_emulator_edit.setObjectName("terminal_emulator_edit")
self.gridLayout.addWidget(self.terminal_emulator_edit, 1, 1, 1, 1)
self.keep_changes_checkbox = QtWidgets.QCheckBox(AppSettingsDialog)
self.keep_changes_checkbox.setText("")
self.keep_changes_checkbox.setObjectName("keep_changes_checkbox")
self.gridLayout.addWidget(self.keep_changes_checkbox, 2, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(AppSettingsDialog)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
self.terminal_emulator_edit = QtWidgets.QLineEdit(AppSettingsDialog)
self.terminal_emulator_edit.setObjectName("terminal_emulator_edit")
self.gridLayout.addWidget(self.terminal_emulator_edit, 1, 1, 1, 1)
self.label_3 = QtWidgets.QLabel(AppSettingsDialog)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
self.label = QtWidgets.QLabel(AppSettingsDialog)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.label_4 = QtWidgets.QLabel(AppSettingsDialog)
self.label_4.setWordWrap(True)
self.label_4.setObjectName("label_4")
self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
self.keep_build_programs_checkbox = QtWidgets.QCheckBox(AppSettingsDialog)
self.keep_build_programs_checkbox.setText("")
self.keep_build_programs_checkbox.setObjectName("keep_build_programs_checkbox")
self.gridLayout.addWidget(self.keep_build_programs_checkbox, 3, 1, 1, 1)
self.label_5 = QtWidgets.QLabel(AppSettingsDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth())
self.label_5.setSizePolicy(sizePolicy)
self.label_5.setObjectName("label_5")
self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1)
self.log_path_edit = QtWidgets.QLineEdit(AppSettingsDialog)
self.log_path_edit.setObjectName("log_path_edit")
self.gridLayout.addWidget(self.log_path_edit, 4, 1, 1, 1)

self.retranslateUi(AppSettingsDialog)
self.buttonBox.accepted.connect(AppSettingsDialog.accept) # type: ignore
Expand All @@ -58,7 +69,8 @@ def setupUi(self, AppSettingsDialog):
def retranslateUi(self, AppSettingsDialog):
_translate = QtCore.QCoreApplication.translate
AppSettingsDialog.setWindowTitle(_translate("AppSettingsDialog", "Settings"))
self.label.setText(_translate("AppSettingsDialog", "Text editor"))
self.label_3.setText(_translate("AppSettingsDialog", "Keep changes on exit"))
self.label_2.setText(_translate("AppSettingsDialog", "Terminal Emulator"))
self.label_3.setText(_translate("AppSettingsDialog", "Keep changes on exit"))
self.label.setText(_translate("AppSettingsDialog", "Text editor"))
self.label_4.setText(_translate("AppSettingsDialog", "keep build programs"))
self.label_5.setText(_translate("AppSettingsDialog", "Default log path"))
82 changes: 82 additions & 0 deletions sw/supervision/python/generated/ui_log_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'ui/ui_log_widget.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_LogWidget(object):
def setupUi(self, LogWidget):
LogWidget.setObjectName("LogWidget")
LogWidget.resize(359, 128)
self.verticalLayout = QtWidgets.QVBoxLayout(LogWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.label = QtWidgets.QLabel(LogWidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.input_button = QtWidgets.QToolButton(LogWidget)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/icons/icons/open.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.input_button.setIcon(icon)
self.input_button.setObjectName("input_button")
self.gridLayout.addWidget(self.input_button, 0, 2, 1, 1)
self.output_button = QtWidgets.QToolButton(LogWidget)
self.output_button.setIcon(icon)
self.output_button.setObjectName("output_button")
self.gridLayout.addWidget(self.output_button, 1, 2, 1, 1)
self.label_2 = QtWidgets.QLabel(LogWidget)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
self.output_edit = QtWidgets.QLineEdit(LogWidget)
self.output_edit.setObjectName("output_edit")
self.gridLayout.addWidget(self.output_edit, 1, 1, 1, 1)
self.input_edit = QtWidgets.QLineEdit(LogWidget)
self.input_edit.setObjectName("input_edit")
self.gridLayout.addWidget(self.input_edit, 0, 1, 1, 1)
self.reset_output_button = QtWidgets.QToolButton(LogWidget)
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/icons/icons/undo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.reset_output_button.setIcon(icon1)
self.reset_output_button.setObjectName("reset_output_button")
self.gridLayout.addWidget(self.reset_output_button, 1, 3, 1, 1)
self.help_button = QtWidgets.QToolButton(LogWidget)
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/icons/icons/help.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.help_button.setIcon(icon2)
self.help_button.setObjectName("help_button")
self.gridLayout.addWidget(self.help_button, 1, 4, 1, 1)
self.status_label = QtWidgets.QLabel(LogWidget)
self.status_label.setText("")
self.status_label.setObjectName("status_label")
self.gridLayout.addWidget(self.status_label, 2, 1, 1, 1)
self.process_button = QtWidgets.QPushButton(LogWidget)
self.process_button.setObjectName("process_button")
self.gridLayout.addWidget(self.process_button, 2, 0, 1, 1)
self.verticalLayout.addLayout(self.gridLayout)

self.retranslateUi(LogWidget)
QtCore.QMetaObject.connectSlotsByName(LogWidget)

def retranslateUi(self, LogWidget):
_translate = QtCore.QCoreApplication.translate
LogWidget.setWindowTitle(_translate("LogWidget", "Form"))
self.label.setText(_translate("LogWidget", "SD log file"))
self.input_button.setToolTip(_translate("LogWidget", "Select SD LOG file"))
self.input_button.setText(_translate("LogWidget", "..."))
self.output_button.setToolTip(_translate("LogWidget", "Select output directory"))
self.output_button.setText(_translate("LogWidget", "..."))
self.label_2.setText(_translate("LogWidget", "Output dir"))
self.reset_output_button.setToolTip(_translate("LogWidget", "reset to default"))
self.reset_output_button.setText(_translate("LogWidget", "..."))
self.help_button.setToolTip(_translate("LogWidget", "help"))
self.help_button.setText(_translate("LogWidget", "..."))
self.process_button.setText(_translate("LogWidget", "Process"))
from generated import resources_rc
27 changes: 24 additions & 3 deletions sw/supervision/python/generated/ui_supervision_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

# Form implementation generated from reading ui file 'ui/ui_supervision_window.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
# WARNING! All changes made in this file will be lost!


from PyQt5 import QtCore, QtGui, QtWidgets
Expand Down Expand Up @@ -33,6 +32,25 @@ def setupUi(self, SupervisionWindow):
self.doc_panel = DocPanel()
self.doc_panel.setObjectName("doc_panel")
self.tabwidget.addTab(self.doc_panel, "")
self.tools_panel = QtWidgets.QWidget()
self.tools_panel.setObjectName("tools_panel")
self.gridLayout = QtWidgets.QGridLayout(self.tools_panel)
self.gridLayout.setObjectName("gridLayout")
self.groupBox = QtWidgets.QGroupBox(self.tools_panel)
self.groupBox.setObjectName("groupBox")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.log_widget = LogWidget(self.groupBox)
self.log_widget.setObjectName("log_widget")
self.verticalLayout_2.addWidget(self.log_widget)
self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem, 0, 1, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem1, 1, 0, 1, 1)
self.gridLayout.setColumnStretch(0, 2)
self.gridLayout.setColumnStretch(1, 1)
self.tabwidget.addTab(self.tools_panel, "")
self.verticalLayout.addWidget(self.tabwidget)
SupervisionWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(SupervisionWindow)
Expand Down Expand Up @@ -65,11 +83,14 @@ def retranslateUi(self, SupervisionWindow):
self.tabwidget.setTabText(self.tabwidget.indexOf(self.configuration_panel), _translate("SupervisionWindow", "Configuration"))
self.tabwidget.setTabText(self.tabwidget.indexOf(self.operation_panel), _translate("SupervisionWindow", "Operation"))
self.tabwidget.setTabText(self.tabwidget.indexOf(self.doc_panel), _translate("SupervisionWindow", "Documentation"))
self.groupBox.setTitle(_translate("SupervisionWindow", "Extract SD logs"))
self.tabwidget.setTabText(self.tabwidget.indexOf(self.tools_panel), _translate("SupervisionWindow", "Utilities"))
self.menuFile.setTitle(_translate("SupervisionWindow", "File"))
self.menuHelp.setTitle(_translate("SupervisionWindow", "Help"))
self.settings_action.setText(_translate("SupervisionWindow", "Edit Settings"))
self.about_action.setText(_translate("SupervisionWindow", "About"))
from configuration_panel import ConfigurationPanel
from doc_panel import DocPanel
from header_widget import HeaderWidget
from log_widget import LogWidget
from operation_panel import OperationPanel
137 changes: 137 additions & 0 deletions sw/supervision/python/log_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QProcess
import utils
from generated.ui_log_widget import Ui_LogWidget
from conf import *
import re
import shutil

TMP_DIR = "/tmp/pprz"

ENVS = [
"PAPARAZZI_HOME",
"PAPARAZZI_SRC",
"HOME"
]

DATE_TAGS = ["YY", "MM", "DD", "hh", "mm", "ss"]
OTHER_TAGS = ["AIRCRAFT", "AC_ID"]


class LogWidget(QWidget, Ui_LogWidget):

def __init__(self, parent=None, *args, **kwargs):
QWidget.__init__(self, parent=parent, *args, **kwargs)
self.setupUi(self)
self.process = QProcess(self)
self.date = None
self.basename = None
self.conf: Conf = None
self.substitutions = {}
self.reset_output()
self.input_button.clicked.connect(self.select_input)
self.output_button.clicked.connect(self.select_output)
self.reset_output_button.clicked.connect(self.reset_output)
self.process_button.clicked.connect(self.start_processing)
self.help_button.clicked.connect(self.show_help)
self.process.finished.connect(self.handle_finished)
self.process.readyReadStandardOutput.connect(self.read_stdout)
self.process.readyReadStandardError.connect(self.read_stderr)

def set_conf(self, conf):
self.conf = conf

def select_input(self):
home = os.path.expandvars("$HOME")
(path, _) = QFileDialog().getOpenFileName(self, "Select log file", home, "Log (*.LOG);; All (*.*)")
self.input_edit.setText(path)

def select_output(self):
start_path = os.path.expandvars(self.output_edit.text())
path = QFileDialog().getExistingDirectory(self, "Select log file", start_path, QFileDialog.ShowDirsOnly)
for env in ENVS:
res = os.getenv(env)
if res in path:
path_sub = path.replace(res, f"${env}")
if os.path.expandvars(path_sub) == path:
# check that the substitution is valid
path = path_sub
self.output_edit.setText(path)

def reset_output(self):
settings = utils.get_settings()
output_path = settings.value("default_log_path", "$PAPARAZZI_HOME/var/logs/$YY-$MM/$DD/$AIRCRAFT", str)
self.output_edit.setText(output_path)

def start_processing(self):
if (self.process.state() == QProcess.ProcessState.NotRunning and
self.input_edit.text() != ""):
program = os.path.expandvars("$PAPARAZZI_SRC/sw/logalizer/sd2log")
input = self.input_edit.text()
output = os.path.expandvars(self.output_edit.text())
if not os.path.exists(TMP_DIR):
os.makedirs(TMP_DIR)
self.process.start(program, [input, TMP_DIR])
self.status_label.setText("processing...")

def handle_finished(self, exit_code: int, exit_status: QProcess.ExitStatus):
if exit_status != QProcess.ExitStatus.NormalExit or exit_code != 0:
self.status_label.setText(f"exited with code {exit_code}")
return

self.status_label.setText("Log processed, copying files...")
ac_id = self.get_ac_id(f"{TMP_DIR}/{self.basename}.data")
ac = self.conf[ac_id]
self.substitutions["AC_ID"] = str(ac_id)
self.substitutions["AIRCRAFT"] = ac.name
out = os.path.expandvars(self.output_edit.text())
for tag in DATE_TAGS + OTHER_TAGS:
try:
value = self.substitutions[tag]
out = out.replace(f"${tag}", value)
except KeyError:
print(f"no tag {tag}")
self.status_label.setText(f"Error: unknown tag {tag}!")
return
if not os.path.exists(out):
os.makedirs(out)
shutil.move(f"{TMP_DIR}/{self.basename}.data", f"{out}/{self.basename}.data")
shutil.move(f"{TMP_DIR}/{self.basename}.log", f"{out}/{self.basename}.log")
shutil.move(f"{TMP_DIR}/{self.basename}.tlm", f"{out}/{self.basename}.tlm")
self.status_label.setText(f"Files extracted in {out}")

def get_ac_id(self, filename) -> int:
with open(filename, 'r') as data:
ac_id = int(data.readline().split()[1])
return ac_id

def read_stdout(self):
out = self.process.readAllStandardOutput()
out = out.data().decode()

def read_stderr(self):
err = self.process.readAllStandardError()
err = err.data().decode()
for line in err.splitlines():
m = re.match(r'(.*)\.data', line)
if m is not None:
self.basename = m.group(1)
m = re.match(r'(\d{2})_(\d{2})_(\d{2})__(\d{2})_(\d{2})_(\d{2})', self.basename)
if len(m.groups()) == 6:
for tag, value in zip(DATE_TAGS, m.groups()):
self.substitutions[tag] = value

def show_help(self):
QMessageBox.information(self, "logs extractor help",
"<h1>SD log extractor</h1><br/>"
"Extract logs from flight recorder.<br/>"
"You can use substitution variables in the output directory:<br/>"
"<b>$AIRCRAFT</b>: aircraft name<br/>"
"<b>$AC_ID</b>: aircraft id<br/>"
"<b>$YY</b>: year<br/>"
"<b>$MM</b>: month<br/>"
"<b>$DD</b>: day<br/>"
"<b>$hh</b>: hour<br/>"
"<b>$mm</b>: minutes<br/>"
"<b>$ss</b>: seconds<br/>")

0 comments on commit 9c1e21d

Please sign in to comment.