From f7138e0d21ff3a15201a88a101c17ab5efd22258 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Wed, 15 Jan 2025 23:50:46 -0600 Subject: [PATCH 01/32] Migrate to PyQt6 --- horusgui/__init__.py | 2 +- horusgui/gui.py | 2686 +++++++++++++++++++++--------------------- horusgui/widgets.py | 6 +- requirements.txt | 3 +- 4 files changed, 1357 insertions(+), 1340 deletions(-) diff --git a/horusgui/__init__.py b/horusgui/__init__.py index 08aad71..151610c 100755 --- a/horusgui/__init__.py +++ b/horusgui/__init__.py @@ -1 +1 @@ -__version__ = "0.3.19" +__version__ = "0.4.0-beta0" diff --git a/horusgui/gui.py b/horusgui/gui.py index fa5d1e0..d768f4d 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -22,9 +22,11 @@ import time import pyqtgraph as pg import numpy as np from queue import Queue -#from pyqtgraph.Qt import QtCore, QtGui, QtWidgets -from PyQt5 import QtWidgets, QtGui +from PyQt6.QtWidgets import * +from PyQt6.QtGui import * +from PyQt6.QtCore import * from pyqtgraph.dockarea import * +import qdarktheme from threading import Thread from .widgets import * @@ -44,44 +46,6 @@ from horusdemodlib.horusudp import send_payload_summary, send_ozimux_message from horusdemodlib.sondehubamateur import * from . import __version__ - -# A few hardcoded defaults -DEFAULT_ESTIMATOR_MIN = 100 -DEFAULT_ESTIMATOR_MAX = 4000 - - -# Global widget store -widgets = {} - -# Queues for handling updates to image / status indications. -fft_update_queue = Queue(1024) -status_update_queue = Queue(1024) -log_update_queue = Queue(2048) - -# List of audio devices and their info -audio_devices = {} - -# Processor objects -audio_stream = None -fft_process = None -horus_modem = None -sondehub_uploader = None -telemetry_logger = None - -decoder_init = False - -last_packet_time = None - - -# Rotator object -rotator = None -rotator_current_az = 0.0 -rotator_current_el = 0.0 - - -# Global running indicator -running = False - # Read command-line arguments parser = argparse.ArgumentParser(description="Project Horus GUI", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--payload-id-list", type=str, default=None, help="Use supplied Payload ID List instead of downloading a new one.") @@ -101,1282 +65,1365 @@ logging.basicConfig( format="%(asctime)s %(levelname)s: %(message)s", level=_log_level ) -# -# GUI Creation - The Bad way. -# - -# Create a Qt App. -pg.mkQApp() - -# GUI LAYOUT - Gtk Style! -win = QtWidgets.QMainWindow() -area = DockArea() -win.setCentralWidget(area) -win.setWindowTitle(f"Horus Telemetry GUI - v{__version__}") -win.setWindowIcon(getHorusIcon()) - -# Create multiple dock areas, for displaying our data. -d0 = Dock("Audio", size=(300, 50)) -d0_modem = Dock("Modem", size=(300, 80)) -d0_habitat = Dock("SondeHub", size=(300, 200)) -d0_other = Dock("Other", size=(300, 100)) -d0_rotator = Dock("Rotator", size=(300, 100)) -d1 = Dock("Spectrum", size=(800, 350)) -d2_stats = Dock("SNR (dB)", size=(50, 300)) -d2_snr = Dock("SNR Plot", size=(750, 300)) -d3_data = Dock("Data", size=(800, 50)) -d3_position = Dock("Position", size=(800, 50)) -d4 = Dock("Log", size=(800, 150)) -# Arrange docks. -area.addDock(d0) -area.addDock(d1, "right", d0) -area.addDock(d0_modem, "bottom", d0) -area.addDock(d0_habitat, "bottom", d0_modem) -area.addDock(d0_other, "below", d0_habitat) -area.addDock(d0_rotator, "below", d0_other) -area.addDock(d2_stats, "bottom", d1) -area.addDock(d3_data, "bottom", d2_stats) -area.addDock(d3_position, "bottom", d3_data) -area.addDock(d4, "bottom", d3_position) -area.addDock(d2_snr, "right", d2_stats) -d0_habitat.raiseDock() - - -# Controls -w1_audio = pg.LayoutWidget() -# TNC Connection -widgets["audioDeviceLabel"] = QtWidgets.QLabel("Audio Device:") -widgets["audioDeviceSelector"] = QtWidgets.QComboBox() - -widgets["audioSampleRateLabel"] = QtWidgets.QLabel("Sample Rate (Hz):") -widgets["audioSampleRateSelector"] = QtWidgets.QComboBox() - -widgets["audioDbfsLabel"] = QtWidgets.QLabel("Input Level (dBFS):") -widgets["audioDbfsValue"] = QtWidgets.QLabel("--") -widgets["audioDbfsValue_float"] = 0.0 - -w1_audio.addWidget(widgets["audioDeviceLabel"], 0, 0, 1, 1) -w1_audio.addWidget(widgets["audioDeviceSelector"], 0, 1, 1, 2) -w1_audio.addWidget(widgets["audioSampleRateLabel"], 1, 0, 1, 1) -w1_audio.addWidget(widgets["audioSampleRateSelector"], 1, 1, 1, 2) -w1_audio.addWidget(widgets["audioDbfsLabel"], 2, 0, 1, 1) -w1_audio.addWidget(widgets["audioDbfsValue"], 2, 1, 1, 2) -d0.addWidget(w1_audio) - -w1_modem = pg.LayoutWidget() - - -# Modem Parameters -widgets["horusModemLabel"] = QtWidgets.QLabel("Mode:") -widgets["horusModemSelector"] = QtWidgets.QComboBox() - -widgets["horusModemRateLabel"] = QtWidgets.QLabel("Baudrate:") -widgets["horusModemRateSelector"] = QtWidgets.QComboBox() - -widgets["horusMaskEstimatorLabel"] = QtWidgets.QLabel("Enable Mask Estim.:") -widgets["horusMaskEstimatorSelector"] = QtWidgets.QCheckBox() -widgets["horusMaskEstimatorSelector"].setToolTip( - "Enable the mask frequency estimator, which makes uses of the \n"\ - "tone spacing value entered below as extra input to the frequency\n"\ - "estimator. This can help decode performance in very weak signal conditions." -) - -widgets["horusMaskSpacingLabel"] = QtWidgets.QLabel("Tone Spacing (Hz):") -widgets["horusMaskSpacingEntry"] = QtWidgets.QLineEdit("270") -widgets["horusMaskSpacingEntry"].setToolTip( - "If the tone spacing of the transmitter is known, it can be entered here,\n"\ - "and used with the mask estimator option above. The default tone spacing for\n"\ - "a RS41-based transmitter is 270 Hz." -) -widgets["horusManualEstimatorLabel"] = QtWidgets.QLabel("Manual Estim. Limits:") -widgets["horusManualEstimatorSelector"] = QtWidgets.QCheckBox() -widgets["horusManualEstimatorSelector"].setToolTip( - "Enables manual selection of the frequency estimator limits. This will enable\n"\ - "a slidable area on the spectrum display, which can be used to select the frequency\n"\ - "range of interest, and help stop in-band CW interference from biasing the frequency\n"\ - "estimator. You can either click-and-drag the entire area, or click-and-drag the edges\n"\ - "to change the estimator frequency range." -) - -# Start/Stop -widgets["startDecodeButton"] = QtWidgets.QPushButton("Start") -widgets["startDecodeButton"].setEnabled(False) - -w1_modem.addWidget(widgets["horusModemLabel"], 0, 0, 1, 1) -w1_modem.addWidget(widgets["horusModemSelector"], 0, 1, 1, 1) -w1_modem.addWidget(widgets["horusModemRateLabel"], 1, 0, 1, 1) -w1_modem.addWidget(widgets["horusModemRateSelector"], 1, 1, 1, 1) -w1_modem.addWidget(widgets["horusMaskEstimatorLabel"], 2, 0, 1, 1) -w1_modem.addWidget(widgets["horusMaskEstimatorSelector"], 2, 1, 1, 1) -w1_modem.addWidget(widgets["horusMaskSpacingLabel"], 3, 0, 1, 1) -w1_modem.addWidget(widgets["horusMaskSpacingEntry"], 3, 1, 1, 1) -w1_modem.addWidget(widgets["horusManualEstimatorLabel"], 4, 0, 1, 1) -w1_modem.addWidget(widgets["horusManualEstimatorSelector"], 4, 1, 1, 1) -w1_modem.addWidget(widgets["startDecodeButton"], 5, 0, 2, 2) - -d0_modem.addWidget(w1_modem) - - -w1_habitat = pg.LayoutWidget() -# Listener Information -widgets["habitatHeading"] = QtWidgets.QLabel("SondeHub Settings") -widgets["sondehubUploadLabel"] = QtWidgets.QLabel("Enable SondeHub-Ham Upload:") -widgets["sondehubUploadSelector"] = QtWidgets.QCheckBox() -widgets["sondehubUploadSelector"].setChecked(True) -widgets["userCallLabel"] = QtWidgets.QLabel("Callsign:") -widgets["userCallEntry"] = QtWidgets.QLineEdit("N0CALL") -widgets["userCallEntry"].setMaxLength(20) -widgets["userCallEntry"].setToolTip( - "Your station callsign, which doesn't necessarily need to be an\n"\ - "amateur radio callsign, just something unique!" -) -widgets["userLocationLabel"] = QtWidgets.QLabel("Latitude / Longitude:") -widgets["userLatEntry"] = QtWidgets.QLineEdit("0.0") -widgets["userLatEntry"].setToolTip("Station Latitude in Decimal Degrees, e.g. -34.123456") -widgets["userLonEntry"] = QtWidgets.QLineEdit("0.0") -widgets["userLonEntry"].setToolTip("Station Longitude in Decimal Degrees, e.g. 138.123456") -widgets["userAltitudeLabel"] = QtWidgets.QLabel("Altitude:") -widgets["userAltEntry"] = QtWidgets.QLineEdit("0.0") -widgets["userAltEntry"].setToolTip("Station Altitude in Metres Above Sea Level.") -widgets["userAntennaLabel"] = QtWidgets.QLabel("Antenna:") -widgets["userAntennaEntry"] = QtWidgets.QLineEdit("") -widgets["userAntennaEntry"].setToolTip("A text description of your station's antenna.") -widgets["userRadioLabel"] = QtWidgets.QLabel("Radio:") -widgets["userRadioEntry"] = QtWidgets.QLineEdit("Horus-GUI " + __version__) -widgets["userRadioEntry"].setToolTip( - "A text description of your station's radio setup.\n"\ - "This field will be automatically prefixed with Horus-GUI\n"\ - "and the Horus-GUI software version." -) -widgets["habitatUploadPosition"] = QtWidgets.QPushButton("Re-upload Station Info") -widgets["habitatUploadPosition"].setToolTip( - "Manually re-upload your station information to SondeHub-Amateur.\n"\ -) -widgets["dialFreqLabel"] = QtWidgets.QLabel("Radio Dial Freq (MHz):") -widgets["dialFreqEntry"] = QtWidgets.QLineEdit("") -widgets["dialFreqEntry"].setToolTip( - "Optional entry of your radio's dial frequency in MHz (e.g. 437.600).\n"\ - "Used to provide frequency information on SondeHub-Amateur."\ -) -widgets["sondehubPositionNotesLabel"] = QtWidgets.QLabel("") - -widgets["saveSettingsButton"] = QtWidgets.QPushButton("Save Settings") - -w1_habitat.addWidget(widgets["sondehubUploadLabel"], 0, 0, 1, 1) -w1_habitat.addWidget(widgets["sondehubUploadSelector"], 0, 1, 1, 1) -w1_habitat.addWidget(widgets["userCallLabel"], 1, 0, 1, 1) -w1_habitat.addWidget(widgets["userCallEntry"], 1, 1, 1, 2) -w1_habitat.addWidget(widgets["userLocationLabel"], 2, 0, 1, 1) -w1_habitat.addWidget(widgets["userLatEntry"], 2, 1, 1, 1) -w1_habitat.addWidget(widgets["userLonEntry"], 2, 2, 1, 1) -w1_habitat.addWidget(widgets["userAltitudeLabel"], 3, 0, 1, 1) -w1_habitat.addWidget(widgets["userAltEntry"], 3, 1, 1, 2) -w1_habitat.addWidget(widgets["userAntennaLabel"], 4, 0, 1, 1) -w1_habitat.addWidget(widgets["userAntennaEntry"], 4, 1, 1, 2) -w1_habitat.addWidget(widgets["userRadioLabel"], 5, 0, 1, 1) -w1_habitat.addWidget(widgets["userRadioEntry"], 5, 1, 1, 2) -w1_habitat.addWidget(widgets["dialFreqLabel"], 6, 0, 1, 1) -w1_habitat.addWidget(widgets["dialFreqEntry"], 6, 1, 1, 2) -w1_habitat.addWidget(widgets["habitatUploadPosition"], 7, 0, 1, 3) -w1_habitat.addWidget(widgets["sondehubPositionNotesLabel"], 8, 0, 1, 3) -w1_habitat.layout.setRowStretch(9, 1) -w1_habitat.addWidget(widgets["saveSettingsButton"], 10, 0, 1, 3) - -d0_habitat.addWidget(w1_habitat) - -w1_other = pg.LayoutWidget() -widgets["horusHeaderLabel"] = QtWidgets.QLabel("Telemetry Forwarding") -widgets["horusUploadLabel"] = QtWidgets.QLabel("Enable Horus UDP Output:") -widgets["horusUploadSelector"] = QtWidgets.QCheckBox() -widgets["horusUploadSelector"].setChecked(True) -widgets["horusUploadSelector"].setToolTip( - "Enable output of 'Horus UDP' JSON messages. These are emitted as a JSON object\n"\ - "and contain the fields: callsign, time, latitude, longitude, altitude, snr"\ -) -widgets["horusUDPLabel"] = QtWidgets.QLabel("Horus UDP Port:") -widgets["horusUDPEntry"] = QtWidgets.QLineEdit("55672") -widgets["horusUDPEntry"].setMaxLength(5) -widgets["horusUDPEntry"].setToolTip( - "UDP Port to output 'Horus UDP' JSON messages to." -) -widgets["ozimuxUploadLabel"] = QtWidgets.QLabel("Enable OziMux UDP Output:") -widgets["ozimuxUploadSelector"] = QtWidgets.QCheckBox() -widgets["ozimuxUploadSelector"].setChecked(False) -widgets["ozimuxUploadSelector"].setToolTip( - "Output OziMux UDP messages. These are of the form:\n"\ - "'TELEMETRY,HH:MM:SS,lat,lon,alt\\n'" -) -widgets["ozimuxUDPLabel"] = QtWidgets.QLabel("Ozimux UDP Port:") -widgets["ozimuxUDPEntry"] = QtWidgets.QLineEdit("55683") -widgets["ozimuxUDPEntry"].setMaxLength(5) -widgets["ozimuxUDPEntry"].setToolTip( - "UDP Port to output 'OziMux' UDP messages to." -) -widgets["loggingHeaderLabel"] = QtWidgets.QLabel("Logging") -widgets["enableLoggingLabel"] = QtWidgets.QLabel("Enable Logging:") -widgets["enableLoggingSelector"] = QtWidgets.QCheckBox() -widgets["enableLoggingSelector"].setChecked(False) -widgets["enableLoggingSelector"].setToolTip( - "Enable logging of received telemetry to disk (JSON)" -) -widgets["loggingFormatLabel"] = QtWidgets.QLabel("Log Format:") -widgets["loggingFormatSelector"] = QtWidgets.QComboBox() -widgets["loggingFormatSelector"].addItem("CSV") -widgets["loggingFormatSelector"].addItem("JSON") -widgets["loggingPathLabel"] = QtWidgets.QLabel("Log Directory:") -widgets["loggingPathEntry"] = QtWidgets.QLineEdit("") -widgets["loggingPathEntry"].setToolTip( - "Logging Directory" -) -widgets["selectLogDirButton"] = QtWidgets.QPushButton("Select Directory") - -widgets["otherHeaderLabel"] = QtWidgets.QLabel("Other Settings") -widgets["inhibitCRCLabel"] = QtWidgets.QLabel("Hide Failed CRC Errors:") -widgets["inhibitCRCSelector"] = QtWidgets.QCheckBox() -widgets["inhibitCRCSelector"].setChecked(True) -widgets["inhibitCRCSelector"].setToolTip( - "Hide CRC Failed error messages." -) - -w1_other.addWidget(widgets["horusHeaderLabel"], 0, 0, 1, 2) -w1_other.addWidget(widgets["horusUploadLabel"], 1, 0, 1, 1) -w1_other.addWidget(widgets["horusUploadSelector"], 1, 1, 1, 1) -w1_other.addWidget(widgets["horusUDPLabel"], 2, 0, 1, 1) -w1_other.addWidget(widgets["horusUDPEntry"], 2, 1, 1, 1) -w1_other.addWidget(widgets["ozimuxUploadLabel"], 3, 0, 1, 1) -w1_other.addWidget(widgets["ozimuxUploadSelector"], 3, 1, 1, 1) -w1_other.addWidget(widgets["ozimuxUDPLabel"], 4, 0, 1, 1) -w1_other.addWidget(widgets["ozimuxUDPEntry"], 4, 1, 1, 1) -w1_other.addWidget(widgets["loggingHeaderLabel"], 5, 0, 1, 2) -w1_other.addWidget(widgets["enableLoggingLabel"], 6, 0, 1, 1) -w1_other.addWidget(widgets["enableLoggingSelector"], 6, 1, 1, 1) -w1_other.addWidget(widgets["loggingFormatLabel"], 7, 0, 1, 1) -w1_other.addWidget(widgets["loggingFormatSelector"], 7, 1, 1, 1) -w1_other.addWidget(widgets["loggingPathLabel"], 8, 0, 1, 1) -w1_other.addWidget(widgets["loggingPathEntry"], 8, 1, 1, 1) -w1_other.addWidget(widgets["selectLogDirButton"], 9, 0, 1, 2) -w1_other.addWidget(widgets["otherHeaderLabel"], 10, 0, 1, 2) -w1_other.addWidget(widgets["inhibitCRCLabel"], 11, 0, 1, 1) -w1_other.addWidget(widgets["inhibitCRCSelector"], 11, 1, 1, 1) -w1_other.layout.setRowStretch(12, 1) - -d0_other.addWidget(w1_other) - - -w1_rotator = pg.LayoutWidget() -widgets["rotatorHeaderLabel"] = QtWidgets.QLabel("Rotator Control") - -widgets["rotatorTypeLabel"] = QtWidgets.QLabel("Rotator Type:") -widgets["rotatorTypeSelector"] = QtWidgets.QComboBox() -widgets["rotatorTypeSelector"].addItem("rotctld") -widgets["rotatorTypeSelector"].addItem("PSTRotator") - -widgets["rotatorHostLabel"] = QtWidgets.QLabel("Rotator Hostname:") -widgets["rotatorHostEntry"] = QtWidgets.QLineEdit("localhost") -widgets["rotatorHostEntry"].setToolTip( - "Hostname of the rotctld or PSTRotator Server.\n"\ -) - -widgets["rotatorPortLabel"] = QtWidgets.QLabel("Rotator TCP/UDP Port:") -widgets["rotatorPortEntry"] = QtWidgets.QLineEdit("4533") -widgets["rotatorPortEntry"].setMaxLength(5) -widgets["rotatorPortEntry"].setToolTip( - "TCP (rotctld) or UDP (PSTRotator) port to connect to.\n"\ - "Default for rotctld: 4533\n"\ - "Default for PSTRotator: 12000" -) -widgets["rotatorThresholdLabel"] = QtWidgets.QLabel("Rotator Movement Threshold:") -widgets["rotatorThresholdEntry"] = QtWidgets.QLineEdit("5.0") -widgets["rotatorThresholdEntry"].setToolTip( - "Only move if the angle between the payload position and \n"\ - "the current rotator position is more than this, in degrees." -) - -widgets["rotatorConnectButton"] = QtWidgets.QPushButton("Start") - -widgets["rotatorCurrentStatusLabel"] = QtWidgets.QLabel("Status:") -widgets["rotatorCurrentStatusValue"] = QtWidgets.QLabel("Not Started.") - -widgets["rotatorCurrentPositionLabel"] = QtWidgets.QLabel("Commanded Az/El:") -widgets["rotatorCurrentPositionValue"] = QtWidgets.QLabel("---˚, --˚") - - - -w1_rotator.addWidget(widgets["rotatorHeaderLabel"], 0, 0, 1, 2) -w1_rotator.addWidget(widgets["rotatorTypeLabel"], 1, 0, 1, 1) -w1_rotator.addWidget(widgets["rotatorTypeSelector"], 1, 1, 1, 1) -w1_rotator.addWidget(widgets["rotatorHostLabel"], 2, 0, 1, 1) -w1_rotator.addWidget(widgets["rotatorHostEntry"], 2, 1, 1, 1) -w1_rotator.addWidget(widgets["rotatorPortLabel"], 3, 0, 1, 1) -w1_rotator.addWidget(widgets["rotatorPortEntry"], 3, 1, 1, 1) -#w1_rotator.addWidget(widgets["rotatorThresholdLabel"], 4, 0, 1, 1) -#w1_rotator.addWidget(widgets["rotatorThresholdEntry"], 4, 1, 1, 1) -w1_rotator.addWidget(widgets["rotatorConnectButton"], 4, 0, 1, 2) -w1_rotator.addWidget(widgets["rotatorCurrentStatusLabel"], 5, 0, 1, 1) -w1_rotator.addWidget(widgets["rotatorCurrentStatusValue"], 5, 1, 1, 1) -w1_rotator.addWidget(widgets["rotatorCurrentPositionLabel"], 6, 0, 1, 1) -w1_rotator.addWidget(widgets["rotatorCurrentPositionValue"], 6, 1, 1, 1) - -w1_rotator.layout.setRowStretch(7, 1) - -d0_rotator.addWidget(w1_rotator) - - -# Spectrum Display -widgets["spectrumPlot"] = pg.PlotWidget(title="Spectra") -widgets["spectrumPlot"].setLabel("left", "Power (dB)") -widgets["spectrumPlot"].setLabel("bottom", "Frequency (Hz)") -widgets["spectrumPlotData"] = widgets["spectrumPlot"].plot([0]) - -# Frequency Estiator Outputs -widgets["estimatorLines"] = [ - pg.InfiniteLine( - pos=-1000, - pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), - label="F1", - labelOpts={'position':0.9} - ), - pg.InfiniteLine( - pos=-1000, - pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), - label="F2", - labelOpts={'position':0.9} - ), - pg.InfiniteLine( - pos=-1000, - pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), - label="F3", - labelOpts={'position':0.9} - ), - pg.InfiniteLine( - pos=-1000, - pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), - label="F4", - labelOpts={'position':0.9} - ), -] -for _line in widgets["estimatorLines"]: - widgets["spectrumPlot"].addItem(_line) - -widgets["spectrumPlot"].setLabel("left", "Power (dBFs)") -widgets["spectrumPlot"].setLabel("bottom", "Frequency", units="Hz") -widgets["spectrumPlot"].setXRange(100, 4000) -widgets["spectrumPlot"].setYRange(-100, -20) -widgets["spectrumPlot"].setLimits(xMin=100, xMax=4000, yMin=-120, yMax=0) -widgets["spectrumPlot"].showGrid(True, True) - -widgets["estimatorRange"] = pg.LinearRegionItem([100,3000]) -widgets["estimatorRange"].setBounds([100,4000]) - -d1.addWidget(widgets["spectrumPlot"]) - -widgets["spectrumPlotRange"] = [-100, -20] - - -w3_stats = pg.LayoutWidget() -widgets["snrBar"] = QtWidgets.QProgressBar() -widgets["snrBar"].setOrientation(QtCore.Qt.Orientation.Vertical) -widgets["snrBar"].setRange(-10, 15) -widgets["snrBar"].setValue(-10) -widgets["snrBar"].setTextVisible(False) -widgets["snrBar"].setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) -widgets["snrLabel"] = QtWidgets.QLabel("--.-") -widgets["snrLabel"].setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter); -widgets["snrLabel"].setFont(QtGui.QFont("Courier New", 14)) -w3_stats.addWidget(widgets["snrBar"], 0, 1, 1, 1) -w3_stats.addWidget(widgets["snrLabel"], 1, 0, 1, 3) -w3_stats.layout.setColumnStretch(0, 2) -w3_stats.layout.setColumnStretch(2, 2) - -d2_stats.addWidget(w3_stats) - -# SNR Plot -w3_snr = pg.LayoutWidget() -widgets["snrPlot"] = pg.PlotWidget(title="SNR") -widgets["snrPlot"].setLabel("left", "SNR (dB)") -widgets["snrPlot"].setLabel("bottom", "Time (s)") -widgets["snrPlot"].setXRange(-60, 0) -widgets["snrPlot"].setYRange(-10, 30) -widgets["snrPlot"].setLimits(xMin=-60, xMax=0, yMin=-10, yMax=40) -widgets["snrPlot"].showGrid(True, True) -widgets["snrPlotRange"] = [-10, 30] -widgets["snrPlotTime"] = np.array([]) -widgets["snrPlotSNR"] = np.array([]) -widgets["snrPlotData"] = widgets["snrPlot"].plot(widgets["snrPlotTime"], widgets["snrPlotSNR"]) - -# TODO: Look into eye diagram more -# widgets["eyeDiagramPlot"] = pg.PlotWidget(title="Eye Diagram") -# widgets["eyeDiagramData"] = widgets["eyeDiagramPlot"].plot([0]) - -#w3_snr.addWidget(widgets["snrPlot"], 0, 1, 2, 1) - -#w3.addWidget(widgets["eyeDiagramPlot"], 0, 1) - -d2_snr.addWidget(widgets["snrPlot"]) - -# Telemetry Data -w4_data = pg.LayoutWidget() -widgets["latestRawSentenceLabel"] = QtWidgets.QLabel("Latest Packet (Raw):") -widgets["latestRawSentenceData"] = QtWidgets.QLineEdit("NO DATA") -widgets["latestRawSentenceData"].setReadOnly(True) -widgets["latestDecodedSentenceLabel"] = QtWidgets.QLabel("Latest Packet (Decoded):") -widgets["latestDecodedSentenceData"] = QtWidgets.QLineEdit("NO DATA") -widgets["latestDecodedSentenceData"].setReadOnly(True) -widgets["latestDecodedAgeLabel"] = QtWidgets.QLabel("Last Packet Age:") -widgets["latestDecodedAgeData"] = QtWidgets.QLabel("No packet yet!") -w4_data.addWidget(widgets["latestRawSentenceLabel"], 0, 0, 1, 1) -w4_data.addWidget(widgets["latestRawSentenceData"], 0, 1, 1, 6) -w4_data.addWidget(widgets["latestDecodedSentenceLabel"], 1, 0, 1, 1) -w4_data.addWidget(widgets["latestDecodedSentenceData"], 1, 1, 1, 6) -w4_data.addWidget(widgets["latestDecodedAgeLabel"], 2, 0, 1, 1) -w4_data.addWidget(widgets["latestDecodedAgeData"], 2, 1, 1, 2) -d3_data.addWidget(w4_data) - -w4_position = pg.LayoutWidget() -# This font seems to look bigger in Windows... not sure why. -if 'Windows' in platform.system(): - POSITION_LABEL_FONT_SIZE = 14 -else: - POSITION_LABEL_FONT_SIZE = 16 - -widgets["latestPacketCallsignLabel"] = QtWidgets.QLabel("Callsign") -widgets["latestPacketCallsignValue"] = QtWidgets.QLabel("---") -widgets["latestPacketCallsignValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold)) -widgets["latestPacketTimeLabel"] = QtWidgets.QLabel("Time") -widgets["latestPacketTimeValue"] = QtWidgets.QLabel("---") -widgets["latestPacketTimeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold)) -widgets["latestPacketLatitudeLabel"] = QtWidgets.QLabel("Latitude") -widgets["latestPacketLatitudeValue"] = QtWidgets.QLabel("---") -widgets["latestPacketLatitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold)) -widgets["latestPacketLongitudeLabel"] = QtWidgets.QLabel("Longitude") -widgets["latestPacketLongitudeValue"] = QtWidgets.QLabel("---") -widgets["latestPacketLongitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold)) -widgets["latestPacketAltitudeLabel"] = QtWidgets.QLabel("Altitude") -widgets["latestPacketAltitudeValue"] = QtWidgets.QLabel("---") -widgets["latestPacketAltitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold)) -widgets["latestPacketBearingLabel"] = QtWidgets.QLabel("Bearing") -widgets["latestPacketBearingValue"] = QtWidgets.QLabel("---") -widgets["latestPacketBearingValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold)) -widgets["latestPacketElevationLabel"] = QtWidgets.QLabel("Elevation") -widgets["latestPacketElevationValue"] = QtWidgets.QLabel("---") -widgets["latestPacketElevationValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold)) -widgets["latestPacketRangeLabel"] = QtWidgets.QLabel("Range (km)") -widgets["latestPacketRangeValue"] = QtWidgets.QLabel("---") -widgets["latestPacketRangeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold)) - -w4_position.addWidget(widgets["latestPacketCallsignLabel"], 0, 0, 1, 2) -w4_position.addWidget(widgets["latestPacketCallsignValue"], 1, 0, 1, 2) -w4_position.addWidget(widgets["latestPacketTimeLabel"], 0, 2, 1, 1) -w4_position.addWidget(widgets["latestPacketTimeValue"], 1, 2, 1, 1) -w4_position.addWidget(widgets["latestPacketLatitudeLabel"], 0, 3, 1, 1) -w4_position.addWidget(widgets["latestPacketLatitudeValue"], 1, 3, 1, 1) -w4_position.addWidget(widgets["latestPacketLongitudeLabel"], 0, 4, 1, 1) -w4_position.addWidget(widgets["latestPacketLongitudeValue"], 1, 4, 1, 1) -w4_position.addWidget(widgets["latestPacketAltitudeLabel"], 0, 5, 1, 1) -w4_position.addWidget(widgets["latestPacketAltitudeValue"], 1, 5, 1, 1) -w4_position.addWidget(widgets["latestPacketBearingLabel"], 0, 7, 1, 1) -w4_position.addWidget(widgets["latestPacketBearingValue"], 1, 7, 1, 1) -w4_position.addWidget(widgets["latestPacketElevationLabel"], 0, 8, 1, 1) -w4_position.addWidget(widgets["latestPacketElevationValue"], 1, 8, 1, 1) -w4_position.addWidget(widgets["latestPacketRangeLabel"], 0, 9, 1, 1) -w4_position.addWidget(widgets["latestPacketRangeValue"], 1, 9, 1, 1) -w4_position.layout.setRowStretch(1, 6) -d3_position.addWidget(w4_position) - -w5 = pg.LayoutWidget() -widgets["console"] = QtWidgets.QPlainTextEdit() -widgets["console"].setReadOnly(True) -w5.addWidget(widgets["console"]) -d4.addWidget(w5) - -# Resize window to final resolution, and display. -logging.info("Starting GUI.") -win.resize(1500, 800) -win.show() - -# Audio Initialization -audio_devices = init_audio(widgets) - - -def update_audio_sample_rates(): - """ Update the sample-rate dropdown when a different audio device is selected. """ - global widgets - # Pass widgets straight on to function from .audio - populate_sample_rates(widgets) - - -widgets["audioDeviceSelector"].currentIndexChanged.connect(update_audio_sample_rates) - -# Initialize modem list. -init_horus_modem(widgets) - - -def update_modem_settings(): - """ Update the modem setting widgets when a different modem is selected """ - global widgets - populate_modem_settings(widgets) - -widgets["horusModemSelector"].currentIndexChanged.connect(update_modem_settings) - - -def select_log_directory(): - global widgets - - folder = str(QtWidgets.QFileDialog.getExistingDirectory(None, "Select Directory")) - - if folder is None: - logging.info("No log directory selected.") - return False - else: - if folder == "": - logging.info("No log directory selected.") - return False - else: - widgets["loggingPathEntry"].setText(folder) - widgets["enableLoggingSelector"].setChecked(False) - if telemetry_logger: - widgets["enableLoggingSelector"].setChecked(True) - telemetry_logger.update_log_directory(widgets["loggingPathEntry"].text()) - telemetry_logger.enabled = True - - return True - -widgets["selectLogDirButton"].clicked.connect(select_log_directory) - - -def set_logging_state(): - global widgets - - logging_enabled = widgets["enableLoggingSelector"].isChecked() - - if logging_enabled: - if widgets["loggingPathEntry"].text() == "": - # No logging directory set, prompt user to select one. - _success = select_log_directory() - if not _success: - # User didn't select a directory, set checkbox to false again. - logging.error("No log directory selected, logging disabled.") - widgets["enableLoggingSelector"].setChecked(False) - # Disable logging. - if telemetry_logger: - telemetry_logger.enabled = False - - return - - # Enable logging - if telemetry_logger: - telemetry_logger.enabled = True - telemetry_logger.update_log_directory(widgets["loggingPathEntry"].text()) - - else: - # Disable logging - if telemetry_logger: - telemetry_logger.enabled = False - -widgets["enableLoggingSelector"].clicked.connect(set_logging_state) - -def set_logging_format(): - if telemetry_logger: - telemetry_logger.log_format = widgets["loggingFormatSelector"].currentText() - -widgets["loggingFormatSelector"].currentIndexChanged.connect(set_logging_format) - -# Clear the configuration if we have been asked to, otherwise read it in from Qt stores -if args.reset: - logging.info("Clearing configuration.") - write_config() -else: - read_config(widgets) - - -try: - if float(widgets["userLatEntry"].text()) == 0.0 and float(widgets["userLonEntry"].text()) == 0.0: - _sondehub_user_pos = None - else: - _sondehub_user_pos = [float(widgets["userLatEntry"].text()), float(widgets["userLonEntry"].text()), 0.0] -except: - _sondehub_user_pos = None - -sondehub_uploader = SondehubAmateurUploader( - upload_rate = 2, - user_callsign = widgets["userCallEntry"].text(), - user_position = _sondehub_user_pos, - user_radio = "Horus-GUI v" + __version__ + " " + widgets["userRadioEntry"].text(), - user_antenna = widgets["userAntennaEntry"].text(), - software_name = "Horus-GUI", - software_version = __version__, -) - -telemetry_logger = TelemetryLogger( - log_directory = widgets["loggingPathEntry"].text(), - log_format = widgets["loggingFormatSelector"].currentText(), - enabled = widgets["enableLoggingSelector"].isChecked() -) - -# Handlers for various checkboxes and push-buttons -def habitat_position_reupload(dummy_arg, upload=True): - """ - Trigger a re-upload of user position information - Note that this requires a dummy argument, as the Qt - 'connect' callback supplies an argument which we don't want. - """ - global widgets, sondehub_uploader - - sondehub_uploader.user_callsign = widgets["userCallEntry"].text() - sondehub_uploader.user_radio = "Horus-GUI v" + __version__ + " " + widgets["userRadioEntry"].text() - sondehub_uploader.user_antenna = widgets["userAntennaEntry"].text() - try: - if float(widgets["userLatEntry"].text()) == 0.0 and float(widgets["userLonEntry"].text()) == 0.0: - sondehub_uploader.user_position = None - else: - sondehub_uploader.user_position = [ - float(widgets["userLatEntry"].text()), - float(widgets["userLonEntry"].text()), - float(widgets["userAltEntry"].text())] - except Exception as e: - logging.error(f"Error parsing station location - {str(e)}") - sondehub_uploader.user_position = None - - if upload: - sondehub_uploader.last_user_position_upload = 0 - widgets["sondehubPositionNotesLabel"].setText("") - logging.info("Triggered user position re-upload.") - -# Connect the 'Re-upload Position' button to the above function. -widgets["habitatUploadPosition"].clicked.connect(habitat_position_reupload) - - -# Update uploader info as soon as it's edited, to ensure we upload with the latest user callsign -def update_uploader_details(): - """ - Wrapper function for position re-upload, called when the user callsign entry is changed. - """ - #habitat_position_reupload("unused arg",upload=False) - widgets["sondehubPositionNotesLabel"].setText("
Station Info out of date - click Re-Upload!
") - -# Connect all the station information fields to this function, so that when the user -# changes any of them they get a prompt to click the re-upload button. -widgets["userCallEntry"].textEdited.connect(update_uploader_details) -widgets["userRadioEntry"].textEdited.connect(update_uploader_details) -widgets["userAntennaEntry"].textEdited.connect(update_uploader_details) -widgets["userLatEntry"].textEdited.connect(update_uploader_details) -widgets["userLonEntry"].textEdited.connect(update_uploader_details) -widgets["userAltEntry"].textEdited.connect(update_uploader_details) - - -def habitat_inhibit(): - """ Update the Habitat inhibit flag """ - global widgets, sondehub_uploader - sondehub_uploader.inhibit = not widgets["sondehubUploadSelector"].isChecked() - logging.debug(f"Updated Sondebub Inhibit state: {sondehub_uploader.inhibit}") - -widgets["sondehubUploadSelector"].clicked.connect(habitat_inhibit) - - -def update_manual_estimator(): - """ Push a change to the manually defined estimator limits into the modem """ - global widgets, horus_modem - - _limits = widgets["estimatorRange"].getRegion() - - _lower = _limits[0] - _upper = _limits[1] - - if horus_modem != None: - horus_modem.set_estimator_limits(_lower, _upper) - -widgets["estimatorRange"].sigRegionChangeFinished.connect(update_manual_estimator) - - -def set_manual_estimator(): - """ Show or hide the manual estimator limit region """ - global widgets - if widgets["horusManualEstimatorSelector"].isChecked(): - widgets["spectrumPlot"].addItem(widgets["estimatorRange"]) - update_manual_estimator() - else: +# Establish signals and worker for multi-threaded use +class WorkerSignals(QObject): + finished = pyqtSignal() + error = pyqtSignal(tuple) + result = pyqtSignal(object) + info = pyqtSignal(object) + +class Worker(QRunnable): + def __init__(self, fn, *args, **kwargs): + super(Worker, self).__init__() + + self.fn = fn + self.args = args + self.kwargs = kwargs + self.signals = WorkerSignals() + + self.kwargs['info_callback'] = self.signals.info + + @pyqtSlot() + def run(self): try: - widgets["spectrumPlot"].removeItem(widgets["estimatorRange"]) - # Reset modem estimator limits to their defaults. - if horus_modem != None: - horus_modem.set_estimator_limits(DEFAULT_ESTIMATOR_MIN, DEFAULT_ESTIMATOR_MAX) + result = self.fn(*self.args, **self.kwargs) + except: + traceback.print_exc() + exctype, value = sys.exc_info()[:2] + self.signals.error.emit((exctype, value, traceback.format_exc())) + else: + self.signals.result.emit(result) + finally: + self.signals.finished.emit() + +def resource_path(relative_path): + try: + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + + return os.path.join(base_path, relative_path) + + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.resize(1500, 800) + + self.threadpool = QThreadPool() + self.stop_signal = False + + # A few hardcoded defaults + self.DEFAULT_ESTIMATOR_MIN = 100 + self.DEFAULT_ESTIMATOR_MAX = 4000 + + + # Global widget store + self.widgets = {} + + # Queues for handling updates to image / status indications. + self.fft_update_queue = Queue(1024) + self.status_update_queue = Queue(1024) + self.log_update_queue = Queue(2048) + + # List of audio devices and their info + self.audio_devices = {} + + # Processor objects + self.audio_stream = None + self.fft_process = None + self.horus_modem = None + self.sondehub_uploader = None + self.telemetry_logger = None + + self.decoder_init = False + + self.last_packet_time = None + + # Rotator object + self.rotator = None + self.rotator_current_az = 0.0 + self.rotator_current_el = 0.0 + + + # Global running indicator + self.running = False + + self.initialize() + + def initialize(self): + # + # GUI Creation - The Bad way. + # + + # Create a Qt App. + pg.mkQApp() + + # GUI LAYOUT - Gtk Style! + area = DockArea() + self.setCentralWidget(area) + self.setWindowTitle(f"Horus Telemetry GUI - v{__version__}") + self.setWindowIcon(getHorusIcon()) + + # Create multiple dock areas, for displaying our data. + d0 = Dock("Audio", size=(300, 50)) + d0_modem = Dock("Modem", size=(300, 80)) + d0_habitat = Dock("SondeHub", size=(300, 200)) + d0_other = Dock("Other", size=(300, 100)) + d0_rotator = Dock("Rotator", size=(300, 100)) + d1 = Dock("Spectrum", size=(800, 350)) + d2_stats = Dock("SNR (dB)", size=(50, 300)) + d2_snr = Dock("SNR Plot", size=(750, 300)) + d3_data = Dock("Data", size=(800, 50)) + d3_position = Dock("Position", size=(800, 50)) + d4 = Dock("Log", size=(800, 150)) + # Arrange docks. + area.addDock(d0) + area.addDock(d1, "right", d0) + area.addDock(d0_modem, "bottom", d0) + area.addDock(d0_habitat, "bottom", d0_modem) + area.addDock(d0_other, "below", d0_habitat) + area.addDock(d0_rotator, "below", d0_other) + area.addDock(d2_stats, "bottom", d1) + area.addDock(d3_data, "bottom", d2_stats) + area.addDock(d3_position, "bottom", d3_data) + area.addDock(d4, "bottom", d3_position) + area.addDock(d2_snr, "right", d2_stats) + d0_habitat.raiseDock() + + + # Controls + w1_audio = pg.LayoutWidget() + # TNC Connection + self.widgets["audioDeviceLabel"] = QLabel("Audio Device:") + self.widgets["audioDeviceSelector"] = QComboBox() + self.widgets["audioDeviceSelector"].currentIndexChanged.connect(self.update_audio_sample_rates) + + self.widgets["audioSampleRateLabel"] = QLabel("Sample Rate (Hz):") + self.widgets["audioSampleRateSelector"] = QComboBox() + + self.widgets["audioDbfsLabel"] = QLabel("Input Level (dBFS):") + self.widgets["audioDbfsValue"] = QLabel("--") + self.widgets["audioDbfsValue_float"] = 0.0 + + w1_audio.addWidget(self.widgets["audioDeviceLabel"], 0, 0, 1, 1) + w1_audio.addWidget(self.widgets["audioDeviceSelector"], 0, 1, 1, 2) + w1_audio.addWidget(self.widgets["audioSampleRateLabel"], 1, 0, 1, 1) + w1_audio.addWidget(self.widgets["audioSampleRateSelector"], 1, 1, 1, 2) + w1_audio.addWidget(self.widgets["audioDbfsLabel"], 2, 0, 1, 1) + w1_audio.addWidget(self.widgets["audioDbfsValue"], 2, 1, 1, 2) + d0.addWidget(w1_audio) + + w1_modem = pg.LayoutWidget() + + + # Modem Parameters + self.widgets["horusModemLabel"] = QLabel("Mode:") + self.widgets["horusModemSelector"] = QComboBox() + self.widgets["horusModemSelector"].currentIndexChanged.connect(self.update_modem_settings) + + self.widgets["horusModemRateLabel"] = QLabel("Baudrate:") + self.widgets["horusModemRateSelector"] = QComboBox() + + self.widgets["horusMaskEstimatorLabel"] = QLabel("Enable Mask Estim.:") + self.widgets["horusMaskEstimatorSelector"] = QCheckBox() + self.widgets["horusMaskEstimatorSelector"].setToolTip( + "Enable the mask frequency estimator, which makes uses of the \n"\ + "tone spacing value entered below as extra input to the frequency\n"\ + "estimator. This can help decode performance in very weak signal conditions." + ) + + self.widgets["horusMaskSpacingLabel"] = QLabel("Tone Spacing (Hz):") + self.widgets["horusMaskSpacingEntry"] = QLineEdit("270") + self.widgets["horusMaskSpacingEntry"].setToolTip( + "If the tone spacing of the transmitter is known, it can be entered here,\n"\ + "and used with the mask estimator option above. The default tone spacing for\n"\ + "a RS41-based transmitter is 270 Hz." + ) + self.widgets["horusManualEstimatorLabel"] = QLabel("Manual Estim. Limits:") + self.widgets["horusManualEstimatorSelector"] = QCheckBox() + self.widgets["horusManualEstimatorSelector"].setToolTip( + "Enables manual selection of the frequency estimator limits. This will enable\n"\ + "a slidable area on the spectrum display, which can be used to select the frequency\n"\ + "range of interest, and help stop in-band CW interference from biasing the frequency\n"\ + "estimator. You can either click-and-drag the entire area, or click-and-drag the edges\n"\ + "to change the estimator frequency range." + ) + self.widgets["horusManualEstimatorSelector"].clicked.connect(self.set_manual_estimator) + + # Start/Stop + self.widgets["startDecodeButton"] = QPushButton("Start") + self.widgets["startDecodeButton"].setEnabled(False) + self.widgets["startDecodeButton"].clicked.connect(self.start_decoding) + + w1_modem.addWidget(self.widgets["horusModemLabel"], 0, 0, 1, 1) + w1_modem.addWidget(self.widgets["horusModemSelector"], 0, 1, 1, 1) + w1_modem.addWidget(self.widgets["horusModemRateLabel"], 1, 0, 1, 1) + w1_modem.addWidget(self.widgets["horusModemRateSelector"], 1, 1, 1, 1) + w1_modem.addWidget(self.widgets["horusMaskEstimatorLabel"], 2, 0, 1, 1) + w1_modem.addWidget(self.widgets["horusMaskEstimatorSelector"], 2, 1, 1, 1) + w1_modem.addWidget(self.widgets["horusMaskSpacingLabel"], 3, 0, 1, 1) + w1_modem.addWidget(self.widgets["horusMaskSpacingEntry"], 3, 1, 1, 1) + w1_modem.addWidget(self.widgets["horusManualEstimatorLabel"], 4, 0, 1, 1) + w1_modem.addWidget(self.widgets["horusManualEstimatorSelector"], 4, 1, 1, 1) + w1_modem.addWidget(self.widgets["startDecodeButton"], 5, 0, 2, 2) + + d0_modem.addWidget(w1_modem) + + + w1_habitat = pg.LayoutWidget() + # Listener Information + self.widgets["habitatHeading"] = QLabel("SondeHub Settings") + self.widgets["sondehubUploadLabel"] = QLabel("Enable SondeHub-Ham Upload:") + self.widgets["sondehubUploadSelector"] = QCheckBox() + self.widgets["sondehubUploadSelector"].setChecked(True) + self.widgets["sondehubUploadSelector"].clicked.connect(self.habitat_inhibit) + self.widgets["userCallLabel"] = QLabel("Callsign:") + self.widgets["userCallEntry"] = QLineEdit("N0CALL") + self.widgets["userCallEntry"].setMaxLength(20) + self.widgets["userCallEntry"].setToolTip( + "Your station callsign, which doesn't necessarily need to be an\n"\ + "amateur radio callsign, just something unique!" + ) + self.widgets["userCallEntry"].textEdited.connect(self.update_uploader_details) + self.widgets["userLocationLabel"] = QLabel("Latitude / Longitude:") + self.widgets["userLatEntry"] = QLineEdit("0.0") + self.widgets["userLatEntry"].setToolTip("Station Latitude in Decimal Degrees, e.g. -34.123456") + self.widgets["userLatEntry"].textEdited.connect(self.update_uploader_details) + self.widgets["userLonEntry"] = QLineEdit("0.0") + self.widgets["userLonEntry"].setToolTip("Station Longitude in Decimal Degrees, e.g. 138.123456") + self.widgets["userLonEntry"].textEdited.connect(self.update_uploader_details) + self.widgets["userAltitudeLabel"] = QLabel("Altitude:") + self.widgets["userAltEntry"] = QLineEdit("0.0") + self.widgets["userAltEntry"].setToolTip("Station Altitude in Metres Above Sea Level.") + self.widgets["userAltEntry"].textEdited.connect(self.update_uploader_details) + self.widgets["userAntennaLabel"] = QLabel("Antenna:") + self.widgets["userAntennaEntry"] = QLineEdit("") + self.widgets["userAntennaEntry"].setToolTip("A text description of your station's antenna.") + self.widgets["userAntennaEntry"].textEdited.connect(self.update_uploader_details) + self.widgets["userRadioLabel"] = QLabel("Radio:") + self.widgets["userRadioEntry"] = QLineEdit("Horus-GUI " + __version__) + self.widgets["userRadioEntry"].setToolTip( + "A text description of your station's radio setup.\n"\ + "This field will be automatically prefixed with Horus-GUI\n"\ + "and the Horus-GUI software version." + ) + self.widgets["userRadioEntry"].textEdited.connect(self.update_uploader_details) + self.widgets["habitatUploadPosition"] = QPushButton("Re-upload Station Info") + self.widgets["habitatUploadPosition"].setToolTip( + "Manually re-upload your station information to SondeHub-Amateur.\n"\ + ) + # Connect the 'Re-upload Position' button to the above function. + self.widgets["habitatUploadPosition"].clicked.connect(self.habitat_position_reupload) + self.widgets["dialFreqLabel"] = QLabel("Radio Dial Freq (MHz):") + self.widgets["dialFreqEntry"] = QLineEdit("") + self.widgets["dialFreqEntry"].setToolTip( + "Optional entry of your radio's dial frequency in MHz (e.g. 437.600).\n"\ + "Used to provide frequency information on SondeHub-Amateur."\ + ) + self.widgets["sondehubPositionNotesLabel"] = QLabel("") + + self.widgets["saveSettingsButton"] = QPushButton("Save Settings") + self.widgets["saveSettingsButton"].clicked.connect(self.save_settings) + + w1_habitat.addWidget(self.widgets["sondehubUploadLabel"], 0, 0, 1, 1) + w1_habitat.addWidget(self.widgets["sondehubUploadSelector"], 0, 1, 1, 1) + w1_habitat.addWidget(self.widgets["userCallLabel"], 1, 0, 1, 1) + w1_habitat.addWidget(self.widgets["userCallEntry"], 1, 1, 1, 2) + w1_habitat.addWidget(self.widgets["userLocationLabel"], 2, 0, 1, 1) + w1_habitat.addWidget(self.widgets["userLatEntry"], 2, 1, 1, 1) + w1_habitat.addWidget(self.widgets["userLonEntry"], 2, 2, 1, 1) + w1_habitat.addWidget(self.widgets["userAltitudeLabel"], 3, 0, 1, 1) + w1_habitat.addWidget(self.widgets["userAltEntry"], 3, 1, 1, 2) + w1_habitat.addWidget(self.widgets["userAntennaLabel"], 4, 0, 1, 1) + w1_habitat.addWidget(self.widgets["userAntennaEntry"], 4, 1, 1, 2) + w1_habitat.addWidget(self.widgets["userRadioLabel"], 5, 0, 1, 1) + w1_habitat.addWidget(self.widgets["userRadioEntry"], 5, 1, 1, 2) + w1_habitat.addWidget(self.widgets["dialFreqLabel"], 6, 0, 1, 1) + w1_habitat.addWidget(self.widgets["dialFreqEntry"], 6, 1, 1, 2) + w1_habitat.addWidget(self.widgets["habitatUploadPosition"], 7, 0, 1, 3) + w1_habitat.addWidget(self.widgets["sondehubPositionNotesLabel"], 8, 0, 1, 3) + w1_habitat.layout.setRowStretch(9, 1) + w1_habitat.addWidget(self.widgets["saveSettingsButton"], 10, 0, 1, 3) + + d0_habitat.addWidget(w1_habitat) + + w1_other = pg.LayoutWidget() + self.widgets["horusHeaderLabel"] = QLabel("Telemetry Forwarding") + self.widgets["horusUploadLabel"] = QLabel("Enable Horus UDP Output:") + self.widgets["horusUploadSelector"] = QCheckBox() + self.widgets["horusUploadSelector"].setChecked(True) + self.widgets["horusUploadSelector"].setToolTip( + "Enable output of 'Horus UDP' JSON messages. These are emitted as a JSON object\n"\ + "and contain the fields: callsign, time, latitude, longitude, altitude, snr"\ + ) + self.widgets["horusUDPLabel"] = QLabel("Horus UDP Port:") + self.widgets["horusUDPEntry"] = QLineEdit("55672") + self.widgets["horusUDPEntry"].setMaxLength(5) + self.widgets["horusUDPEntry"].setToolTip( + "UDP Port to output 'Horus UDP' JSON messages to." + ) + self.widgets["ozimuxUploadLabel"] = QLabel("Enable OziMux UDP Output:") + self.widgets["ozimuxUploadSelector"] = QCheckBox() + self.widgets["ozimuxUploadSelector"].setChecked(False) + self.widgets["ozimuxUploadSelector"].setToolTip( + "Output OziMux UDP messages. These are of the form:\n"\ + "'TELEMETRY,HH:MM:SS,lat,lon,alt\\n'" + ) + self.widgets["ozimuxUDPLabel"] = QLabel("Ozimux UDP Port:") + self.widgets["ozimuxUDPEntry"] = QLineEdit("55683") + self.widgets["ozimuxUDPEntry"].setMaxLength(5) + self.widgets["ozimuxUDPEntry"].setToolTip( + "UDP Port to output 'OziMux' UDP messages to." + ) + self.widgets["loggingHeaderLabel"] = QLabel("Logging") + self.widgets["enableLoggingLabel"] = QLabel("Enable Logging:") + self.widgets["enableLoggingSelector"] = QCheckBox() + self.widgets["enableLoggingSelector"].setChecked(False) + self.widgets["enableLoggingSelector"].setToolTip( + "Enable logging of received telemetry to disk (JSON)" + ) + self.widgets["enableLoggingSelector"].clicked.connect(self.set_logging_state) + self.widgets["loggingFormatLabel"] = QLabel("Log Format:") + self.widgets["loggingFormatSelector"] = QComboBox() + self.widgets["loggingFormatSelector"].addItem("CSV") + self.widgets["loggingFormatSelector"].addItem("JSON") + self.widgets["loggingFormatSelector"].currentIndexChanged.connect(self.set_logging_format) + self.widgets["loggingPathLabel"] = QLabel("Log Directory:") + self.widgets["loggingPathEntry"] = QLineEdit("") + self.widgets["loggingPathEntry"].setToolTip( + "Logging Directory" + ) + self.widgets["selectLogDirButton"] = QPushButton("Select Directory") + self.widgets["selectLogDirButton"].clicked.connect(self.select_log_directory) + + self.widgets["otherHeaderLabel"] = QLabel("Other Settings") + self.widgets["inhibitCRCLabel"] = QLabel("Hide Failed CRC Errors:") + self.widgets["inhibitCRCSelector"] = QCheckBox() + self.widgets["inhibitCRCSelector"].setChecked(True) + self.widgets["inhibitCRCSelector"].setToolTip( + "Hide CRC Failed error messages." + ) + + w1_other.addWidget(self.widgets["horusHeaderLabel"], 0, 0, 1, 2) + w1_other.addWidget(self.widgets["horusUploadLabel"], 1, 0, 1, 1) + w1_other.addWidget(self.widgets["horusUploadSelector"], 1, 1, 1, 1) + w1_other.addWidget(self.widgets["horusUDPLabel"], 2, 0, 1, 1) + w1_other.addWidget(self.widgets["horusUDPEntry"], 2, 1, 1, 1) + w1_other.addWidget(self.widgets["ozimuxUploadLabel"], 3, 0, 1, 1) + w1_other.addWidget(self.widgets["ozimuxUploadSelector"], 3, 1, 1, 1) + w1_other.addWidget(self.widgets["ozimuxUDPLabel"], 4, 0, 1, 1) + w1_other.addWidget(self.widgets["ozimuxUDPEntry"], 4, 1, 1, 1) + w1_other.addWidget(self.widgets["loggingHeaderLabel"], 5, 0, 1, 2) + w1_other.addWidget(self.widgets["enableLoggingLabel"], 6, 0, 1, 1) + w1_other.addWidget(self.widgets["enableLoggingSelector"], 6, 1, 1, 1) + w1_other.addWidget(self.widgets["loggingFormatLabel"], 7, 0, 1, 1) + w1_other.addWidget(self.widgets["loggingFormatSelector"], 7, 1, 1, 1) + w1_other.addWidget(self.widgets["loggingPathLabel"], 8, 0, 1, 1) + w1_other.addWidget(self.widgets["loggingPathEntry"], 8, 1, 1, 1) + w1_other.addWidget(self.widgets["selectLogDirButton"], 9, 0, 1, 2) + w1_other.addWidget(self.widgets["otherHeaderLabel"], 10, 0, 1, 2) + w1_other.addWidget(self.widgets["inhibitCRCLabel"], 11, 0, 1, 1) + w1_other.addWidget(self.widgets["inhibitCRCSelector"], 11, 1, 1, 1) + w1_other.layout.setRowStretch(12, 1) + + d0_other.addWidget(w1_other) + + + w1_rotator = pg.LayoutWidget() + self.widgets["rotatorHeaderLabel"] = QLabel("Rotator Control") + + self.widgets["rotatorTypeLabel"] = QLabel("Rotator Type:") + self.widgets["rotatorTypeSelector"] = QComboBox() + self.widgets["rotatorTypeSelector"].addItem("rotctld") + self.widgets["rotatorTypeSelector"].addItem("PSTRotator") + + self.widgets["rotatorHostLabel"] = QLabel("Rotator Hostname:") + self.widgets["rotatorHostEntry"] = QLineEdit("localhost") + self.widgets["rotatorHostEntry"].setToolTip( + "Hostname of the rotctld or PSTRotator Server.\n"\ + ) + + self.widgets["rotatorPortLabel"] = QLabel("Rotator TCP/UDP Port:") + self.widgets["rotatorPortEntry"] = QLineEdit("4533") + self.widgets["rotatorPortEntry"].setMaxLength(5) + self.widgets["rotatorPortEntry"].setToolTip( + "TCP (rotctld) or UDP (PSTRotator) port to connect to.\n"\ + "Default for rotctld: 4533\n"\ + "Default for PSTRotator: 12000" + ) + self.widgets["rotatorThresholdLabel"] = QLabel("Rotator Movement Threshold:") + self.widgets["rotatorThresholdEntry"] = QLineEdit("5.0") + self.widgets["rotatorThresholdEntry"].setToolTip( + "Only move if the angle between the payload position and \n"\ + "the current rotator position is more than this, in degrees." + ) + + self.widgets["rotatorConnectButton"] = QPushButton("Start") + self.widgets["rotatorConnectButton"].clicked.connect(self.startstop_rotator) + + self.widgets["rotatorCurrentStatusLabel"] = QLabel("Status:") + self.widgets["rotatorCurrentStatusValue"] = QLabel("Not Started.") + + self.widgets["rotatorCurrentPositionLabel"] = QLabel("Commanded Az/El:") + self.widgets["rotatorCurrentPositionValue"] = QLabel("---˚, --˚") + + + + w1_rotator.addWidget(self.widgets["rotatorHeaderLabel"], 0, 0, 1, 2) + w1_rotator.addWidget(self.widgets["rotatorTypeLabel"], 1, 0, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorTypeSelector"], 1, 1, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorHostLabel"], 2, 0, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorHostEntry"], 2, 1, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorPortLabel"], 3, 0, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorPortEntry"], 3, 1, 1, 1) + #w1_rotator.addWidget(self.widgets["rotatorThresholdLabel"], 4, 0, 1, 1) + #w1_rotator.addWidget(self.widgets["rotatorThresholdEntry"], 4, 1, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorConnectButton"], 4, 0, 1, 2) + w1_rotator.addWidget(self.widgets["rotatorCurrentStatusLabel"], 5, 0, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorCurrentStatusValue"], 5, 1, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorCurrentPositionLabel"], 6, 0, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorCurrentPositionValue"], 6, 1, 1, 1) + + w1_rotator.layout.setRowStretch(7, 1) + + d0_rotator.addWidget(w1_rotator) + + + # Spectrum Display + self.widgets["spectrumPlot"] = pg.PlotWidget(title="Spectra") + self.widgets["spectrumPlot"].setLabel("left", "Power (dB)") + self.widgets["spectrumPlot"].setLabel("bottom", "Frequency (Hz)") + self.widgets["spectrumPlotData"] = self.widgets["spectrumPlot"].plot([0]) + + # Frequency Estiator Outputs + self.widgets["estimatorLines"] = [ + pg.InfiniteLine( + pos=-1000, + pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), + label="F1", + labelOpts={'position':0.9} + ), + pg.InfiniteLine( + pos=-1000, + pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), + label="F2", + labelOpts={'position':0.9} + ), + pg.InfiniteLine( + pos=-1000, + pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), + label="F3", + labelOpts={'position':0.9} + ), + pg.InfiniteLine( + pos=-1000, + pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), + label="F4", + labelOpts={'position':0.9} + ), + ] + for _line in self.widgets["estimatorLines"]: + self.widgets["spectrumPlot"].addItem(_line) + + self.widgets["spectrumPlot"].setLabel("left", "Power (dBFs)") + self.widgets["spectrumPlot"].setLabel("bottom", "Frequency", units="Hz") + self.widgets["spectrumPlot"].setXRange(100, 4000) + self.widgets["spectrumPlot"].setYRange(-100, -20) + self.widgets["spectrumPlot"].setLimits(xMin=100, xMax=4000, yMin=-120, yMax=0) + self.widgets["spectrumPlot"].showGrid(True, True) + + self.widgets["estimatorRange"] = pg.LinearRegionItem([100,3000]) + self.widgets["estimatorRange"].setBounds([100,4000]) + self.widgets["estimatorRange"].sigRegionChangeFinished.connect(self.update_manual_estimator) + + d1.addWidget(self.widgets["spectrumPlot"]) + + self.widgets["spectrumPlotRange"] = [-100, -20] + + + w3_stats = pg.LayoutWidget() + self.widgets["snrBar"] = QProgressBar() + self.widgets["snrBar"].setOrientation(QtCore.Qt.Orientation.Vertical) + self.widgets["snrBar"].setRange(-10, 15) + self.widgets["snrBar"].setValue(-10) + self.widgets["snrBar"].setTextVisible(False) + self.widgets["snrBar"].setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.widgets["snrLabel"] = QLabel("--.-") + self.widgets["snrLabel"].setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter); + self.widgets["snrLabel"].setFont(QFont("Courier New", 14)) + w3_stats.addWidget(self.widgets["snrBar"], 0, 1, 1, 1) + w3_stats.addWidget(self.widgets["snrLabel"], 1, 0, 1, 3) + w3_stats.layout.setColumnStretch(0, 2) + w3_stats.layout.setColumnStretch(2, 2) + + d2_stats.addWidget(w3_stats) + + # SNR Plot + w3_snr = pg.LayoutWidget() + self.widgets["snrPlot"] = pg.PlotWidget(title="SNR") + self.widgets["snrPlot"].setLabel("left", "SNR (dB)") + self.widgets["snrPlot"].setLabel("bottom", "Time (s)") + self.widgets["snrPlot"].setXRange(-60, 0) + self.widgets["snrPlot"].setYRange(-10, 30) + self.widgets["snrPlot"].setLimits(xMin=-60, xMax=0, yMin=-10, yMax=40) + self.widgets["snrPlot"].showGrid(True, True) + self.widgets["snrPlotRange"] = [-10, 30] + self.widgets["snrPlotTime"] = np.array([]) + self.widgets["snrPlotSNR"] = np.array([]) + self.widgets["snrPlotData"] = self.widgets["snrPlot"].plot(self.widgets["snrPlotTime"], self.widgets["snrPlotSNR"]) + + # TODO: Look into eye diagram more + # self.widgets["eyeDiagramPlot"] = pg.PlotWidget(title="Eye Diagram") + # self.widgets["eyeDiagramData"] = self.widgets["eyeDiagramPlot"].plot([0]) + + #w3_snr.addWidget(self.widgets["snrPlot"], 0, 1, 2, 1) + + #w3.addWidget(self.widgets["eyeDiagramPlot"], 0, 1) + + d2_snr.addWidget(self.widgets["snrPlot"]) + + # Telemetry Data + w4_data = pg.LayoutWidget() + self.widgets["latestRawSentenceLabel"] = QLabel("Latest Packet (Raw):") + self.widgets["latestRawSentenceData"] = QLineEdit("NO DATA") + self.widgets["latestRawSentenceData"].setReadOnly(True) + self.widgets["latestDecodedSentenceLabel"] = QLabel("Latest Packet (Decoded):") + self.widgets["latestDecodedSentenceData"] = QLineEdit("NO DATA") + self.widgets["latestDecodedSentenceData"].setReadOnly(True) + self.widgets["latestDecodedAgeLabel"] = QLabel("Last Packet Age:") + self.widgets["latestDecodedAgeData"] = QLabel("No packet yet!") + w4_data.addWidget(self.widgets["latestRawSentenceLabel"], 0, 0, 1, 1) + w4_data.addWidget(self.widgets["latestRawSentenceData"], 0, 1, 1, 6) + w4_data.addWidget(self.widgets["latestDecodedSentenceLabel"], 1, 0, 1, 1) + w4_data.addWidget(self.widgets["latestDecodedSentenceData"], 1, 1, 1, 6) + w4_data.addWidget(self.widgets["latestDecodedAgeLabel"], 2, 0, 1, 1) + w4_data.addWidget(self.widgets["latestDecodedAgeData"], 2, 1, 1, 2) + d3_data.addWidget(w4_data) + + w4_position = pg.LayoutWidget() + # This font seems to look bigger in Windows... not sure why. + if 'Windows' in platform.system(): + POSITION_LABEL_FONT_SIZE = 14 + else: + POSITION_LABEL_FONT_SIZE = 16 + + self.widgets["latestPacketCallsignLabel"] = QLabel("Callsign") + self.widgets["latestPacketCallsignValue"] = QLabel("---") + self.widgets["latestPacketCallsignValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestPacketTimeLabel"] = QLabel("Time") + self.widgets["latestPacketTimeValue"] = QLabel("---") + self.widgets["latestPacketTimeValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestPacketLatitudeLabel"] = QLabel("Latitude") + self.widgets["latestPacketLatitudeValue"] = QLabel("---") + self.widgets["latestPacketLatitudeValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestPacketLongitudeLabel"] = QLabel("Longitude") + self.widgets["latestPacketLongitudeValue"] = QLabel("---") + self.widgets["latestPacketLongitudeValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestPacketAltitudeLabel"] = QLabel("Altitude") + self.widgets["latestPacketAltitudeValue"] = QLabel("---") + self.widgets["latestPacketAltitudeValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestPacketBearingLabel"] = QLabel("Bearing") + self.widgets["latestPacketBearingValue"] = QLabel("---") + self.widgets["latestPacketBearingValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestPacketElevationLabel"] = QLabel("Elevation") + self.widgets["latestPacketElevationValue"] = QLabel("---") + self.widgets["latestPacketElevationValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestPacketRangeLabel"] = QLabel("Range (km)") + self.widgets["latestPacketRangeValue"] = QLabel("---") + self.widgets["latestPacketRangeValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + + w4_position.addWidget(self.widgets["latestPacketCallsignLabel"], 0, 0, 1, 2) + w4_position.addWidget(self.widgets["latestPacketCallsignValue"], 1, 0, 1, 2) + w4_position.addWidget(self.widgets["latestPacketTimeLabel"], 0, 2, 1, 1) + w4_position.addWidget(self.widgets["latestPacketTimeValue"], 1, 2, 1, 1) + w4_position.addWidget(self.widgets["latestPacketLatitudeLabel"], 0, 3, 1, 1) + w4_position.addWidget(self.widgets["latestPacketLatitudeValue"], 1, 3, 1, 1) + w4_position.addWidget(self.widgets["latestPacketLongitudeLabel"], 0, 4, 1, 1) + w4_position.addWidget(self.widgets["latestPacketLongitudeValue"], 1, 4, 1, 1) + w4_position.addWidget(self.widgets["latestPacketAltitudeLabel"], 0, 5, 1, 1) + w4_position.addWidget(self.widgets["latestPacketAltitudeValue"], 1, 5, 1, 1) + w4_position.addWidget(self.widgets["latestPacketBearingLabel"], 0, 7, 1, 1) + w4_position.addWidget(self.widgets["latestPacketBearingValue"], 1, 7, 1, 1) + w4_position.addWidget(self.widgets["latestPacketElevationLabel"], 0, 8, 1, 1) + w4_position.addWidget(self.widgets["latestPacketElevationValue"], 1, 8, 1, 1) + w4_position.addWidget(self.widgets["latestPacketRangeLabel"], 0, 9, 1, 1) + w4_position.addWidget(self.widgets["latestPacketRangeValue"], 1, 9, 1, 1) + w4_position.layout.setRowStretch(1, 6) + d3_position.addWidget(w4_position) + + w5 = pg.LayoutWidget() + self.widgets["console"] = QPlainTextEdit() + self.widgets["console"].setReadOnly(True) + w5.addWidget(self.widgets["console"]) + d4.addWidget(w5) + + # Resize window to final resolution, and display. + logging.info("Starting GUI.") + self.resize(1500, 800) + + self.post_initialize() + + + def post_initialize(self): + # Audio Initialization + self.audio_devices = init_audio(self.widgets) + + # Initialize modem list. + init_horus_modem(self.widgets) + + # Clear the configuration if we have been asked to, otherwise read it in from Qt stores + if args.reset: + logging.info("Clearing configuration.") + write_config() + else: + read_config(self.widgets) + + + try: + if float(self.widgets["userLatEntry"].text()) == 0.0 and float(self.widgets["userLonEntry"].text()) == 0.0: + _sondehub_user_pos = None + else: + _sondehub_user_pos = [float(self.widgets["userLatEntry"].text()), float(self.widgets["userLonEntry"].text()), 0.0] + except: + _sondehub_user_pos = None + + self.sondehub_uploader = SondehubAmateurUploader( + upload_rate = 2, + user_callsign = self.widgets["userCallEntry"].text(), + user_position = _sondehub_user_pos, + user_radio = "Horus-GUI v" + __version__ + " " + self.widgets["userRadioEntry"].text(), + user_antenna = self.widgets["userAntennaEntry"].text(), + software_name = "Horus-GUI", + software_version = __version__, + ) + + self.telemetry_logger = TelemetryLogger( + log_directory = self.widgets["loggingPathEntry"].text(), + log_format = self.widgets["loggingFormatSelector"].currentText(), + enabled = self.widgets["enableLoggingSelector"].isChecked() + ) + + self.gui_update_timer = QTimer() + self.gui_update_timer.timeout.connect(self.processQueues) + self.gui_update_timer.start(100) + + # Add console handler to top level logger. + console_handler = ConsoleHandler(self.log_update_queue) + logging.getLogger().addHandler(console_handler) + + logging.info("Started GUI.") + + + def cleanup(self): + try: + self.audio_stream.stop() + except Exception as e: + pass + + try: + self.fft_process.stop() + except Exception as e: + pass + + try: + self.sondehub_uploader.close() except: pass -widgets["horusManualEstimatorSelector"].clicked.connect(set_manual_estimator) - - -def save_settings(): - """ Manually save current settings """ - global widgets - save_config(widgets) - -widgets["saveSettingsButton"].clicked.connect(save_settings) - - -# Handlers for data arriving via queues. - -def handle_fft_update(data): - """ Handle a new FFT update """ - global widgets - - _scale = data["scale"] - _data = data["fft"] - _dbfs = data["dbfs"] - - widgets["spectrumPlotData"].setData(_scale, _data) - - # Really basic IIR to smoothly adjust scale - _old_max = widgets["spectrumPlotRange"][1] - _tc = 0.1 - _new_max = float((_old_max * (1 - _tc)) + (np.max(_data) * _tc)) - - # Store new max - widgets["spectrumPlotRange"][1] = max(widgets["spectrumPlotRange"][0], _new_max) - - widgets["spectrumPlot"].setYRange( - widgets["spectrumPlotRange"][0], widgets["spectrumPlotRange"][1] + 20 - ) - - # Ignore NaN values. - if np.isnan(_dbfs) or np.isinf(_dbfs): - return - - - # Use same IIR to smooth out dBFS readings a little. - _new_dbfs = float((widgets["audioDbfsValue_float"] * (1 - _tc)) + (_dbfs * _tc)) - - # Set dBFS value - if (_new_dbfs>-5.0): - _dbfs_ok = "TOO HIGH" - elif (_new_dbfs < -90.0): - _dbfs_ok = "NO AUDIO?" - elif (_new_dbfs < -50.0): - _dbfs_ok = "LOW" - else: - _dbfs_ok = "GOOD" - - widgets["audioDbfsValue"].setText(f"{_new_dbfs:.0f}\t{_dbfs_ok}") - widgets["audioDbfsValue_float"] = _new_dbfs - -def handle_status_update(status): - """ Handle a new status frame """ - global widgets, habitat - - # Update Frequency estimator markers - _fest_average = 0.0 - _fest_count = 0 - for _i in range(len(status.extended_stats.f_est)): - _fest_pos = float(status.extended_stats.f_est[_i]) - if _fest_pos != 0.0: - _fest_average += _fest_pos - _fest_count += 1 - widgets["estimatorLines"][_i].setPos(_fest_pos) - - _fest_average = _fest_average/_fest_count - widgets["fest_float"] = _fest_average - - # Update SNR Plot - _time = time.time() - # Roll Time/SNR - widgets["snrPlotTime"] = np.append(widgets["snrPlotTime"], _time) - widgets["snrPlotSNR"] = np.append(widgets["snrPlotSNR"], float(status.snr)) - if len(widgets["snrPlotTime"]) > 200: - widgets["snrPlotTime"] = widgets["snrPlotTime"][1:] - widgets["snrPlotSNR"] = widgets["snrPlotSNR"][1:] - - # Plot new SNR data - widgets["snrPlotData"].setData((widgets["snrPlotTime"]-_time), widgets["snrPlotSNR"]) - _old_max = widgets["snrPlotRange"][1] - _tc = 0.1 - _new_max = float((_old_max * (1 - _tc)) + (np.max(widgets["snrPlotSNR"]) * _tc)) - widgets["snrPlotRange"][1] = _new_max - widgets["snrPlot"].setYRange( - widgets["snrPlotRange"][0], _new_max+10 - ) - - # Update SNR bar and label - widgets["snrLabel"].setText(f"{float(status.snr):2.1f}") - widgets["snrBar"].setValue(int(status.snr)) - - -def get_latest_snr(): - global widgets - - _current_modem = widgets["horusModemSelector"].currentText() - - _snr_update_rate = 2 # Hz - - if "RTTY" in _current_modem: - # RTTY needs a much longer lookback period to find the peak SNR - # This is because of a very long buffer used in the RTTY demod - _snr_lookback = _snr_update_rate * 15 - else: - # For Horus Binary we can use a smaller lookback time - _snr_lookback = _snr_update_rate * 4 - - if len(widgets["snrPlotSNR"])>_snr_lookback: - return np.max(widgets["snrPlotSNR"][-1*_snr_lookback:]) - else: - return np.max(widgets["snrPlotSNR"]) - - - - -def add_fft_update(data): - """ Try and insert a new set of FFT data into the update queue """ - global fft_update_queue - try: - fft_update_queue.put_nowait(data) - except: - logging.error("FFT Update Queue Full!") - - -def add_stats_update(frame): - """ Try and insert modem statistics into the processing queue """ - global status_update_queue - try: - status_update_queue.put_nowait(frame) - except: - logging.error("Status Update Queue Full!") - - - - -def handle_new_packet(frame): - """ Handle receipt of a newly decoded packet """ - global last_packet_time - - if len(frame.data) > 0: - if type(frame.data) == bytes: - # Packets from the binary decoders are provided as raw bytes. - # Conver them to a hexadecimal representation for display in the 'raw' area. - _packet = frame.data.hex().upper() - else: - # RTTY packets are provided as a string, and can be displayed directly - _packet = frame.data - - - - _decoded = None - - # Grab SNR. - _snr = get_latest_snr() - #logging.info(f"Packet SNR: {_snr:.2f}") - - - # Grab other metadata out of the GUI - _radio_dial = None - - if widgets["dialFreqEntry"].text() != "": - try: - _radio_dial = float(widgets["dialFreqEntry"].text())*1e6 - if widgets["fest_float"]: - # Add on the centre frequency estimation onto the dial frequency. - _radio_dial += widgets["fest_float"] - - except: - logging.warning("Could not parse radio dial frequency. This must be in MMM.KKK format e.g. 437.600") - _radio_dial = None - - - _baud_rate = int(widgets["horusModemRateSelector"].currentText()) - _modulation_detail = HORUS_MODEM_LIST[widgets["horusModemSelector"].currentText()]['modulation_detail'] - - if type(frame.data) == str: - # RTTY packet handling. - # Attempt to extract fields from it: - try: - _decoded = parse_ukhas_string(frame.data) - _decoded['snr'] = _snr - _decoded['baud_rate'] = _baud_rate - if _modulation_detail: - _decoded['modulation_detail'] = _modulation_detail - if _radio_dial: - _decoded['f_centre'] = _radio_dial - # If we get here, the string is valid! - widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") - widgets["latestDecodedSentenceData"].setText(f"{_packet}") - last_packet_time = time.time() - - # Upload the string to Sondehub Amateur - if widgets["userCallEntry"].text() == "N0CALL": - logging.warning("Uploader callsign is set as N0CALL. Please change this, otherwise telemetry data may be discarded!") - - sondehub_uploader.add(_decoded) - - except Exception as e: - if "CRC Failure" in str(e) and widgets["inhibitCRCSelector"].isChecked(): - pass - else: - widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") - widgets["latestDecodedSentenceData"].setText("DECODE FAILED") - logging.error(f"Decode Failed: {str(e)}") - - else: - # Handle binary packets - try: - _decoded = decode_packet(frame.data) - _decoded['snr'] = _snr - _decoded['baud_rate'] = _baud_rate - if _modulation_detail: - _decoded['modulation_detail'] = _modulation_detail - if _radio_dial: - _decoded['f_centre'] = _radio_dial - - widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") - widgets["latestDecodedSentenceData"].setText(_decoded['ukhas_str']) - last_packet_time = time.time() - # Upload the string to Sondehub Amateur - if widgets["userCallEntry"].text() == "N0CALL": - logging.warning("Uploader callsign is set as N0CALL. Please change this, otherwise telemetry data may be discarded!") - - sondehub_uploader.add(_decoded) - except Exception as e: - if "CRC Failure" in str(e) and widgets["inhibitCRCSelector"].isChecked(): - pass - else: - widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") - widgets["latestDecodedSentenceData"].setText("DECODE FAILED") - logging.error(f"Decode Failed: {str(e)}") - - # If we have extracted data, update the decoded data display - if _decoded: - widgets["latestPacketCallsignValue"].setText(_decoded['callsign']) - widgets["latestPacketTimeValue"].setText(_decoded['time']) - widgets["latestPacketLatitudeValue"].setText(f"{_decoded['latitude']:.5f}") - widgets["latestPacketLongitudeValue"].setText(f"{_decoded['longitude']:.5f}") - widgets["latestPacketAltitudeValue"].setText(f"{_decoded['altitude']}") - - # Attempt to update the range/elevation/bearing fields. - try: - _station_lat = float(widgets["userLatEntry"].text()) - _station_lon = float(widgets["userLonEntry"].text()) - _station_alt = float(widgets["userAltEntry"].text()) - - if (_station_lat != 0.0) or (_station_lon != 0.0): - _position_info = position_info( - (_station_lat, _station_lon, _station_alt), - (_decoded['latitude'], _decoded['longitude'], _decoded['altitude']) - ) - - widgets['latestPacketBearingValue'].setText(f"{_position_info['bearing']:.1f}") - widgets['latestPacketElevationValue'].setText(f"{_position_info['elevation']:.1f}") - widgets['latestPacketRangeValue'].setText(f"{_position_info['straight_distance']/1000.0:.1f}") - - if rotator and not ( _decoded['latitude'] == 0.0 and _decoded['longitude'] == 0.0 ): - try: - rotator.set_azel(_position_info['bearing'], _position_info['elevation'], check_response=False) - widgets["rotatorCurrentPositionValue"].setText(f"{_position_info['bearing']:3.1f}˚, {_position_info['elevation']:2.1f}˚") - except Exception as e: - logging.error("Rotator - Error setting Position: " + str(e)) - - except Exception as e: - logging.error(f"Could not calculate relative position to payload - {str(e)}") - - # Send data out via Horus UDP - if widgets["horusUploadSelector"].isChecked(): - _udp_port = int(widgets["horusUDPEntry"].text()) - # Add in SNR data - try: - _snr = float(widgets["snrLabel"].text()) - except ValueError as e: - logging.error(e) - _snr = 0 - _decoded['snr'] = _snr - - send_payload_summary(_decoded, port=_udp_port) - - # Send data out via OziMux messaging - if widgets["ozimuxUploadSelector"].isChecked(): - _udp_port = int(widgets["ozimuxUDPEntry"].text()) - send_ozimux_message(_decoded, port=_udp_port) - - # Log telemetry - if telemetry_logger: - telemetry_logger.add(_decoded) - - # Try and force a refresh of the displays. - QtWidgets.QApplication.processEvents() - - - -def start_decoding(): - """ - Read settings from the GUI - Set up all elements of the decode chain - Start decoding! - (Or, stop decoding) - """ - global widgets, audio_stream, fft_process, horus_modem, audio_devices, running, fft_update_queue, status_update_queue, last_packet_time, args - - if not running: - # Reset last packet time - - if widgets["userCallEntry"].text() == "N0CALL": - # We don't allow the decoder to start if the callsign is still at the default. - _error_msgbox = QtWidgets.QMessageBox() - _error_msgbox.setWindowTitle("Uploader Callsign Invalid") - _error_msgbox.setText("Please change your SondeHub uploader callsign before starting!") - _error_msgbox.exec_() - - return - - last_packet_time = None - widgets['latestDecodedAgeData'].setText("No packet yet!") - # Grab settings off widgets - _dev_name = widgets["audioDeviceSelector"].currentText() - if _dev_name != 'UDP Audio (127.0.0.1:7355)': - _sample_rate = int(widgets["audioSampleRateSelector"].currentText()) - _dev_index = audio_devices[_dev_name]["index"] - else: - # Override sample rate for GQRX UDP input. - _sample_rate = 48000 - - # Grab Horus Settings - _modem_name = widgets["horusModemSelector"].currentText() - _modem_id = HORUS_MODEM_LIST[_modem_name]['id'] - _modem_rate = int(widgets["horusModemRateSelector"].currentText()) - _modem_mask_enabled = widgets["horusMaskEstimatorSelector"].isChecked() - if _modem_mask_enabled: - _modem_tone_spacing = int(widgets["horusMaskSpacingEntry"].text()) - else: - _modem_tone_spacing = -1 - - # Reset Frequency Estimator indicators - for _line in widgets["estimatorLines"]: - _line.setPos(-1000) - - # Reset data fields - widgets["latestRawSentenceData"].setText("NO DATA") - widgets["latestDecodedSentenceData"].setText("NO DATA") - widgets["latestPacketCallsignValue"].setText("---") - widgets["latestPacketTimeValue"].setText("---") - widgets["latestPacketLatitudeValue"].setText("---") - widgets["latestPacketLongitudeValue"].setText("---") - widgets["latestPacketAltitudeValue"].setText("---") - widgets["latestPacketElevationValue"].setText("---") - widgets["latestPacketBearingValue"].setText("---") - widgets["latestPacketRangeValue"].setText("---") - - # Ensure the SondeHub upload is set correctly. - sondehub_uploader.inhibit = not widgets["sondehubUploadSelector"].isChecked() - - # Init FFT Processor - NFFT = 2 ** 13 - STRIDE = 2 ** 13 - fft_process = FFTProcess( - nfft=NFFT, - stride=STRIDE, - update_decimation=1, - fs=_sample_rate, - callback=add_fft_update - ) - - # Setup Modem - _libpath = "" - if args.libfix: - _libpath = "./" - - horus_modem = HorusLib( - libpath=_libpath, - mode=_modem_id, - rate=_modem_rate, - tone_spacing=_modem_tone_spacing, - callback=handle_new_packet, - sample_rate=_sample_rate - ) - - # Set manual estimator limits, if enabled - if widgets["horusManualEstimatorSelector"].isChecked(): - update_manual_estimator() - else: - horus_modem.set_estimator_limits(DEFAULT_ESTIMATOR_MIN, DEFAULT_ESTIMATOR_MAX) - - # Setup Audio (or UDP input) - if _dev_name == 'UDP Audio (127.0.0.1:7355)': - audio_stream = UDPStream( - udp_port=7355, - fs=_sample_rate, - block_size=fft_process.stride, - fft_input=fft_process.add_samples, - modem=horus_modem, - stats_callback=add_stats_update - ) - else: - audio_stream = AudioStream( - _dev_index, - fs=_sample_rate, - block_size=fft_process.stride, - fft_input=fft_process.add_samples, - modem=horus_modem, - stats_callback=add_stats_update - ) - - widgets["startDecodeButton"].setText("Stop") - running = True - logging.info("Started Audio Processing.") - - # Grey out some selectors, so the user cannot adjust them while we are decoding. - widgets["audioDeviceSelector"].setEnabled(False) - widgets["audioSampleRateSelector"].setEnabled(False) - widgets["horusModemSelector"].setEnabled(False) - widgets["horusModemRateSelector"].setEnabled(False) - widgets["horusMaskEstimatorSelector"].setEnabled(False) # This should really be editable while running. - widgets["horusMaskSpacingEntry"].setEnabled(False) # This should really be editable while running - - else: try: - audio_stream.stop() - except Exception as e: - logging.exception("Could not stop audio stream.", exc_info=e) - - try: - fft_process.stop() - except Exception as e: - logging.exception("Could not stop fft processing.", exc_info=e) - - try: - horus_modem.close() - except Exception as e: - logging.exception("Could not close horus modem.", exc_info=e) - - horus_modem = None - - fft_update_queue = Queue(256) - status_update_queue = Queue(256) - - widgets["startDecodeButton"].setText("Start") - running = False - - logging.info("Stopped Audio Processing.") - - # Re-Activate selectors. - widgets["audioDeviceSelector"].setEnabled(True) - widgets["audioSampleRateSelector"].setEnabled(True) - widgets["horusModemSelector"].setEnabled(True) - widgets["horusModemRateSelector"].setEnabled(True) - widgets["horusMaskEstimatorSelector"].setEnabled(True) - widgets["horusMaskSpacingEntry"].setEnabled(True) - - -widgets["startDecodeButton"].clicked.connect(start_decoding) - - -def handle_log_update(log_update): - global widgets - - widgets["console"].appendPlainText(log_update) - # Make sure the scroll bar is right at the bottom. - _sb = widgets["console"].verticalScrollBar() - _sb.setValue(_sb.maximum()) - - -# GUI Update Loop -def processQueues(): - """ Read in data from the queues, this decouples the GUI and async inputs somewhat. """ - global fft_update_queue, status_update_queue, decoder_init, widgets, args, running, last_packet_time - - while fft_update_queue.qsize() > 0: - _data = fft_update_queue.get() - - handle_fft_update(_data) - - while status_update_queue.qsize() > 0: - _status = status_update_queue.get() - - handle_status_update(_status) - - while log_update_queue.qsize() > 0: - _log = log_update_queue.get() - - handle_log_update(_log) - - if running: - if last_packet_time != None: - _time_delta = int(time.time() - last_packet_time) - _time_delta_seconds = int(_time_delta%60) - _time_delta_minutes = int((_time_delta/60) % 60) - _time_delta_hours = int((_time_delta/3600)) - widgets['latestDecodedAgeData'].setText(f"{_time_delta_hours:02d}:{_time_delta_minutes:02d}:{_time_delta_seconds:02d}") - - # Try and force a re-draw. - QtWidgets.QApplication.processEvents() - - if not decoder_init: - # Initialise decoders, and other libraries here. - init_payloads(payload_id_list = args.payload_id_list, custom_field_list = args.custom_field_list) - decoder_init = True - # Once initialised, enable the start button - widgets["startDecodeButton"].setEnabled(True) - -gui_update_timer = QtCore.QTimer() -gui_update_timer.timeout.connect(processQueues) -gui_update_timer.start(100) - - - -# Rotator Control - -def startstop_rotator(): - global rotator, widgets - - if rotator is None: - # Start a rotator connection. - - try: - _host = widgets["rotatorHostEntry"].text() - _port = int(widgets["rotatorPortEntry"].text()) - _threshold = float(widgets["rotatorThresholdEntry"].text()) + self.telemetry_logger.close() except: - widgets["rotatorCurrentStatusValue"].setText("Bad Host/Port") - return + pass - if widgets["rotatorTypeSelector"].currentText() == "rotctld": + if self.rotator: try: - rotator = ROTCTLD(hostname=_host, port=_port, threshold=_threshold) - rotator.connect() - except Exception as e: - logging.error("Rotctld Connect Error: " + str(e)) - rotator = None - return - elif widgets["rotatorTypeSelector"].currentText() == "PSTRotator": - rotator = PSTRotator(hostname=_host, port=_port, threshold=_threshold) + self.rotator.close() + except: + pass + + + def update_audio_sample_rates(self): + """ Update the sample-rate dropdown when a different audio device is selected. """ + # Pass widgets straight on to function from .audio + populate_sample_rates(self.widgets) + + + def update_modem_settings(self): + """ Update the modem setting widgets when a different modem is selected """ + populate_modem_settings(self.widgets) + + + def select_log_directory(self): + folder = str(QFileDialog.getExistingDirectory(None, "Select Directory")) + + if folder is None: + logging.info("No log directory selected.") + return False + else: + if folder == "": + logging.info("No log directory selected.") + return False + else: + self.widgets["loggingPathEntry"].setText(folder) + self.widgets["enableLoggingSelector"].setChecked(False) + if self.telemetry_logger: + self.widgets["enableLoggingSelector"].setChecked(True) + self.telemetry_logger.update_log_directory(self.widgets["loggingPathEntry"].text()) + self.telemetry_logger.enabled = True + + return True + + + def set_logging_state(self): + logging_enabled = self.widgets["enableLoggingSelector"].isChecked() + + if logging_enabled: + if self.widgets["loggingPathEntry"].text() == "": + # No logging directory set, prompt user to select one. + _success = self.select_log_directory() + if not _success: + # User didn't select a directory, set checkbox to false again. + logging.error("No log directory selected, logging disabled.") + self.widgets["enableLoggingSelector"].setChecked(False) + # Disable logging. + if self.telemetry_logger: + self.telemetry_logger.enabled = False + + return + + # Enable logging + if self.telemetry_logger: + self.telemetry_logger.enabled = True + self.telemetry_logger.update_log_directory(self.widgets["loggingPathEntry"].text()) else: + # Disable logging + if self.telemetry_logger: + self.telemetry_logger.enabled = False + + + def set_logging_format(self): + if self.telemetry_logger: + self.telemetry_logger.log_format = self.widgets["loggingFormatSelector"].currentText() + + + # Handlers for various checkboxes and push-buttons + def habitat_position_reupload(self, dummy_arg, upload=True): + """ + Trigger a re-upload of user position information + Note that this requires a dummy argument, as the Qt + 'connect' callback supplies an argument which we don't want. + """ + self.sondehub_uploader.user_callsign = self.widgets["userCallEntry"].text() + self.sondehub_uploader.user_radio = "Horus-GUI v" + __version__ + " " + self.widgets["userRadioEntry"].text() + self.sondehub_uploader.user_antenna = self.widgets["userAntennaEntry"].text() + try: + if float(self.widgets["userLatEntry"].text()) == 0.0 and float(self.widgets["userLonEntry"].text()) == 0.0: + self.sondehub_uploader.user_position = None + else: + self.sondehub_uploader.user_position = [ + float(self.widgets["userLatEntry"].text()), + float(self.widgets["userLonEntry"].text()), + float(self.widgets["userAltEntry"].text())] + except Exception as e: + logging.error(f"Error parsing station location - {str(e)}") + self.sondehub_uploader.user_position = None + + if upload: + self.sondehub_uploader.last_user_position_upload = 0 + self.widgets["sondehubPositionNotesLabel"].setText("") + logging.info("Triggered user position re-upload.") + + + # Update uploader info as soon as it's edited, to ensure we upload with the latest user callsign + def update_uploader_details(self): + """ + Wrapper function for position re-upload, called when the user callsign entry is changed. + """ + #habitat_position_reupload("unused arg",upload=False) + self.widgets["sondehubPositionNotesLabel"].setText("
Station Info out of date - click Re-Upload!
") + + + def habitat_inhibit(self): + """ Update the Habitat inhibit flag """ + self.sondehub_uploader.inhibit = not self.widgets["sondehubUploadSelector"].isChecked() + logging.debug(f"Updated Sondebub Inhibit state: {self.sondehub_uploader.inhibit}") + + + def update_manual_estimator(self): + """ Push a change to the manually defined estimator limits into the modem """ + _limits = self.widgets["estimatorRange"].getRegion() + + _lower = _limits[0] + _upper = _limits[1] + + if self.horus_modem != None: + self.horus_modem.set_estimator_limits(_lower, _upper) + + + def set_manual_estimator(self): + """ Show or hide the manual estimator limit region """ + if self.widgets["horusManualEstimatorSelector"].isChecked(): + self.widgets["spectrumPlot"].addItem(self.widgets["estimatorRange"]) + self.update_manual_estimator() + else: + try: + self.widgets["spectrumPlot"].removeItem(self.widgets["estimatorRange"]) + # Reset modem estimator limits to their defaults. + if self.horus_modem != None: + self.horus_modem.set_estimator_limits(self.DEFAULT_ESTIMATOR_MIN, self.DEFAULT_ESTIMATOR_MAX) + except: + pass + + + def save_settings(self): + """ Manually save current settings """ + save_config(self.widgets) + + + # Handlers for data arriving via queues. + def handle_fft_update(self, data): + """ Handle a new FFT update """ + + _scale = data["scale"] + _data = data["fft"] + _dbfs = data["dbfs"] + + self.widgets["spectrumPlotData"].setData(_scale, _data) + + # Really basic IIR to smoothly adjust scale + _old_max = self.widgets["spectrumPlotRange"][1] + _tc = 0.1 + _new_max = float((_old_max * (1 - _tc)) + (np.max(_data) * _tc)) + + # Store new max + self.widgets["spectrumPlotRange"][1] = max(self.widgets["spectrumPlotRange"][0], _new_max) + + self.widgets["spectrumPlot"].setYRange( + self.widgets["spectrumPlotRange"][0], self.widgets["spectrumPlotRange"][1] + 20 + ) + + # Ignore NaN values. + if np.isnan(_dbfs) or np.isinf(_dbfs): return - widgets["rotatorCurrentStatusValue"].setText("Connected") - widgets["rotatorConnectButton"].setText("Stop") - else: - # Stop the rotator - rotator.close() - rotator = None - widgets["rotatorConnectButton"].setText("Start") - widgets["rotatorCurrentStatusValue"].setText("Not Connected") - widgets["rotatorCurrentPositionValue"].setText(f"---˚, --˚") + # Use same IIR to smooth out dBFS readings a little. + _new_dbfs = float((self.widgets["audioDbfsValue_float"] * (1 - _tc)) + (_dbfs * _tc)) + + # Set dBFS value + if (_new_dbfs>-5.0): + _dbfs_ok = "TOO HIGH" + elif (_new_dbfs < -90.0): + _dbfs_ok = "NO AUDIO?" + elif (_new_dbfs < -50.0): + _dbfs_ok = "LOW" + else: + _dbfs_ok = "GOOD" + + self.widgets["audioDbfsValue"].setText(f"{_new_dbfs:.0f}\t{_dbfs_ok}") + self.widgets["audioDbfsValue_float"] = _new_dbfs -widgets["rotatorConnectButton"].clicked.connect(startstop_rotator) + def handle_status_update(self, status): + """ Handle a new status frame """ -# def poll_rotator(): -# global rotator, widgets, rotator_current_az, rotator_current_el + # Update Frequency estimator markers + _fest_average = 0.0 + _fest_count = 0 + for _i in range(len(status.extended_stats.f_est)): + _fest_pos = float(status.extended_stats.f_est[_i]) + if _fest_pos != 0.0: + _fest_average += _fest_pos + _fest_count += 1 + self.widgets["estimatorLines"][_i].setPos(_fest_pos) -# if rotator: -# _az, _el = rotator.get_azel() + _fest_average = _fest_average/_fest_count + self.widgets["fest_float"] = _fest_average -# if _az != None: -# rotator_current_az = _az + # Update SNR Plot + _time = time.time() + # Roll Time/SNR + self.widgets["snrPlotTime"] = np.append(self.widgets["snrPlotTime"], _time) + self.widgets["snrPlotSNR"] = np.append(self.widgets["snrPlotSNR"], float(status.snr)) + if len(self.widgets["snrPlotTime"]) > 200: + self.widgets["snrPlotTime"] = self.widgets["snrPlotTime"][1:] + self.widgets["snrPlotSNR"] = self.widgets["snrPlotSNR"][1:] -# if _el != None: -# rotator_current_el = _el + # Plot new SNR data + self.widgets["snrPlotData"].setData((self.widgets["snrPlotTime"]-_time), self.widgets["snrPlotSNR"]) + _old_max = self.widgets["snrPlotRange"][1] + _tc = 0.1 + _new_max = float((_old_max * (1 - _tc)) + (np.max(self.widgets["snrPlotSNR"]) * _tc)) + self.widgets["snrPlotRange"][1] = _new_max + self.widgets["snrPlot"].setYRange( + self.widgets["snrPlotRange"][0], _new_max+10 + ) -# widgets["rotatorCurrentPositionValue"].setText(f"{rotator_current_az:3.1f}˚, {rotator_current_el:2.1f}˚") + # Update SNR bar and label + self.widgets["snrLabel"].setText(f"{float(status.snr):2.1f}") + self.widgets["snrBar"].setValue(int(status.snr)) -# rotator_poll_timer = QtCore.QTimer() -# rotator_poll_timer.timeout.connect(poll_rotator) -# rotator_poll_timer.start(2000) + + def get_latest_snr(self): + _current_modem = self.widgets["horusModemSelector"].currentText() + + _snr_update_rate = 2 # Hz + + if "RTTY" in _current_modem: + # RTTY needs a much longer lookback period to find the peak SNR + # This is because of a very long buffer used in the RTTY demod + _snr_lookback = _snr_update_rate * 15 + else: + # For Horus Binary we can use a smaller lookback time + _snr_lookback = _snr_update_rate * 4 + + if len(self.widgets["snrPlotSNR"])>_snr_lookback: + return np.max(self.widgets["snrPlotSNR"][-1*_snr_lookback:]) + else: + return np.max(self.widgets["snrPlotSNR"]) + + + def add_fft_update(data): + """ Try and insert a new set of FFT data into the update queue """ + global fft_update_queue + try: + fft_update_queue.put_nowait(data) + except: + logging.error("FFT Update Queue Full!") + + + def add_stats_update(frame): + """ Try and insert modem statistics into the processing queue """ + global status_update_queue + try: + status_update_queue.put_nowait(frame) + except: + logging.error("Status Update Queue Full!") + + + + + def handle_new_packet(self, frame): + """ Handle receipt of a newly decoded packet """ + + if len(frame.data) > 0: + if type(frame.data) == bytes: + # Packets from the binary decoders are provided as raw bytes. + # Conver them to a hexadecimal representation for display in the 'raw' area. + _packet = frame.data.hex().upper() + else: + # RTTY packets are provided as a string, and can be displayed directly + _packet = frame.data + + + + _decoded = None + + # Grab SNR. + _snr = self.get_latest_snr() + #logging.info(f"Packet SNR: {_snr:.2f}") + + + # Grab other metadata out of the GUI + _radio_dial = None + + if self.widgets["dialFreqEntry"].text() != "": + try: + _radio_dial = float(self.widgets["dialFreqEntry"].text())*1e6 + if self.widgets["fest_float"]: + # Add on the centre frequency estimation onto the dial frequency. + _radio_dial += self.widgets["fest_float"] + + except: + logging.warning("Could not parse radio dial frequency. This must be in MMM.KKK format e.g. 437.600") + _radio_dial = None + + + _baud_rate = int(self.widgets["horusModemRateSelector"].currentText()) + _modulation_detail = HORUS_MODEM_LIST[self.widgets["horusModemSelector"].currentText()]['modulation_detail'] + + if type(frame.data) == str: + # RTTY packet handling. + # Attempt to extract fields from it: + try: + _decoded = parse_ukhas_string(frame.data) + _decoded['snr'] = _snr + _decoded['baud_rate'] = _baud_rate + if _modulation_detail: + _decoded['modulation_detail'] = _modulation_detail + if _radio_dial: + _decoded['f_centre'] = _radio_dial + # If we get here, the string is valid! + self.widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") + self.widgets["latestDecodedSentenceData"].setText(f"{_packet}") + self.last_packet_time = time.time() + + # Upload the string to Sondehub Amateur + if self.widgets["userCallEntry"].text() == "N0CALL": + logging.warning("Uploader callsign is set as N0CALL. Please change this, otherwise telemetry data may be discarded!") + + self.sondehub_uploader.add(_decoded) + + except Exception as e: + if "CRC Failure" in str(e) and self.widgets["inhibitCRCSelector"].isChecked(): + pass + else: + self.widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") + self.widgets["latestDecodedSentenceData"].setText("DECODE FAILED") + logging.error(f"Decode Failed: {str(e)}") + + else: + # Handle binary packets + try: + _decoded = decode_packet(frame.data) + _decoded['snr'] = _snr + _decoded['baud_rate'] = _baud_rate + if _modulation_detail: + _decoded['modulation_detail'] = _modulation_detail + if _radio_dial: + _decoded['f_centre'] = _radio_dial + + self.widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") + self.widgets["latestDecodedSentenceData"].setText(_decoded['ukhas_str']) + last_packet_time = time.time() + # Upload the string to Sondehub Amateur + if self.widgets["userCallEntry"].text() == "N0CALL": + logging.warning("Uploader callsign is set as N0CALL. Please change this, otherwise telemetry data may be discarded!") + + self.sondehub_uploader.add(_decoded) + except Exception as e: + if "CRC Failure" in str(e) and self.widgets["inhibitCRCSelector"].isChecked(): + pass + else: + self.widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") + self.widgets["latestDecodedSentenceData"].setText("DECODE FAILED") + logging.error(f"Decode Failed: {str(e)}") + + # If we have extracted data, update the decoded data display + if _decoded: + self.widgets["latestPacketCallsignValue"].setText(_decoded['callsign']) + self.widgets["latestPacketTimeValue"].setText(_decoded['time']) + self.widgets["latestPacketLatitudeValue"].setText(f"{_decoded['latitude']:.5f}") + self.widgets["latestPacketLongitudeValue"].setText(f"{_decoded['longitude']:.5f}") + self.widgets["latestPacketAltitudeValue"].setText(f"{_decoded['altitude']}") + + # Attempt to update the range/elevation/bearing fields. + try: + _station_lat = float(self.widgets["userLatEntry"].text()) + _station_lon = float(self.widgets["userLonEntry"].text()) + _station_alt = float(self.widgets["userAltEntry"].text()) + + if (_station_lat != 0.0) or (_station_lon != 0.0): + _position_info = position_info( + (_station_lat, _station_lon, _station_alt), + (_decoded['latitude'], _decoded['longitude'], _decoded['altitude']) + ) + + self.widgets['latestPacketBearingValue'].setText(f"{_position_info['bearing']:.1f}") + self.widgets['latestPacketElevationValue'].setText(f"{_position_info['elevation']:.1f}") + self.widgets['latestPacketRangeValue'].setText(f"{_position_info['straight_distance']/1000.0:.1f}") + + if self.rotator and not ( _decoded['latitude'] == 0.0 and _decoded['longitude'] == 0.0 ): + try: + self.rotator.set_azel(_position_info['bearing'], _position_info['elevation'], check_response=False) + self.widgets["rotatorCurrentPositionValue"].setText(f"{_position_info['bearing']:3.1f}˚, {_position_info['elevation']:2.1f}˚") + except Exception as e: + logging.error("Rotator - Error setting Position: " + str(e)) + + except Exception as e: + logging.error(f"Could not calculate relative position to payload - {str(e)}") + + # Send data out via Horus UDP + if self.widgets["horusUploadSelector"].isChecked(): + _udp_port = int(self.widgets["horusUDPEntry"].text()) + # Add in SNR data + try: + _snr = float(self.widgets["snrLabel"].text()) + except ValueError as e: + logging.error(e) + _snr = 0 + _decoded['snr'] = _snr + + send_payload_summary(_decoded, port=_udp_port) + + # Send data out via OziMux messaging + if self.widgets["ozimuxUploadSelector"].isChecked(): + _udp_port = int(self.widgets["ozimuxUDPEntry"].text()) + send_ozimux_message(_decoded, port=_udp_port) + + # Log telemetry + if self.telemetry_logger: + self.telemetry_logger.add(_decoded) + + # Try and force a refresh of the displays. + QApplication.processEvents() + + + + def start_decoding(self): + """ + Read settings from the GUI + Set up all elements of the decode chain + Start decoding! + (Or, stop decoding) + """ + global args + + if not self.running: + # Reset last packet time + + if self.widgets["userCallEntry"].text() == "N0CALL": + # We don't allow the decoder to start if the callsign is still at the default. + _error_msgbox = QMessageBox() + _error_msgbox.setWindowTitle("Uploader Callsign Invalid") + _error_msgbox.setText("Please change your SondeHub uploader callsign before starting!") + _error_msgbox.exec() + + return + + self.last_packet_time = None + self.widgets['latestDecodedAgeData'].setText("No packet yet!") + # Grab settings off widgets + _dev_name = self.widgets["audioDeviceSelector"].currentText() + if _dev_name != 'UDP Audio (127.0.0.1:7355)': + _sample_rate = int(self.widgets["audioSampleRateSelector"].currentText()) + _dev_index = self.audio_devices[_dev_name]["index"] + else: + # Override sample rate for GQRX UDP input. + _sample_rate = 48000 + + # Grab Horus Settings + _modem_name = self.widgets["horusModemSelector"].currentText() + _modem_id = HORUS_MODEM_LIST[_modem_name]['id'] + _modem_rate = int(self.widgets["horusModemRateSelector"].currentText()) + _modem_mask_enabled = self.widgets["horusMaskEstimatorSelector"].isChecked() + if _modem_mask_enabled: + _modem_tone_spacing = int(self.widgets["horusMaskSpacingEntry"].text()) + else: + _modem_tone_spacing = -1 + + # Reset Frequency Estimator indicators + for _line in self.widgets["estimatorLines"]: + _line.setPos(-1000) + + # Reset data fields + self.widgets["latestRawSentenceData"].setText("NO DATA") + self.widgets["latestDecodedSentenceData"].setText("NO DATA") + self.widgets["latestPacketCallsignValue"].setText("---") + self.widgets["latestPacketTimeValue"].setText("---") + self.widgets["latestPacketLatitudeValue"].setText("---") + self.widgets["latestPacketLongitudeValue"].setText("---") + self.widgets["latestPacketAltitudeValue"].setText("---") + self.widgets["latestPacketElevationValue"].setText("---") + self.widgets["latestPacketBearingValue"].setText("---") + self.widgets["latestPacketRangeValue"].setText("---") + + # Ensure the SondeHub upload is set correctly. + self.sondehub_uploader.inhibit = not self.widgets["sondehubUploadSelector"].isChecked() + + # Init FFT Processor + NFFT = 2 ** 13 + STRIDE = 2 ** 13 + self.fft_process = FFTProcess( + nfft=NFFT, + stride=STRIDE, + update_decimation=1, + fs=_sample_rate, + callback=self.add_fft_update + ) + + # Setup Modem + _libpath = "" + if args.libfix: + _libpath = "./" + + self.horus_modem = HorusLib( + libpath=_libpath, + mode=_modem_id, + rate=_modem_rate, + tone_spacing=_modem_tone_spacing, + callback=self.handle_new_packet, + sample_rate=_sample_rate + ) + + # Set manual estimator limits, if enabled + if self.widgets["horusManualEstimatorSelector"].isChecked(): + self.update_manual_estimator() + else: + self.horus_modem.set_estimator_limits(self.DEFAULT_ESTIMATOR_MIN, self.DEFAULT_ESTIMATOR_MAX) + + # Setup Audio (or UDP input) + if _dev_name == 'UDP Audio (127.0.0.1:7355)': + self.audio_stream = UDPStream( + udp_port=7355, + fs=_sample_rate, + block_size=self.fft_process.stride, + fft_input=self.fft_process.add_samples, + modem=self.horus_modem, + stats_callback=self.add_stats_update + ) + else: + self.audio_stream = AudioStream( + _dev_index, + fs=_sample_rate, + block_size=self.fft_process.stride, + fft_input=self.fft_process.add_samples, + modem=self.horus_modem, + stats_callback=self.add_stats_update + ) + + self.widgets["startDecodeButton"].setText("Stop") + self.running = True + logging.info("Started Audio Processing.") + + # Grey out some selectors, so the user cannot adjust them while we are decoding. + self.widgets["audioDeviceSelector"].setEnabled(False) + self.widgets["audioSampleRateSelector"].setEnabled(False) + self.widgets["horusModemSelector"].setEnabled(False) + self.widgets["horusModemRateSelector"].setEnabled(False) + self.widgets["horusMaskEstimatorSelector"].setEnabled(False) # This should really be editable while running. + self.widgets["horusMaskSpacingEntry"].setEnabled(False) # This should really be editable while running + + else: + try: + self.audio_stream.stop() + except Exception as e: + logging.exception("Could not stop audio stream.", exc_info=e) + + try: + self.fft_process.stop() + except Exception as e: + logging.exception("Could not stop fft processing.", exc_info=e) + + try: + self.horus_modem.close() + except Exception as e: + logging.exception("Could not close horus modem.", exc_info=e) + + self.horus_modem = None + + self.fft_update_queue = Queue(256) + self.status_update_queue = Queue(256) + + self.widgets["startDecodeButton"].setText("Start") + self.running = False + + logging.info("Stopped Audio Processing.") + + # Re-Activate selectors. + self.widgets["audioDeviceSelector"].setEnabled(True) + self.widgets["audioSampleRateSelector"].setEnabled(True) + self.widgets["horusModemSelector"].setEnabled(True) + self.widgets["horusModemRateSelector"].setEnabled(True) + self.widgets["horusMaskEstimatorSelector"].setEnabled(True) + self.widgets["horusMaskSpacingEntry"].setEnabled(True) + + + def handle_log_update(self, log_update): + self.widgets["console"].appendPlainText(log_update) + # Make sure the scroll bar is right at the bottom. + _sb = self.widgets["console"].verticalScrollBar() + _sb.setValue(_sb.maximum()) + + + # GUI Update Loop + def processQueues(self): + """ Read in data from the queues, this decouples the GUI and async inputs somewhat. """ + global args + + while self.fft_update_queue.qsize() > 0: + _data = self.fft_update_queue.get() + + self.handle_fft_update(_data) + + while self.status_update_queue.qsize() > 0: + _status = self.status_update_queue.get() + + self.handle_status_update(_status) + + while self.log_update_queue.qsize() > 0: + _log = self.log_update_queue.get() + + self.handle_log_update(_log) + + if self.running: + if self.last_packet_time != None: + _time_delta = int(time.time() - self.last_packet_time) + _time_delta_seconds = int(_time_delta%60) + _time_delta_minutes = int((_time_delta/60) % 60) + _time_delta_hours = int((_time_delta/3600)) + self.widgets['latestDecodedAgeData'].setText(f"{_time_delta_hours:02d}:{_time_delta_minutes:02d}:{_time_delta_seconds:02d}") + + # Try and force a re-draw. + QApplication.processEvents() + + if not self.decoder_init: + # Initialise decoders, and other libraries here. + init_payloads(payload_id_list = args.payload_id_list, custom_field_list = args.custom_field_list) + self.decoder_init = True + # Once initialised, enable the start button + self.widgets["startDecodeButton"].setEnabled(True) + + + # Rotator Control + def startstop_rotator(self): + if self.rotator is None: + # Start a rotator connection. + + try: + _host = self.widgets["rotatorHostEntry"].text() + _port = int(self.widgets["rotatorPortEntry"].text()) + _threshold = float(self.widgets["rotatorThresholdEntry"].text()) + except: + self.widgets["rotatorCurrentStatusValue"].setText("Bad Host/Port") + return + + if self.widgets["rotatorTypeSelector"].currentText() == "rotctld": + try: + self.rotator = ROTCTLD(hostname=_host, port=_port, threshold=_threshold) + self.rotator.connect() + except Exception as e: + logging.error("Rotctld Connect Error: " + str(e)) + self.rotator = None + return + elif self.widgets["rotatorTypeSelector"].currentText() == "PSTRotator": + self.rotator = PSTRotator(hostname=_host, port=_port, threshold=_threshold) + + else: + return + + + self.widgets["rotatorCurrentStatusValue"].setText("Connected") + self.widgets["rotatorConnectButton"].setText("Stop") + else: + # Stop the rotator + self.rotator.close() + self.rotator = None + self.widgets["rotatorConnectButton"].setText("Start") + self.widgets["rotatorCurrentStatusValue"].setText("Not Connected") + self.widgets["rotatorCurrentPositionValue"].setText(f"---˚, --˚") + + + + # def poll_rotator(): + # global rotator, widgets, rotator_current_az, rotator_current_el + + # if rotator: + # _az, _el = rotator.get_azel() + + # if _az != None: + # rotator_current_az = _az + + # if _el != None: + # rotator_current_el = _el + + # self.widgets["rotatorCurrentPositionValue"].setText(f"{rotator_current_az:3.1f}˚, {rotator_current_el:2.1f}˚") + + # rotator_poll_timer = QtCore.QTimer() + # rotator_poll_timer.timeout.connect(poll_rotator) + # rotator_poll_timer.start(2000) class ConsoleHandler(logging.Handler): @@ -1395,50 +1442,19 @@ class ConsoleHandler(logging.Handler): except: print("Console Log Queue full!") - - -# Add console handler to top level logger. -console_handler = ConsoleHandler(log_update_queue) -logging.getLogger().addHandler(console_handler) - - -logging.info("Started GUI.") - - - # Main def main(): + app = QApplication(sys.argv) + app.setStyleSheet(qdarktheme.load_stylesheet()) + window = MainWindow() + app.aboutToQuit.connect(window.cleanup) + window.show() + sys.exit(app.exec()) + # Start the Qt Loop if (sys.flags.interactive != 1) or not hasattr(QtCore, "PYQT_VERSION"): - QtWidgets.QApplication.instance().exec() + QApplication.instance().exec() save_config(widgets) - try: - audio_stream.stop() - except Exception as e: - pass - - try: - fft_process.stop() - except Exception as e: - pass - - try: - sondehub_uploader.close() - except: - pass - - try: - telemetry_logger.close() - except: - pass - - if rotator: - try: - rotator.close() - except: - pass - - if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/horusgui/widgets.py b/horusgui/widgets.py index 781d1a6..3a2576c 100644 --- a/horusgui/widgets.py +++ b/horusgui/widgets.py @@ -1,9 +1,9 @@ # Useful widgets -from PyQt5 import QtWidgets +from PyQt6 import QtWidgets # Useful class for adding horizontal lines. class QHLine(QtWidgets.QFrame): def __init__(self): super(QHLine, self).__init__() - self.setFrameShape(QtWidgets.QFrame.HLine) - self.setFrameShadow(QtWidgets.QFrame.Sunken) + self.setFrameShape(QtWidgets.QFrame.Shape.HLine) + self.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) diff --git a/requirements.txt b/requirements.txt index 024b07a..d9528ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ numpy pyaudio crcmod -PyQt5 +PyQt6 pyqtgraph requests horusdemodlib>=0.3.12 +audioop-lts; python_version>='3.13' \ No newline at end of file From c7418969320275755474b3ca35210d84e7c81170 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Wed, 15 Jan 2025 23:53:43 -0600 Subject: [PATCH 02/32] Add PyQt6 branch to pipeline --- .github/workflows/pull-request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 8e99860..9efbcca 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -3,6 +3,7 @@ name: Build on: push: branches: ["master"] + branches: ["pyqt6"] pull_request: branches: ["master"] From 978503ecdfb2f0a978318ae2c43328bd63aa8107 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Wed, 15 Jan 2025 23:55:14 -0600 Subject: [PATCH 03/32] Update from Python 3.11 to 3.13 --- .github/workflows/pull-request.yml | 6 +++--- horusgui/libhorus.dll | Bin 0 -> 350480 bytes libhorus.dll | Bin 0 -> 350480 bytes 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 horusgui/libhorus.dll create mode 100644 libhorus.dll diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 9efbcca..caa586f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.13' cache: 'pip' # caching pip dependencies - name: Install pyAudio wheel @@ -89,7 +89,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.13' cache: 'pip' # caching pip dependencies - name: Install Homebrew dependencies @@ -149,7 +149,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.13' cache: 'pip' # caching pip dependencies - name: Install Homebrew dependencies diff --git a/horusgui/libhorus.dll b/horusgui/libhorus.dll new file mode 100644 index 0000000000000000000000000000000000000000..ccc36ccc6c615006d25d0b819374fea6a277f360 GIT binary patch literal 350480 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ξ#xs8-f53H~ zoQ&Hjyd&qylcB3&(P@ehkAjhu91a6F#mGt(=^IUJvsSm9;G^Po;aJdR2ZBd@PfxkaL| z@*pB5x8z`vXs=>VJU;XM6jC$ZJR1UrGMIi9rL4Uof0&6AZ;>r=oMj=PU^?G9n|;C% zc1I5Tm8-jJ>5UXh^LbeILWJ>Dd+=1kms<0AB3{6g^)G(sFZjW%pRKKV^RBfh!f?jF z`9=_?8;`>hJZHGYs1s_X(usmt&&AJgNh=1NV)fq-1Z z6st=aYMoGxKS~-6m;z~U0fPrkDu#H}a!qrD&k=NjLJZlc^^09)>C`gi3?H)sh@&j0 zKxTT<(6g@%rXvNwqC&N~GNEQ_4P7mCc#n~$paCy=%Z)W%N|V9a2vM&XZ@O-RGd{~X zSCw(5E{1WYYY0}xnQHTKrZ2g}hnl_&nQ)AcHhme!d5Cp)SSj)Od&4C)e*RQh0DwjQ ziHVO^p(rYThVY7j|3^H~K>y*F>G?T=!Ji3@!!P~=1R2PeuE4{TAlWNNm2Yz3=ht7o z)hDV*%{~9_XGE%C*tZ&~L2439Qis$djYtz>Pn<|g(w1~0-7pK`MS2i_(uedXgGo4v zA~9q%8AHaB43b5(B!~P!7Lgyx8nT{jBHPHHSR25|5g4L{c$u25h2Mz9w;2wemh!CmMj^c98*!-R=KhA>@tY4p}esA7Wd0skfa2RW!LBRsx{ z>rh^Kxdg5o@{TdBir%Ec9+!_?L259I1cSyTuV-zV~ts9gq^0oDw=Z zs#8ql$WZUZh}1Dr37VA9{)wr{5m9Qd#K@@7*u>=2l*p(tiIMSfBSTY?BSNE7Mz@I| zzA0)CwKM6bIGXgZ=yq}d_bFjP@^kN$@nd93RzZ566yE~FO;;Aj3?1S_Hf$r|@ z)srTv;dUgzptf~a_Z|=s!2PW~qQfF1z6y!fghfQhd>Pn3`pf6o@PvdgP58+8s4v4) zG~t?*ilBt3@lh$^W0KTv?&`ok-adh0{ex+6|1h7yK4Jd7y~6@&2yu*S5!iB!liKae z7{T((xP(Z&a7;i>Tefp*5k#U=G+|@n64V~<>ee#-$ZUHhG(4T7`Nu#HwLTFcOPBE9 zZhhTz_EH3(O#dv434(NL? zmD4M7$mNuabnc@+SH3>F)uVEPN`5`vCuIDY*=?xu?U08YCemacAY>&aL=r2fA;dtp z9Gdy%JN{%U>hme--(~7u?jS!hy&`Il_;r5zMTN(!eSL$~iAkEcF>w>ZHF1dv>M^M) z8uiF1b=0`1gfxz8uXYJY?dk{Nuco`N;$St8|V@ij0atRSt_tOpXGr5n%~?k3-@klOkGsj0sNzgPC$aMqt!%lMo zm%rKOVd3sltGhT0zHdDUvX=DE5u(+2$anEP3?3Z6uL@`9* z0ZI}>BKZw&JUMsdB(lcfC++zSF$Ci;8 zk}2pF7>KDv=P%_4<*vsd<5J%6a6=3ky*{NrCVxV({?1(mnw}2+C?`;0RK$}L$!R%s znL_2J0Fr_87aTIb$#)r+99;%0N8*5F55FtO49_wSp4319UMPPuKpBRTN=B+sflv;n z5-25Hh9U#wVC73rtAra;$?#=rIdmRDA?hCGNtkai!WEmIbX_5Ro*+AQ4T>0Rr-xO3!L=|i+L9bHhLZOh5eTeo2)J#C)Btk-pu z>YVyksuj9dd|~=da<-W(4mo;P>L}Kvn*)c5LEpQwu&J0+NRMG%w%f5GPxGbga~#Fz z>UQjh**nE#6APMOrw$vJXhL_?E0vo3RxDL*+L*=S{iLj^^wYekAW26V7wKeSfSez^i0 zwe7rg?>kdEaqKm5x^I-YGVHW?r(PO6K7TZuR&qzmEmc#mqt~TK??J47a<){^q7mD$ ze34{+>KoeiM|XDg=gx6x~K`+IY<)_36 zg`1>7L;BE-J1&T8iocWQKDA{ouEXi91@FbizdLviJG5OK)9(*)^SuUQ_0r$Oj3&+L z-0dmMw`vD!b!8B}^XD^ZvAq$k8v27c0}Dh8XZW#)Yv)jhyke>CFeBEkO`&KORw#}C zSS-c;IU=v`u8-2;2S1D7KT&xe-tk&|TgQo&MmLn6CH$1v{;Y~!U(t+B?m1t4-g44PvHK=>tLpXW zix3C;^3Oo(UvGf;#OQ^z%XyA?=h-RoQ2HjZXnvm5y0lo@HmMG~w9bun=v9qg`e4jF zMh~M0rau*jjId?iOFA(2eShh-%T*~aq#KD{VhG$kThR z5leJ8W9ud!l+s%`h<4hO;x|pJu@&9T>4z?Rqyt~6H+zhl_IR?04%lGH#s!A5k0D#csV8eO zpJQEF`pNM$Y-TOCFR2^b_l+t0b4PV*dC!by>|1ugwK$OtI@N*Q*_cL`TqqVJtJ~81 zBR+`5ivrlUb03Qh+_Koz8!)@OHlY^A&&4jMlf>ga8dK-NBWZ(s*Cgv(8PdHj^=K6J zr00@5u<2dX=-CMmr0?#mke<4}7S|LuqK9_R@f;sw%YHv4(tQqnX?DZwqFb#~2hM(U zm5T1#usS(8Vo&>rQhm>E%yagUycv&@=%h<4^UBYv=(HaXiCw)q(d`SmNz40ppq78^ zlvJ1V#OR>i(%H04V$)vprJ7bo^w5ZIY=NK1CPaP1x(+FnTY30))e!nddsobhv8C~a&Do~S z>Eg{b2NAx^gozmNf#N!8j=*6ueH1Bsx z7d{SPK}Q~lBL*3ZE=&H(vppTm)~=3cle!j)g91ER{LGrHM@v^}bX*`Lmk+SkzTChpl_)VW5$NeUo7@JRF!q@Sc6V@TlKRvLdaQa5^{V||I^LrO9asH=I4|6meJ?rF;&G+Y*4oCj z`$bP?pVwy4Y#lvXFCk<%4YQNO0mzoWJQYcNe z7*2-{iel=-_nsr}xAK_lKU17}yA=&T+C&=qQ(rdgaxgnTf4}t0q!^kJQjN`j-jtbD zTh3~4PGPRwmx?!95PIh7Z1MEAW^5pxB0h7Epnngo#%3gYvCtJ8#i#opNM=(;OUc~@ z+S#ox+xy*PscFhNv3~D7X==o$yuCh8r8zoD9HqT2jvm*RrV767?DH^oVZj@*%T`JJ zIO6t!IwyV+Kd!wm^|;ZHhSrOaPN=KVTP9aT^W1uD)NNZ9V&6u*TTq4eYgvPJtJ8^X zoR&bp4Kk(Gymm@67oU^v-EK!6JN_)4t+q}&Fr>QptV(aG_F+r9#Qm^1vTp-%@x$?w zSK~KQLXFOBcnwQ&fp;P+ndZj!_uVg*FNtRt-!BjcHP4fFT3ryQ&G8jA^=)aBKOEVT z+KVM|TcbR?TQ8-NA12eSYhBrlH@Q;cagAtEqd>g<%9j>5G^Pi_LZm7!7fTbJ=7|Fj zwVt~j=Xt6546%I;H##}No;gpR%3SuhVyDN{Vdq~AqNeJ4tY?FXtbXTSblrr7 z;`DCzY#ALVzW;TLbpHaO>+7u&H>EO>3~$MTG}YM_wKq%iqAa^X8)h?fBqdWFrAKKK z+3zQ;nS-AZefhl+y-+1q>Ug;!ThJwd?wV=C+LZnz=KPo^S|p#4v?nG>>KC^3VC#!w zhscHE(7i@<1psO<__AsP+q1`SPKhtwZK zgT}8G*Iubfm+l`xXEhxp^=TTyPM-gU<-NTsrN`8zbGFu|1z9b{JzE6k-5^Qa)zO?y zt>Vn;7VMJ#3XVDuSAJeH4Q|GUEf~fEt2L&ZK3UWF@nKZ6`&eGkkUq?4SSTC&wjQlD zcA41o{6Mxa{x9k80cxy~Zb`4JMoP_pRngs6IZ~T93+dI`hs9!-^OA$BG0V9=*z;u9 znXD+tn6`N_jCIltrgOYSw)#?m=ZW50d;q{Z9ziN@-4lEyJdYTU@0z1h@; z?l>ukdB*MN-|Fk)`Bg{7VZmK#)2A$NbL;cc&$(Ijo$W>`r^r$YFm_>XSA6N?gJ$%% z)l;R7`R~Q~>n};mPj3{RtWDU~b@5`O(X-k9VTZ*h(GA(B)OHuXU( zx}m-=3ozO%bul|EPC4&DpBvk;{abg41=@#Fi9p+v+tj|u zT|F#Q+SF^bk3aK!aCDd zWj$%62;LnUE)^%u$~*98t2A_ZHJ1BBf#_Us7Oh^R2Kzmxo9DP!-RYFa^=WC3`};FC zMAOF~hf9UaOzEDP<>JnZoevaA8X7RAI=dTIlX*A)ODyyn%x;8l6FtT*lDvLbv)Hm~ zZ0x}Pv{Q%X($N|g>~f9?+tM#ne6Yie1}?Q=O-ee`CnFs5JmTupon<3w(znw*YgOq^ ze+{a|JpPbBCm@73((k6}`?KW0jI?#>tOg1fNokoCTz5B+)aPvVID0NOb}fW5b_ z%AT9vm3&mgq!sm?*o~P1(mO{l_Dg62{Wvm5tc}s(R4h%hCPOf_vYX5$R7fEENo#V3 z)FqU3CTenzgkntDi+qC-vU(VAUV#r;RV7m~0zHr{MW}$W<2Q&|jYJF0NmpV{TuFNp zj9Be3Ze5@JL3Uz1Is+rrzGMeRsVzw|#;j*NG zR7Z;X#14Z)RmdpPoir6jkVV3F;zDYXe#DQQB^}62JgEdnY;jnGrzFx?ctRFnWPCG5 zxzk8v@)t29gFsPw(?}q35F9WP-iD-*B^YfF!6v_gCnDHZ%iD)zn2)J7s+aj!A8d3ln&XF#{22xL$O*~Kr6NN{RzA!QlB@_gC zh#_u5B4#$)2zSXo;TrId6Mh8`-;#!cr|<(AA~X?>kPgC6;fG z%p+rkAYi%(_$`E3NXm0kO*lkuLZW{mBT+|ugnp38XbdOi3cVmjMP!h$8yv4G7z+={ zUeK5dzQqZz$S2gHO~7v|Y$XH8@5sBA5HGwzExJY~385&X%aFMS!c0hFZ3w3uF=6!61 zSn#BephHb-LCQ%PYUCwy9<@4H*aR87NWK@&ldZxq@;C7pvV=^`^{gbN!g`V-V6zjS{Y)mSzjDAqlBM85u0(2^UCzl-XV~ zOPCJ`RUi?^0pq&hBV0ypHh~12gCjA*AHosAQJ8|d`aJK~&&EbS0} z5WdCi;{x&vQs#kYeTBQiD`4F%+=d)Tz&2kv2`(Q({p|(_TgYMLXsmEUc!cYd@J84x ztU!t%gx`b@LIXi3bQEla>yX3W1Z(7RE9Cm2P#}CSyhiQ2Ed&Xd@V5jSM1suZgPME7 z9pNRQod%|RLJ2tYP#7=Ff*w1Ll39a1*2HGpF~Vd>-812!P)uq=M=cjt3%8JdmynHI zLT5&4c8WXg| z9%$DaqV-6Frb$8@SeyI|>tO*}uqlIY#m^INd$3ufTL+g%!&hSEOKNs3( zC}{<~*^V?oTihQlhXu5sHB$TF-2}9mx1iU?qvgy(8_@;2zbEjzLLYX6E=z%?+m05o zH`=vofZrRfbUV-!h*oYmTFbg3$Xqg9qE;THQo@j@wfFj=JjR!1m-2I?` zJ41`|_P;UUHbuK04%=V~@*9A+B2tX*DO&i>pwkjDP4G4r^k0Hy&9g zMk(shCTu|qKMw8C4y3Y2tGWU9ge5K!W!M2$jSc*D(IS2W9?n53YviIeSq0t)fdXs9 z?})r?N6FfPZzBP#0ZMc^D2_z_nm|+aK>mlJ)!c+!HwPcr!CLb|xCVLI1WPCyvhh9m z=L7D=16K%a!@iIVd%UTR@?U|HT#b~q$UN|FC{m9=*;hr&HkHI3Xs%eMz9{J^OcNa7EGRR`te1!*t^z4O3}Z@_hX$gL5| ztp@BmXQbW=*hkQgEki5X3i<5?-u6VD*u`xtz==Su$Omscz{B;Ba|fhy1hjCJ>I_g( zh+NGF?foIE9guG|WT8FqRRxqLptTOddjrF2gw{a042C6_4f!07XA0=uVKw>y;*X#; z96a}gMdXLP&4I)PActom4YN=xj>x+ctn{knKIC~cXsQPehoa7efSN><`y!NCE!5X| z;HZa^Z3THdj~vX$b2ei5fZ`#jvrQ1|0dlW_Tm{3v*#;})G2*^~1+xlr{|N2a4ct8; zbr)eVtU}xR0j*nK#BYim7lAi_Bm6W<_B`@`7iF><;H4lc&P{{}oe z0a{0*gvuc|dvGm;y}1=SVi#oT9?EwQ>g@wqe!r0Cz;*`m_dROfFi6iO#5{zMTu{6i zF!6C?*g=!AA_q;dtq3SfsEgBF|v+ujQt=!JAmVXu8B=kXjkJrYzcMPJ}CYQb$-68UKHV?fVK z)bMAZCI)q_A^75pQf>q}+6@hw2&#&~wQG>Jhmgu2kY*#E9z)X1QG;~g;y6(EBdpYk z;J{1t14VG?Ni5w&CkIQT1SVQbmi zO-Ek0LIU1G_TPgm$8i4(ye|f~4nkM|1!@?iU?)nmBk0)<`nSM86jbkp-gSpI`~dEb zhFk^$(l+Qs2|S$)43iM^B&78Za3T)enT?W~iF^zIZA)O4q#(r=K*<5Oe*?v*QI5+| z&R0>k15rLl0cjCRI|HsOBt0MXdmp&31?96)B1=&-kAdbXfNqY`+zSoz47?*k7NoEU zJmPqHE&T(f^&Itm1~|6^t-v4P%qLL5E%O`5-x`$7LF8jTWOyp-(0$aBQ_#HEA^W{h zM$e%KJ|gTSq-ZnXFr>Z*Nw^BB^8|$T(0Th2?f}kbK~hpsc7qY?TX1S2^0pe9;IiCb z-U#V?iCS3z3a=m^3&5pTkfc=9w+v8Pj@%@GcfX@1Z2^}TLq}f#rnivw*+{t_H6j){ zd7DGlpp0#4pbke`1DV5O#VtAL5aoF@i+XZfLERku{k6e zfb!N5iCi(Fztn3-Q|dKgM_*kKum0`K%1<1Z?hSh>jtMlE#L%Tu{>!+$8oza5?^_-4 z%pUYmtY;P=CO@%d!yDSO=yjdhTpeXSB@1SBx7f2&oix@vW|wIF>ZauB-H1Ltuk~Cv zygt31-;EllSh5c@nzI#URax-c>yp$fn5GrJ6}PePrPIG$5LX7Z%e(l#2dlNVP+Ydx zj1`D!^u-+w4X<`joLtn6%|DVLnmuknM`o+ppJ}eN=xAp4@e}X?IcbO?Vm->(-IBI$+PXrgdOr zR3qtyFjHoI*piKOCDe6Qxuofm;rYwxwbD-e{nE9bqomDeC27u8J8ACJ#?;*Tp7`WC zp?_J_qIV+ecwQ~6A}*{+*beh3x=b_`-#TuR28Zj!v;lAOhOT|>xn+7F%dr2B+Lg4Y z3pD*%l^7G&`t=6U(k3^ruuFUCkBgh76*s!jf-6?6Q*MaZxMOXpnRHpI-N1n+`b`wQ zx2%(vlop6x+WD|WE2oNmFRT*BzG}}*wbjIVQyzOh=vK|S6KqXggPw`|`t1=% zUGB!xEQ6`_a(g(h;PHL2OErqrX~b?L{0Q}VPwI_Aw@ZY-77+$B|a7%Z+BGlAV5e#djR zt|1*+yHGsvss%gdv_@)#IoL+w6Q#?>jac*e<}~$(_2R3BRoKrxYDym0s<2rva;5i! z&q~+s%=a9AwjEtmzEo^h<)hS@1u#v3llXIIN0u|km$e@{iWb*d%JxUO(*xy2QcBJk zTK~2?Yvt6=(>kORTmB}J&ADjG_HL`qHhkNH9)I*$>gTm=KOM#-2X~#c%GQhau*_lU zFJ0)m1{=j+VvEJQqo+#YW2?|lFJ_2B=w)ft%d_I@PbK2MtxSr1G?2Es8^TT<8Y0>D zuP!>xaTB*R@nmibTG6?m%EX_i{Vs00Q!ajIvsZL){!)zjv{Ll2oI!tGb4Ti*SeG>% zx>5?*m@3A1+aykQdX(pKW3A|~EA+g5%7s42_#iFw2obFoRHrTf>PXiV8L`LB-%4lC zn6mF*#nS6Tx>DhfqtcZ%-NbeSf<^Pgd!?pcy$^Kn<1FcKv(oJt?21~3|YKqOK)loVt3#8vSC25<#MX27NaNi86z!{oN)z|mu%b72L@WOqY>v7<^_XJEtjZ>d4KxoV z+ns-k?pF20uo|oA#}C8kuXDQ7(v0QOR4IctDBmHyFYdV4kx6 z{9RJJXSw3bgXXM%=5UGL%M&kNZ^U+ld=kf*6y#l*A0&-l(}rd{Heg}LCW-aL>j(O@ z_F%1s@6Wq;r#`d3)`7muXhQ!!(}tZYyC-(u(uj?)Jt*b>P@SDSu~Q2DxFPT2?J@LX zv)AI)q6gCO-EEj-sw2C&FO^;y70WtZcp*M1j}`-bFG(AUZiy@Ic4fZ`bHoj;oLJLS z4x*dth}2ngMU33@Seo(rpw$28n_@)kgW`sMgw6ToxmX@(OBb!0CB3eGO-w1WVjX(e z(_7JJ#C-Nq`Vfz`9NFg7!P=Q6H_w*RF}d;A4^~X~>bJa*bWhsG!k<1Fw2++$Y@0W_ zYEzcF+=w=ZMc)|KWd!VTJM@Ds(6&d)eYvWzLps7rsfrj5u&5fL#dn3JV~@UuH7xqx zusO`oOE5yJ&T@Z)``pmK7z0Zy2==rydQM%@*Q*W7r8iNd_f`+KdAQtHGDrWx6z?4n z-VnG)0ecL3kUoIyjDA21*i3ctt`B-r9-yi^sHp~w)zOGmMGvq$?6sbN>>&5Wn;?Dz zq;!QP*cLH5ffZ{0`j-8YC54ekp+9MHLQ{}z-|evq!D^D zqXDfUth|Yc-x>KC1v~9~^sC}w({w*C&8#Y=XENMSjPa^2fgsqth z9t;N`yD^|TL}v-5jOF7P|yvu@$r9q@MHpD zWdUnrSi8*tKLNb50(FC6UxosD7ua29u=4^zS08Y25NtMI^fA5xul>;%+Xd`i;QnlI zrZcRe)H-vX4`YRG4E(6T%p8y;87w{+&w(K-mx))&2u7iI)Z28NwZM_kY z^FY%vaP&{~2ksyji3pLvr{96+JS^fpu;EX_X1)YUgK;lK%Gq)cJ_@~+({|m(E4w!pU+8ZEEI=~(cyLvrhY(nTM z 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 Vr%`VM8rO{#DfLj>m{Ff={1-EFj}QO= literal 0 HcmV?d00001 diff --git a/libhorus.dll b/libhorus.dll new file mode 100644 index 0000000000000000000000000000000000000000..ccc36ccc6c615006d25d0b819374fea6a277f360 GIT binary patch literal 350480 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ξ#xs8-f53H~ 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 z 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 Vr%`VM8rO{#DfLj>m{Ff={1-EFj}QO= literal 0 HcmV?d00001 From 10637af98e143fba37a0b9be50e12cc04153bb10 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Thu, 16 Jan 2025 00:00:31 -0600 Subject: [PATCH 04/32] Update actions --- .github/workflows/pull-request.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index caa586f..746b383 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -2,8 +2,7 @@ name: Build on: push: - branches: ["master"] - branches: ["pyqt6"] + branches: ["master", "pyqt6"] pull_request: branches: ["master"] From 8735ea0f628af2ab88775579f026732dc9bb38f0 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Thu, 16 Jan 2025 00:09:41 -0600 Subject: [PATCH 05/32] Revert to Python 3.11 on Windows due to numpy build failure --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 746b383..8d3adc9 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.13' + python-version: '3.11' cache: 'pip' # caching pip dependencies - name: Install pyAudio wheel From 334ca67431e23a9c7fdf1573e2b1bc472fd72332 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Thu, 16 Jan 2025 00:19:55 -0600 Subject: [PATCH 06/32] Updated requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d9528ea..7b03403 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ PyQt6 pyqtgraph requests horusdemodlib>=0.3.12 -audioop-lts; python_version>='3.13' \ No newline at end of file +audioop-lts; python_version>='3.13' +pyqtdarktheme \ No newline at end of file From 45128384e7e0d2e83b0d01ad06df89b7629a387d Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Thu, 16 Jan 2025 00:27:03 -0600 Subject: [PATCH 07/32] Tweaks for class compliance --- horusgui/gui.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index d768f4d..937b02a 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -985,20 +985,18 @@ class MainWindow(QMainWindow): return np.max(self.widgets["snrPlotSNR"]) - def add_fft_update(data): + def add_fft_update(self, data): """ Try and insert a new set of FFT data into the update queue """ - global fft_update_queue try: - fft_update_queue.put_nowait(data) + self.fft_update_queue.put_nowait(data) except: logging.error("FFT Update Queue Full!") - def add_stats_update(frame): + def add_stats_update(self, frame): """ Try and insert modem statistics into the processing queue """ - global status_update_queue try: - status_update_queue.put_nowait(frame) + self.status_update_queue.put_nowait(frame) except: logging.error("Status Update Queue Full!") From a261bfca580450b52b8c1da141082993839b16a5 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Thu, 16 Jan 2025 20:50:56 -0600 Subject: [PATCH 08/32] Remove pyqtdarktheme and let PyQt6 use the system default --- horusgui/gui.py | 4 ++-- requirements.txt | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 937b02a..220f6d4 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -26,7 +26,7 @@ from PyQt6.QtWidgets import * from PyQt6.QtGui import * from PyQt6.QtCore import * from pyqtgraph.dockarea import * -import qdarktheme +# import qdarktheme from threading import Thread from .widgets import * @@ -1443,7 +1443,7 @@ class ConsoleHandler(logging.Handler): # Main def main(): app = QApplication(sys.argv) - app.setStyleSheet(qdarktheme.load_stylesheet()) + # app.setStyleSheet(qdarktheme.load_stylesheet()) window = MainWindow() app.aboutToQuit.connect(window.cleanup) window.show() diff --git a/requirements.txt b/requirements.txt index 7b03403..d9528ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,4 @@ PyQt6 pyqtgraph requests horusdemodlib>=0.3.12 -audioop-lts; python_version>='3.13' -pyqtdarktheme \ No newline at end of file +audioop-lts; python_version>='3.13' \ No newline at end of file From fc16eeaf126dfabbb76e953220d8573bb705a18d Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Thu, 16 Jan 2025 23:53:06 -0600 Subject: [PATCH 09/32] Converted from Dock() to QGroupBox() --- horusgui/gui.py | 181 +++++++++++++++++++++++++++++++----------------- 1 file changed, 116 insertions(+), 65 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 220f6d4..fa9f483 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -156,47 +156,28 @@ class MainWindow(QMainWindow): # GUI Creation - The Bad way. # - # Create a Qt App. - pg.mkQApp() - # GUI LAYOUT - Gtk Style! - area = DockArea() - self.setCentralWidget(area) self.setWindowTitle(f"Horus Telemetry GUI - v{__version__}") self.setWindowIcon(getHorusIcon()) - # Create multiple dock areas, for displaying our data. - d0 = Dock("Audio", size=(300, 50)) - d0_modem = Dock("Modem", size=(300, 80)) - d0_habitat = Dock("SondeHub", size=(300, 200)) - d0_other = Dock("Other", size=(300, 100)) - d0_rotator = Dock("Rotator", size=(300, 100)) - d1 = Dock("Spectrum", size=(800, 350)) - d2_stats = Dock("SNR (dB)", size=(50, 300)) - d2_snr = Dock("SNR Plot", size=(750, 300)) - d3_data = Dock("Data", size=(800, 50)) - d3_position = Dock("Position", size=(800, 50)) - d4 = Dock("Log", size=(800, 150)) - # Arrange docks. - area.addDock(d0) - area.addDock(d1, "right", d0) - area.addDock(d0_modem, "bottom", d0) - area.addDock(d0_habitat, "bottom", d0_modem) - area.addDock(d0_other, "below", d0_habitat) - area.addDock(d0_rotator, "below", d0_other) - area.addDock(d2_stats, "bottom", d1) - area.addDock(d3_data, "bottom", d2_stats) - area.addDock(d3_position, "bottom", d3_data) - area.addDock(d4, "bottom", d3_position) - area.addDock(d2_snr, "right", d2_stats) - d0_habitat.raiseDock() + self.mainWidget = QWidget() + self.setCentralWidget(self.mainWidget) + self.mainLayout = QGridLayout() + self.mainWidget.setLayout(self.mainLayout) + # Left Column VBox + left_column = QGridLayout() # Controls - w1_audio = pg.LayoutWidget() - # TNC Connection + w1_audio_groupbox = QGroupBox('Audio') + w1_audio_groupbox.setObjectName("b1") + w1_audio_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + w1_audio = QGridLayout(w1_audio_groupbox) + + # Audio Parameters self.widgets["audioDeviceLabel"] = QLabel("Audio Device:") self.widgets["audioDeviceSelector"] = QComboBox() + self.widgets["audioDeviceSelector"].setFixedWidth(300) # Dirty, but it needed to be done self.widgets["audioDeviceSelector"].currentIndexChanged.connect(self.update_audio_sample_rates) self.widgets["audioSampleRateLabel"] = QLabel("Sample Rate (Hz):") @@ -212,12 +193,14 @@ class MainWindow(QMainWindow): w1_audio.addWidget(self.widgets["audioSampleRateSelector"], 1, 1, 1, 2) w1_audio.addWidget(self.widgets["audioDbfsLabel"], 2, 0, 1, 1) w1_audio.addWidget(self.widgets["audioDbfsValue"], 2, 1, 1, 2) - d0.addWidget(w1_audio) - - w1_modem = pg.LayoutWidget() - + w1_audio_groupbox.setLayout(w1_audio) # Modem Parameters + w1_modem_groupbox = QGroupBox('Modem') + w1_modem_groupbox.setObjectName("b1") + w1_modem_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + w1_modem = QGridLayout(w1_modem_groupbox) + self.widgets["horusModemLabel"] = QLabel("Mode:") self.widgets["horusModemSelector"] = QComboBox() self.widgets["horusModemSelector"].currentIndexChanged.connect(self.update_modem_settings) @@ -267,11 +250,13 @@ class MainWindow(QMainWindow): w1_modem.addWidget(self.widgets["horusManualEstimatorLabel"], 4, 0, 1, 1) w1_modem.addWidget(self.widgets["horusManualEstimatorSelector"], 4, 1, 1, 1) w1_modem.addWidget(self.widgets["startDecodeButton"], 5, 0, 2, 2) - - d0_modem.addWidget(w1_modem) + for i in range(w1_modem.columnCount()): + w1_modem.setColumnStretch(i, 1) + w1_modem_groupbox.setLayout(w1_modem) - w1_habitat = pg.LayoutWidget() + w1_habitat_groupbox = QGroupBox('SondeHub') + w1_habitat = QGridLayout(w1_habitat_groupbox) # Listener Information self.widgets["habitatHeading"] = QLabel("SondeHub Settings") self.widgets["sondehubUploadLabel"] = QLabel("Enable SondeHub-Ham Upload:") @@ -343,12 +328,14 @@ class MainWindow(QMainWindow): w1_habitat.addWidget(self.widgets["dialFreqEntry"], 6, 1, 1, 2) w1_habitat.addWidget(self.widgets["habitatUploadPosition"], 7, 0, 1, 3) w1_habitat.addWidget(self.widgets["sondehubPositionNotesLabel"], 8, 0, 1, 3) - w1_habitat.layout.setRowStretch(9, 1) + w1_habitat.setRowStretch(9, 1) w1_habitat.addWidget(self.widgets["saveSettingsButton"], 10, 0, 1, 3) + for i in range(w1_habitat.columnCount()): + w1_habitat.setColumnStretch(i, 1) + w1_habitat_groupbox.setLayout(w1_habitat) - d0_habitat.addWidget(w1_habitat) - - w1_other = pg.LayoutWidget() + w1_other_groupbox = QGroupBox("Other") + w1_other = QGridLayout(w1_other_groupbox) self.widgets["horusHeaderLabel"] = QLabel("Telemetry Forwarding") self.widgets["horusUploadLabel"] = QLabel("Enable Horus UDP Output:") self.widgets["horusUploadSelector"] = QCheckBox() @@ -425,12 +412,14 @@ class MainWindow(QMainWindow): w1_other.addWidget(self.widgets["otherHeaderLabel"], 10, 0, 1, 2) w1_other.addWidget(self.widgets["inhibitCRCLabel"], 11, 0, 1, 1) w1_other.addWidget(self.widgets["inhibitCRCSelector"], 11, 1, 1, 1) - w1_other.layout.setRowStretch(12, 1) - - d0_other.addWidget(w1_other) + w1_other.setRowStretch(12, 1) + for i in range(w1_other.columnCount()): + w1_other.setColumnStretch(i, 1) + w1_other_groupbox.setLayout(w1_other) - w1_rotator = pg.LayoutWidget() + w1_rotator_groupbox = QGroupBox("Rotator") + w1_rotator = QGridLayout(w1_rotator_groupbox) self.widgets["rotatorHeaderLabel"] = QLabel("Rotator Control") self.widgets["rotatorTypeLabel"] = QLabel("Rotator Type:") @@ -484,11 +473,28 @@ class MainWindow(QMainWindow): w1_rotator.addWidget(self.widgets["rotatorCurrentStatusValue"], 5, 1, 1, 1) w1_rotator.addWidget(self.widgets["rotatorCurrentPositionLabel"], 6, 0, 1, 1) w1_rotator.addWidget(self.widgets["rotatorCurrentPositionValue"], 6, 1, 1, 1) + w1_rotator.setRowStretch(7, 1) + for i in range(w1_rotator.columnCount()): + w1_rotator.setColumnStretch(i, 1) - w1_rotator.layout.setRowStretch(7, 1) + w1_rotator_groupbox.setLayout(w1_rotator) - d0_rotator.addWidget(w1_rotator) + w1_tab_widget = QTabWidget() + w1_tab_widget.setTabPosition(QTabWidget.TabPosition.North) + w1_tab_widget.tabBar().setExpanding(True) + w1_tab_widget.addTab(w1_habitat_groupbox, "SondeHub") + w1_tab_widget.addTab(w1_other_groupbox, "Other") + w1_tab_widget.addTab(w1_rotator_groupbox, "Rotator") + w1_tab_widget.setStyleSheet("QTabBar {font: bold 14px;}") + # Add widgets to left column + left_column.addWidget(w1_audio_groupbox, 0, 0, 1, 1) + left_column.addWidget(w1_modem_groupbox, 1, 0, 1, 1) + left_column.addWidget(w1_tab_widget, 2, 0, 1, 1) + # left_column.maximumSize(QSize.setWidth(225)) + + # Right Column QGrid (Grid for merged cells) + right_column = QGridLayout() # Spectrum Display self.widgets["spectrumPlot"] = pg.PlotWidget(title="Spectra") @@ -537,12 +543,18 @@ class MainWindow(QMainWindow): self.widgets["estimatorRange"].setBounds([100,4000]) self.widgets["estimatorRange"].sigRegionChangeFinished.connect(self.update_manual_estimator) - d1.addWidget(self.widgets["spectrumPlot"]) + w2_spectrum_groupbox = QGroupBox("Spectrum") + w2_spectrum_groupbox.setObjectName("b1") + w2_spectrum_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + spectrum = QGridLayout(w2_spectrum_groupbox) + spectrum.addWidget(self.widgets["spectrumPlot"]) self.widgets["spectrumPlotRange"] = [-100, -20] - - w3_stats = pg.LayoutWidget() + w3_stats_groupbox = QGroupBox("SNR (dB)") + w3_stats_groupbox.setObjectName("b1") + w3_stats_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + w3_stats = QGridLayout(w3_stats_groupbox) self.widgets["snrBar"] = QProgressBar() self.widgets["snrBar"].setOrientation(QtCore.Qt.Orientation.Vertical) self.widgets["snrBar"].setRange(-10, 15) @@ -554,13 +566,17 @@ class MainWindow(QMainWindow): self.widgets["snrLabel"].setFont(QFont("Courier New", 14)) w3_stats.addWidget(self.widgets["snrBar"], 0, 1, 1, 1) w3_stats.addWidget(self.widgets["snrLabel"], 1, 0, 1, 3) - w3_stats.layout.setColumnStretch(0, 2) - w3_stats.layout.setColumnStretch(2, 2) + w3_stats.setColumnStretch(0, 2) + w3_stats.setColumnStretch(2, 2) + + w3_stats_groupbox.setLayout(w3_stats) - d2_stats.addWidget(w3_stats) # SNR Plot - w3_snr = pg.LayoutWidget() + w3_snr_groupbox = QGroupBox("SNR Plot") + w3_snr_groupbox.setObjectName("b1") + w3_snr_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + w3_snr = QGridLayout(w3_snr_groupbox) self.widgets["snrPlot"] = pg.PlotWidget(title="SNR") self.widgets["snrPlot"].setLabel("left", "SNR (dB)") self.widgets["snrPlot"].setLabel("bottom", "Time (s)") @@ -572,6 +588,9 @@ class MainWindow(QMainWindow): self.widgets["snrPlotTime"] = np.array([]) self.widgets["snrPlotSNR"] = np.array([]) self.widgets["snrPlotData"] = self.widgets["snrPlot"].plot(self.widgets["snrPlotTime"], self.widgets["snrPlotSNR"]) + w3_snr.addWidget(self.widgets["snrPlot"]) + + w3_snr_groupbox.setLayout(w3_snr) # TODO: Look into eye diagram more # self.widgets["eyeDiagramPlot"] = pg.PlotWidget(title="Eye Diagram") @@ -581,10 +600,11 @@ class MainWindow(QMainWindow): #w3.addWidget(self.widgets["eyeDiagramPlot"], 0, 1) - d2_snr.addWidget(self.widgets["snrPlot"]) - # Telemetry Data - w4_data = pg.LayoutWidget() + w4_data_groupbox = QGroupBox("Data") + w4_data_groupbox.setObjectName("b1") + w4_data_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + w4_data = QGridLayout(w4_data_groupbox) self.widgets["latestRawSentenceLabel"] = QLabel("Latest Packet (Raw):") self.widgets["latestRawSentenceData"] = QLineEdit("NO DATA") self.widgets["latestRawSentenceData"].setReadOnly(True) @@ -599,9 +619,13 @@ class MainWindow(QMainWindow): w4_data.addWidget(self.widgets["latestDecodedSentenceData"], 1, 1, 1, 6) w4_data.addWidget(self.widgets["latestDecodedAgeLabel"], 2, 0, 1, 1) w4_data.addWidget(self.widgets["latestDecodedAgeData"], 2, 1, 1, 2) - d3_data.addWidget(w4_data) - w4_position = pg.LayoutWidget() + w4_data_groupbox.setLayout(w4_data) + + w4_position_groupbox = QGroupBox("Position") + w4_position_groupbox.setObjectName("b1") + w4_position_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + w4_position = QGridLayout(w4_position_groupbox) # This font seems to look bigger in Windows... not sure why. if 'Windows' in platform.system(): POSITION_LABEL_FONT_SIZE = 14 @@ -649,14 +673,41 @@ class MainWindow(QMainWindow): w4_position.addWidget(self.widgets["latestPacketElevationValue"], 1, 8, 1, 1) w4_position.addWidget(self.widgets["latestPacketRangeLabel"], 0, 9, 1, 1) w4_position.addWidget(self.widgets["latestPacketRangeValue"], 1, 9, 1, 1) - w4_position.layout.setRowStretch(1, 6) - d3_position.addWidget(w4_position) + w4_position.setRowStretch(1, 6) - w5 = pg.LayoutWidget() + w4_position_groupbox.setLayout(w4_position) + + w5_groupbox = QGroupBox("Log") + w5_groupbox.setObjectName("b1") + w5_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + w5 = QGridLayout(w5_groupbox) self.widgets["console"] = QPlainTextEdit() self.widgets["console"].setReadOnly(True) w5.addWidget(self.widgets["console"]) - d4.addWidget(w5) + + w5_groupbox.setLayout(w5) + + right_column.addWidget(w2_spectrum_groupbox, 0, 0, 1, 2) + right_column.addWidget(w3_stats_groupbox, 1, 0, 1, 1) + right_column.addWidget(w3_snr_groupbox, 1, 1, 1, 1) + right_column.addWidget(w4_data_groupbox, 2, 0, 1, 2) + right_column.addWidget(w4_position_groupbox, 3, 0, 1, 2) + right_column.addWidget(w5_groupbox, 4, 0, 1, 2) + + right_column.setColumnStretch(0, 1) + right_column.setColumnStretch(1, 9) + + self.mainLayout.addLayout(left_column, 0, 0, 1, 1) + self.mainLayout.addLayout(right_column, 0, 1, 1, 1) + + # Grid: (Row, Column, RowSpan, ColumnSpan) + # self.mainLayout.setVerticalSpacing(4) + # self.mainLayout.addWidget(self.titleLabel, 0, 0, 1, 1) + # self.mainLayout.addWidget(self.tabWidget, 1, 0, 1, 1) + # self.mainLayout.addLayout(self.rightLayout, 1, 1, 1, 1) + # self.mainLayout.setContentsMargins(0, 25, 25, 25) + self.mainLayout.setColumnStretch(0, 0) + self.mainLayout.setColumnStretch(1, 10) # Resize window to final resolution, and display. logging.info("Starting GUI.") From efd7961199ad8dfea9075ddaaf6f2bfd545df11a Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Wed, 22 Jan 2025 20:53:28 -0600 Subject: [PATCH 10/32] Add telemetry groupbox --- horusgui/gui.py | 147 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 113 insertions(+), 34 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index fa9f483..32a95d2 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -65,6 +65,12 @@ logging.basicConfig( format="%(asctime)s %(levelname)s: %(message)s", level=_log_level ) +# This font seems to look bigger in Windows... not sure why. +if 'Windows' in platform.system(): + POSITION_LABEL_FONT_SIZE = 14 +else: + POSITION_LABEL_FONT_SIZE = 16 + # Establish signals and worker for multi-threaded use class WorkerSignals(QObject): finished = pyqtSignal() @@ -166,7 +172,7 @@ class MainWindow(QMainWindow): self.mainWidget.setLayout(self.mainLayout) # Left Column VBox - left_column = QGridLayout() + left_column = QVBoxLayout() # Controls w1_audio_groupbox = QGroupBox('Audio') @@ -250,8 +256,6 @@ class MainWindow(QMainWindow): w1_modem.addWidget(self.widgets["horusManualEstimatorLabel"], 4, 0, 1, 1) w1_modem.addWidget(self.widgets["horusManualEstimatorSelector"], 4, 1, 1, 1) w1_modem.addWidget(self.widgets["startDecodeButton"], 5, 0, 2, 2) - for i in range(w1_modem.columnCount()): - w1_modem.setColumnStretch(i, 1) w1_modem_groupbox.setLayout(w1_modem) @@ -330,8 +334,6 @@ class MainWindow(QMainWindow): w1_habitat.addWidget(self.widgets["sondehubPositionNotesLabel"], 8, 0, 1, 3) w1_habitat.setRowStretch(9, 1) w1_habitat.addWidget(self.widgets["saveSettingsButton"], 10, 0, 1, 3) - for i in range(w1_habitat.columnCount()): - w1_habitat.setColumnStretch(i, 1) w1_habitat_groupbox.setLayout(w1_habitat) w1_other_groupbox = QGroupBox("Other") @@ -413,8 +415,6 @@ class MainWindow(QMainWindow): w1_other.addWidget(self.widgets["inhibitCRCLabel"], 11, 0, 1, 1) w1_other.addWidget(self.widgets["inhibitCRCSelector"], 11, 1, 1, 1) w1_other.setRowStretch(12, 1) - for i in range(w1_other.columnCount()): - w1_other.setColumnStretch(i, 1) w1_other_groupbox.setLayout(w1_other) @@ -474,8 +474,6 @@ class MainWindow(QMainWindow): w1_rotator.addWidget(self.widgets["rotatorCurrentPositionLabel"], 6, 0, 1, 1) w1_rotator.addWidget(self.widgets["rotatorCurrentPositionValue"], 6, 1, 1, 1) w1_rotator.setRowStretch(7, 1) - for i in range(w1_rotator.columnCount()): - w1_rotator.setColumnStretch(i, 1) w1_rotator_groupbox.setLayout(w1_rotator) @@ -488,9 +486,17 @@ class MainWindow(QMainWindow): w1_tab_widget.setStyleSheet("QTabBar {font: bold 14px;}") # Add widgets to left column - left_column.addWidget(w1_audio_groupbox, 0, 0, 1, 1) - left_column.addWidget(w1_modem_groupbox, 1, 0, 1, 1) - left_column.addWidget(w1_tab_widget, 2, 0, 1, 1) + # w1_audio_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) + # w1_modem_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) + # w1_tab_widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) + # w1_habitat_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) + # w1_other_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) + # w1_rotator_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) + + left_column.addWidget(w1_audio_groupbox) #, 0, 0, 1, 1) + left_column.addWidget(w1_modem_groupbox) #, 1, 0, 1, 1) + left_column.addWidget(w1_tab_widget) #, 2, 0, 1, 1) + #left_column. # left_column.maximumSize(QSize.setWidth(225)) # Right Column QGrid (Grid for merged cells) @@ -626,11 +632,6 @@ class MainWindow(QMainWindow): w4_position_groupbox.setObjectName("b1") w4_position_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') w4_position = QGridLayout(w4_position_groupbox) - # This font seems to look bigger in Windows... not sure why. - if 'Windows' in platform.system(): - POSITION_LABEL_FONT_SIZE = 14 - else: - POSITION_LABEL_FONT_SIZE = 16 self.widgets["latestPacketCallsignLabel"] = QLabel("Callsign") self.widgets["latestPacketCallsignValue"] = QLabel("---") @@ -677,25 +678,87 @@ class MainWindow(QMainWindow): w4_position_groupbox.setLayout(w4_position) - w5_groupbox = QGroupBox("Log") - w5_groupbox.setObjectName("b1") - w5_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') - w5 = QGridLayout(w5_groupbox) + w5_telemetry_groupbox = QGroupBox("Telemetry") + w5_telemetry_groupbox.setObjectName("b1") + w5_telemetry_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + self.w5_telemetry = QGridLayout(w5_telemetry_groupbox) + w5_telemetry_groupbox.setLayout(self.w5_telemetry) + + # These are placeholders and will be updated when telemetry is received. + self.widgets["latestTelem0Label"] = QLabel("Battery Voltage") + self.widgets["latestTelem0Value"] = QLabel("---") + self.widgets["latestTelem0Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelem1Label"] = QLabel("External Temperature") + self.widgets["latestTelem1Value"] = QLabel("---") + self.widgets["latestTelem1Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelem2Label"] = QLabel("External Humidity") + self.widgets["latestTelem2Value"] = QLabel("---") + self.widgets["latestTelem2Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelem3Label"] = QLabel("External Pressure") + self.widgets["latestTelem3Value"] = QLabel("---") + self.widgets["latestTelem3Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelem4Label"] = QLabel() + self.widgets["latestTelem4Value"] = QLabel() + self.widgets["latestTelem4Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelem5Label"] = QLabel() + self.widgets["latestTelem5Value"] = QLabel() + self.widgets["latestTelem5Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelem6Label"] = QLabel() + self.widgets["latestTelem6Value"] = QLabel() + self.widgets["latestTelem6Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelem7Label"] = QLabel() + self.widgets["latestTelem7Value"] = QLabel() + self.widgets["latestTelem7Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelem8Label"] = QLabel() + self.widgets["latestTelem8Value"] = QLabel() + self.widgets["latestTelem8Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + + self.w5_telemetry.addWidget(self.widgets["latestTelem0Label"], 0, 0, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem0Value"], 1, 0, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem1Label"], 0, 1, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem1Value"], 1, 1, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem2Label"], 0, 2, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem2Value"], 1, 2, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem3Label"], 0, 3, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem3Value"], 1, 3, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem4Label"], 0, 4, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem4Value"], 1, 4, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem5Label"], 0, 5, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem5Value"], 1, 5, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem6Label"], 0, 6, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem6Value"], 1, 6, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem7Label"], 0, 7, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem7Value"], 1, 7, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem8Label"], 0, 8, 1, 1) + self.w5_telemetry.addWidget(self.widgets["latestTelem8Value"], 1, 8, 1, 1) + self.w5_telemetry.setRowStretch(1, 6) + + w6_groupbox = QGroupBox("Log") + w6_groupbox.setObjectName("b1") + w6_groupbox.setStyleSheet('QWidget#b1 { font-size: 15px; font-weight: bold}') + w6 = QGridLayout(w6_groupbox) self.widgets["console"] = QPlainTextEdit() self.widgets["console"].setReadOnly(True) - w5.addWidget(self.widgets["console"]) + w6.addWidget(self.widgets["console"]) - w5_groupbox.setLayout(w5) + w6_groupbox.setLayout(w6) - right_column.addWidget(w2_spectrum_groupbox, 0, 0, 1, 2) - right_column.addWidget(w3_stats_groupbox, 1, 0, 1, 1) - right_column.addWidget(w3_snr_groupbox, 1, 1, 1, 1) - right_column.addWidget(w4_data_groupbox, 2, 0, 1, 2) - right_column.addWidget(w4_position_groupbox, 3, 0, 1, 2) - right_column.addWidget(w5_groupbox, 4, 0, 1, 2) + right_column.addWidget(w2_spectrum_groupbox, 0, 0, 1, 1) + right_column.addWidget(w3_snr_groupbox, 0, 1, 1, 1) + right_column.addWidget(w3_stats_groupbox, 0, 2, 1, 1) + right_column.addWidget(w4_data_groupbox, 1, 0, 1, 3) + right_column.addWidget(w4_position_groupbox, 2, 0, 1, 3) + right_column.addWidget(w5_telemetry_groupbox, 3, 0, 1, 3) + right_column.addWidget(w6_groupbox, 4, 0, 1, 3) - right_column.setColumnStretch(0, 1) - right_column.setColumnStretch(1, 9) + right_column.setColumnStretch(0, 10) + right_column.setColumnStretch(1, 6) + right_column.setColumnStretch(2, 1) + + # right_column.setRowStretch(0, 5) + # right_column.setRowStretch(1, 3) + # right_column.setRowStretch(2, 3) + # right_column.setRowStretch(3, 3) self.mainLayout.addLayout(left_column, 0, 0, 1, 1) self.mainLayout.addLayout(right_column, 0, 1, 1, 1) @@ -707,11 +770,11 @@ class MainWindow(QMainWindow): # self.mainLayout.addLayout(self.rightLayout, 1, 1, 1, 1) # self.mainLayout.setContentsMargins(0, 25, 25, 25) self.mainLayout.setColumnStretch(0, 0) - self.mainLayout.setColumnStretch(1, 10) + self.mainLayout.setColumnStretch(1, 1) # Resize window to final resolution, and display. logging.info("Starting GUI.") - self.resize(1500, 800) + self.resize(1500, self.minimumSize().height()) self.post_initialize() @@ -1136,7 +1199,7 @@ class MainWindow(QMainWindow): self.widgets["latestRawSentenceData"].setText(f"{_packet} ({_snr:.1f} dB SNR)") self.widgets["latestDecodedSentenceData"].setText(_decoded['ukhas_str']) - last_packet_time = time.time() + self.last_packet_time = time.time() # Upload the string to Sondehub Amateur if self.widgets["userCallEntry"].text() == "N0CALL": logging.warning("Uploader callsign is set as N0CALL. Please change this, otherwise telemetry data may be discarded!") @@ -1158,6 +1221,22 @@ class MainWindow(QMainWindow): self.widgets["latestPacketLongitudeValue"].setText(f"{_decoded['longitude']:.5f}") self.widgets["latestPacketAltitudeValue"].setText(f"{_decoded['altitude']}") + # Update telemetry fields + if len(_decoded['custom_field_names']) > 0: + column = 0 + for field in _decoded['custom_field_names']: + self.widgets[f"latestTelem{column}Label"].setText(f"{field}") + self.widgets[f"latestTelem{column}Value"].setText(f"{_decoded[field]}") + + column += 1 + + # Hide remaining columns + if column < 8: + for i in range(column, 9): + self.widgets[f"latestTelem{column}Label"].hide() + self.widgets[f"latestTelem{column}Value"].hide() + + # Attempt to update the range/elevation/bearing fields. try: _station_lat = float(self.widgets["userLatEntry"].text()) From 796e917275466bc4c59fa4fe2fbe7f73f7d58d5f Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Fri, 24 Jan 2025 23:00:33 -0600 Subject: [PATCH 11/32] Added range inhibit checkbox to prevent rotator from chasing position jitter locally --- horusgui/gui.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 32a95d2..0e2c129 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -441,8 +441,16 @@ class MainWindow(QMainWindow): "Default for rotctld: 4533\n"\ "Default for PSTRotator: 12000" ) + self.widgets["rotatorRangeInhibitLabel"] = QLabel("Inhibit Local Movement:") + self.widgets["rotatorRangeInhibitCheckBox"] = QCheckBox() + self.widgets["rotatorRangeInhibitCheckBox"].setChecked(True) + self.widgets["rotatorRangeInhibitCheckBox"].setToolTip( + "Inhibit Horus GUI from sending rotator position updates\n"\ + "if range is less than 250 meters. This is useful if testing\n"\ + "transmitter in close vicinity of receiver." + ) self.widgets["rotatorThresholdLabel"] = QLabel("Rotator Movement Threshold:") - self.widgets["rotatorThresholdEntry"] = QLineEdit("5.0") + self.widgets["rotatorThresholdEntry"] = QLineEdit("2.5") self.widgets["rotatorThresholdEntry"].setToolTip( "Only move if the angle between the payload position and \n"\ "the current rotator position is more than this, in degrees." @@ -468,11 +476,13 @@ class MainWindow(QMainWindow): w1_rotator.addWidget(self.widgets["rotatorPortEntry"], 3, 1, 1, 1) #w1_rotator.addWidget(self.widgets["rotatorThresholdLabel"], 4, 0, 1, 1) #w1_rotator.addWidget(self.widgets["rotatorThresholdEntry"], 4, 1, 1, 1) - w1_rotator.addWidget(self.widgets["rotatorConnectButton"], 4, 0, 1, 2) - w1_rotator.addWidget(self.widgets["rotatorCurrentStatusLabel"], 5, 0, 1, 1) - w1_rotator.addWidget(self.widgets["rotatorCurrentStatusValue"], 5, 1, 1, 1) - w1_rotator.addWidget(self.widgets["rotatorCurrentPositionLabel"], 6, 0, 1, 1) - w1_rotator.addWidget(self.widgets["rotatorCurrentPositionValue"], 6, 1, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorRangeInhibitLabel"], 5, 0, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorRangeInhibitCheckBox"], 5, 1, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorConnectButton"], 6, 0, 1, 2) + w1_rotator.addWidget(self.widgets["rotatorCurrentStatusLabel"], 7, 0, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorCurrentStatusValue"], 7, 1, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorCurrentPositionLabel"], 8, 0, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorCurrentPositionValue"], 8, 1, 1, 1) w1_rotator.setRowStretch(7, 1) w1_rotator_groupbox.setLayout(w1_rotator) @@ -1253,7 +1263,12 @@ class MainWindow(QMainWindow): self.widgets['latestPacketElevationValue'].setText(f"{_position_info['elevation']:.1f}") self.widgets['latestPacketRangeValue'].setText(f"{_position_info['straight_distance']/1000.0:.1f}") - if self.rotator and not ( _decoded['latitude'] == 0.0 and _decoded['longitude'] == 0.0 ): + _range_inhibit = False + if self.widgets["rotatorRangeInhibitCheckBox"].isChecked() and (_position_info['straight_distance'] < 250): + logging.debug("Rotator - Not moving due to Range Inhibit (less than 250m)") + _range_inhibit = True + + if self.rotator and not ( _decoded['latitude'] == 0.0 and _decoded['longitude'] == 0.0 ) and not _range_inhibit: try: self.rotator.set_azel(_position_info['bearing'], _position_info['elevation'], check_response=False) self.widgets["rotatorCurrentPositionValue"].setText(f"{_position_info['bearing']:3.1f}˚, {_position_info['elevation']:2.1f}˚") From 1a93e2e795cd67dacc98d378c8c1846a378268db Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Fri, 24 Jan 2025 23:14:15 -0600 Subject: [PATCH 12/32] Add rotator range inhibit to persistent config --- horusgui/config.py | 3 +++ horusgui/gui.py | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/horusgui/config.py b/horusgui/config.py index 126ade6..86a19b1 100644 --- a/horusgui/config.py +++ b/horusgui/config.py @@ -34,6 +34,7 @@ default_config = { "rotator_type": "rotctld", "rotator_host": "localhost", "rotator_port": 4533, + "rotator_rangeinhibit": True, "logging_enabled": False, "log_format": "CSV", "log_directory": "", @@ -124,6 +125,7 @@ def read_config(widgets): widgets["rotatorTypeSelector"].setCurrentText(default_config["rotator_type"]) widgets["rotatorHostEntry"].setText(str(default_config["rotator_host"])) widgets["rotatorPortEntry"].setText(str(default_config["rotator_port"])) + widgets["rotatorRangeInhibit"].setChecked(default_config["rotator_rangeinhibit"]) # Logging Settings widgets["loggingPathEntry"].setText(str(default_config["log_directory"])) @@ -173,6 +175,7 @@ def save_config(widgets): default_config["rotator_type"] = widgets["rotatorTypeSelector"].currentText() default_config["rotator_host"] = widgets["rotatorHostEntry"].text() default_config["rotator_port"] = int(widgets["rotatorPortEntry"].text()) + default_config["rotator_rangeinhibit"] = widgets["rotatorRangeInhibit"].isChecked() default_config["logging_enabled"] = widgets["enableLoggingSelector"].isChecked() default_config["log_directory"] = widgets["loggingPathEntry"].text() default_config["log_format"] = widgets["loggingFormatSelector"].currentText() diff --git a/horusgui/gui.py b/horusgui/gui.py index 0e2c129..91b434f 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -442,9 +442,9 @@ class MainWindow(QMainWindow): "Default for PSTRotator: 12000" ) self.widgets["rotatorRangeInhibitLabel"] = QLabel("Inhibit Local Movement:") - self.widgets["rotatorRangeInhibitCheckBox"] = QCheckBox() - self.widgets["rotatorRangeInhibitCheckBox"].setChecked(True) - self.widgets["rotatorRangeInhibitCheckBox"].setToolTip( + self.widgets["rotatorRangeInhibit"] = QCheckBox() + self.widgets["rotatorRangeInhibit"].setChecked(True) + self.widgets["rotatorRangeInhibit"].setToolTip( "Inhibit Horus GUI from sending rotator position updates\n"\ "if range is less than 250 meters. This is useful if testing\n"\ "transmitter in close vicinity of receiver." @@ -477,7 +477,7 @@ class MainWindow(QMainWindow): #w1_rotator.addWidget(self.widgets["rotatorThresholdLabel"], 4, 0, 1, 1) #w1_rotator.addWidget(self.widgets["rotatorThresholdEntry"], 4, 1, 1, 1) w1_rotator.addWidget(self.widgets["rotatorRangeInhibitLabel"], 5, 0, 1, 1) - w1_rotator.addWidget(self.widgets["rotatorRangeInhibitCheckBox"], 5, 1, 1, 1) + w1_rotator.addWidget(self.widgets["rotatorRangeInhibit"], 5, 1, 1, 1) w1_rotator.addWidget(self.widgets["rotatorConnectButton"], 6, 0, 1, 2) w1_rotator.addWidget(self.widgets["rotatorCurrentStatusLabel"], 7, 0, 1, 1) w1_rotator.addWidget(self.widgets["rotatorCurrentStatusValue"], 7, 1, 1, 1) @@ -1264,7 +1264,7 @@ class MainWindow(QMainWindow): self.widgets['latestPacketRangeValue'].setText(f"{_position_info['straight_distance']/1000.0:.1f}") _range_inhibit = False - if self.widgets["rotatorRangeInhibitCheckBox"].isChecked() and (_position_info['straight_distance'] < 250): + if self.widgets["rotatorRangeInhibit"].isChecked() and (_position_info['straight_distance'] < 250): logging.debug("Rotator - Not moving due to Range Inhibit (less than 250m)") _range_inhibit = True From ff726ee1377872509a344124de8a4b02ce067e10 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 02:06:59 -0600 Subject: [PATCH 13/32] GUI threading improvements --- horusgui/audio.py | 19 ++- horusgui/fft.py | 14 +- horusgui/gui.py | 170 +++++++++++---------- horusgui/habitat.py | 353 ------------------------------------------- horusgui/rotators.py | 14 +- horusgui/udpaudio.py | 15 +- 6 files changed, 128 insertions(+), 457 deletions(-) delete mode 100644 horusgui/habitat.py diff --git a/horusgui/audio.py b/horusgui/audio.py index 3aaf07b..043b489 100755 --- a/horusgui/audio.py +++ b/horusgui/audio.py @@ -1,6 +1,7 @@ # Audio Interfacing import logging import pyaudio +import time # Global PyAudio object @@ -125,8 +126,14 @@ class AudioStream(object): self.modem = modem self.stats_callback = stats_callback - # Start audio stream + self.audio_thread_running = True + self.audio = pyaudio.PyAudio() + + def start_stream(self, info_callback=None): + if info_callback: + self.stats_callback = info_callback + self.stream = self.audio.open( format=pyaudio.paInt16, channels=1, @@ -138,6 +145,11 @@ class AudioStream(object): stream_callback=self.handle_samples, ) + while self.audio_thread_running: + time.sleep(0.5) + + logging.debug("Stopped audio stream thread") + def handle_samples(self, data, frame_count, time_info="", status_flags=""): """ Handle incoming samples from pyaudio """ @@ -151,10 +163,11 @@ class AudioStream(object): # Send any stats data back to the stats callback if _stats: if self.stats_callback: - self.stats_callback(_stats) + self.stats_callback.emit(_stats) return (None, pyaudio.paContinue) def stop(self): """ Halt stream """ - self.stream.close() + self.audio_thread_running = False + self.stream.close() \ No newline at end of file diff --git a/horusgui/fft.py b/horusgui/fft.py index 319f1b4..cf0cb70 100644 --- a/horusgui/fft.py +++ b/horusgui/fft.py @@ -3,7 +3,7 @@ import logging import time import numpy as np from queue import Queue -from threading import Thread +#from threading import Thread class FFTProcess(object): @@ -37,8 +37,8 @@ class FFTProcess(object): self.processing_thread_running = True - self.t = Thread(target=self.processing_thread) - self.t.start() + #self.t = Thread(target=self.processing_thread) + #self.t.start() def init_window(self): """ Initialise Window functions and FFT scales. """ @@ -74,7 +74,7 @@ class FFTProcess(object): if self.callback != None: if self.update_counter % self.update_decimation == 0: - self.callback({"fft": _fft[self.mask], "scale": self.fft_scale[self.mask], 'dbfs': _dbfs}) + self.callback.emit({"fft": _fft[self.mask], "scale": self.fft_scale[self.mask], 'dbfs': _dbfs}) self.update_counter += 1 @@ -86,7 +86,9 @@ class FFTProcess(object): while len(self.sample_buffer) > self.nfft * self.sample_width: self.perform_fft() - def processing_thread(self): + def processing_thread(self, info_callback=None): + if info_callback: + self.callback = info_callback while self.processing_thread_running: if self.input_queue.qsize() > 0: @@ -95,6 +97,8 @@ class FFTProcess(object): else: time.sleep(0.01) + logging.debug("Stopped FFT processing thread") + def add_samples(self, samples): """ Add a block of samples to the input queue """ try: diff --git a/horusgui/gui.py b/horusgui/gui.py index 91b434f..4f14591 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -21,7 +21,6 @@ import platform import time import pyqtgraph as pg import numpy as np -from queue import Queue from PyQt6.QtWidgets import * from PyQt6.QtGui import * from PyQt6.QtCore import * @@ -73,9 +72,9 @@ else: # Establish signals and worker for multi-threaded use class WorkerSignals(QObject): - finished = pyqtSignal() + # finished = pyqtSignal() error = pyqtSignal(tuple) - result = pyqtSignal(object) + # result = pyqtSignal(object) info = pyqtSignal(object) class Worker(QRunnable): @@ -97,10 +96,10 @@ class Worker(QRunnable): traceback.print_exc() exctype, value = sys.exc_info()[:2] self.signals.error.emit((exctype, value, traceback.format_exc())) - else: - self.signals.result.emit(result) - finally: - self.signals.finished.emit() + # else: + # self.signals.result.emit(result) + # finally: + # self.signals.finished.emit() def resource_path(relative_path): try: @@ -127,11 +126,6 @@ class MainWindow(QMainWindow): # Global widget store self.widgets = {} - # Queues for handling updates to image / status indications. - self.fft_update_queue = Queue(1024) - self.status_update_queue = Queue(1024) - self.log_update_queue = Queue(2048) - # List of audio devices and their info self.audio_devices = {} @@ -142,8 +136,6 @@ class MainWindow(QMainWindow): self.sondehub_uploader = None self.telemetry_logger = None - self.decoder_init = False - self.last_packet_time = None # Rotator object @@ -828,18 +820,27 @@ class MainWindow(QMainWindow): enabled = self.widgets["enableLoggingSelector"].isChecked() ) - self.gui_update_timer = QTimer() - self.gui_update_timer.timeout.connect(self.processQueues) - self.gui_update_timer.start(100) + # Init payload IDs and such in singleShot timer + self.payload_init_timer = QTimer() + self.payload_init_timer.singleShot(100, self.payload_init) # Add console handler to top level logger. - console_handler = ConsoleHandler(self.log_update_queue) + console_handler = ConsoleHandler(self.handle_log_update) logging.getLogger().addHandler(console_handler) logging.info("Started GUI.") def cleanup(self): + self.running = False + + try: + if self.horus_modem: + self.horus_modem.close() + self.horus_modem = None + except Exception as e: + pass + try: self.audio_stream.stop() except Exception as e: @@ -855,17 +856,17 @@ class MainWindow(QMainWindow): except: pass - try: - self.telemetry_logger.close() - except: - pass - if self.rotator: try: self.rotator.close() except: pass + try: + self.telemetry_logger.close() + except: + pass + def update_audio_sample_rates(self): """ Update the sample-rate dropdown when a different audio device is selected. """ @@ -1006,7 +1007,7 @@ class MainWindow(QMainWindow): save_config(self.widgets) - # Handlers for data arriving via queues. + # Handlers for data arriving via callbacks def handle_fft_update(self, data): """ Handle a new FFT update """ @@ -1109,24 +1110,6 @@ class MainWindow(QMainWindow): return np.max(self.widgets["snrPlotSNR"]) - def add_fft_update(self, data): - """ Try and insert a new set of FFT data into the update queue """ - try: - self.fft_update_queue.put_nowait(data) - except: - logging.error("FFT Update Queue Full!") - - - def add_stats_update(self, frame): - """ Try and insert modem statistics into the processing queue """ - try: - self.status_update_queue.put_nowait(frame) - except: - logging.error("Status Update Queue Full!") - - - - def handle_new_packet(self, frame): """ Handle receipt of a newly decoded packet """ @@ -1374,9 +1357,16 @@ class MainWindow(QMainWindow): stride=STRIDE, update_decimation=1, fs=_sample_rate, - callback=self.add_fft_update ) + # Create FFT Processor worker thread + worker = Worker(self.fft_process.processing_thread) + # worker.signals.result.connect(self.null_thread_complete) + # worker.signals.finished.connect(self.null_thread_complete) + worker.signals.info.connect(self.handle_fft_update) + + self.threadpool.start(worker) + # Setup Modem _libpath = "" if args.libfix: @@ -1405,8 +1395,14 @@ class MainWindow(QMainWindow): block_size=self.fft_process.stride, fft_input=self.fft_process.add_samples, modem=self.horus_modem, - stats_callback=self.add_stats_update ) + + # Create UDP stream worker thread + worker = Worker(self.audio_stream.udp_listen_thread) + worker.signals.info.connect(self.handle_status_update) + self.threadpool.start(worker) + + else: self.audio_stream = AudioStream( _dev_index, @@ -1414,13 +1410,25 @@ class MainWindow(QMainWindow): block_size=self.fft_process.stride, fft_input=self.fft_process.add_samples, modem=self.horus_modem, - stats_callback=self.add_stats_update ) + # Create AudioStream worker thred + worker = Worker(self.audio_stream.start_stream) + worker.signals.info.connect(self.handle_status_update) + self.threadpool.start(worker) + self.widgets["startDecodeButton"].setText("Stop") self.running = True logging.info("Started Audio Processing.") + # Start thread to update the last packet age + worker = Worker(self.decoded_age_thread) + # worker.signals.result.connect(self.null_thread_complete) + # worker.signals.finished.connect(self.null_thread_complete) + worker.signals.info.connect(self.handle_decoded_age_update) + + self.threadpool.start(worker) + # Grey out some selectors, so the user cannot adjust them while we are decoding. self.widgets["audioDeviceSelector"].setEnabled(False) self.widgets["audioSampleRateSelector"].setEnabled(False) @@ -1447,9 +1455,6 @@ class MainWindow(QMainWindow): self.horus_modem = None - self.fft_update_queue = Queue(256) - self.status_update_queue = Queue(256) - self.widgets["startDecodeButton"].setText("Start") self.running = False @@ -1463,7 +1468,6 @@ class MainWindow(QMainWindow): self.widgets["horusMaskEstimatorSelector"].setEnabled(True) self.widgets["horusMaskSpacingEntry"].setEnabled(True) - def handle_log_update(self, log_update): self.widgets["console"].appendPlainText(log_update) # Make sure the scroll bar is right at the bottom. @@ -1471,44 +1475,29 @@ class MainWindow(QMainWindow): _sb.setValue(_sb.maximum()) - # GUI Update Loop - def processQueues(self): - """ Read in data from the queues, this decouples the GUI and async inputs somewhat. """ - global args + # Payload init + def payload_init(self): + global args - while self.fft_update_queue.qsize() > 0: - _data = self.fft_update_queue.get() + # Initialise decoders, and other libraries here. + init_payloads(payload_id_list = args.payload_id_list, custom_field_list = args.custom_field_list) + # Once initialised, enable the start button + self.widgets["startDecodeButton"].setEnabled(True) - self.handle_fft_update(_data) - - while self.status_update_queue.qsize() > 0: - _status = self.status_update_queue.get() - - self.handle_status_update(_status) - - while self.log_update_queue.qsize() > 0: - _log = self.log_update_queue.get() - - self.handle_log_update(_log) - - if self.running: + # Thread to update last packet age + def decoded_age_thread(self, info_callback): + while self.running: if self.last_packet_time != None: _time_delta = int(time.time() - self.last_packet_time) _time_delta_seconds = int(_time_delta%60) _time_delta_minutes = int((_time_delta/60) % 60) _time_delta_hours = int((_time_delta/3600)) - self.widgets['latestDecodedAgeData'].setText(f"{_time_delta_hours:02d}:{_time_delta_minutes:02d}:{_time_delta_seconds:02d}") + info_callback.emit(f"{_time_delta_hours:02d}:{_time_delta_minutes:02d}:{_time_delta_seconds:02d}") - # Try and force a re-draw. - QApplication.processEvents() - - if not self.decoder_init: - # Initialise decoders, and other libraries here. - init_payloads(payload_id_list = args.payload_id_list, custom_field_list = args.custom_field_list) - self.decoder_init = True - # Once initialised, enable the start button - self.widgets["startDecodeButton"].setEnabled(True) + time.sleep(0.5) + def handle_decoded_age_update(self, text): + self.widgets['latestDecodedAgeData'].setText(text) # Rotator Control def startstop_rotator(self): @@ -1533,7 +1522,16 @@ class MainWindow(QMainWindow): return elif self.widgets["rotatorTypeSelector"].currentText() == "PSTRotator": self.rotator = PSTRotator(hostname=_host, port=_port, threshold=_threshold) + + # Create worker thread for commanding rotator + worker = Worker(self.rotator.azel_rx_loop) + worker.signals.info.connect(self.info_callback) + self.threadpool.start(worker) + # Create worker thread for receiving info from rotator + worker = Worker(self.rotator.azel_poll_loop) + worker.signals.info.connect(self.info_callback) + self.threadpool.start(worker) else: return @@ -1568,22 +1566,28 @@ class MainWindow(QMainWindow): # rotator_poll_timer.timeout.connect(poll_rotator) # rotator_poll_timer.start(2000) + # Dummy function to call from worker threads + def null_thread_complete(self): + logging.debug("Thread exit!!!") + return class ConsoleHandler(logging.Handler): """ Logging handler to write to the GUI console """ - def __init__(self, log_queue): + def __init__(self, callback): logging.Handler.__init__(self) - self.log_queue = log_queue + self.signaller = WorkerSignals() + self.signaller.info.connect(callback) def emit(self, record): _time = datetime.datetime.now() _text = f"{_time.strftime('%H:%M:%S')} [{record.levelname}] {record.msg}" - + + # TODO -- create gentle dismount when exiting try: - self.log_queue.put_nowait(_text) + self.signaller.info.emit(_text) except: - print("Console Log Queue full!") + pass # Main def main(): diff --git a/horusgui/habitat.py b/horusgui/habitat.py deleted file mode 100644 index e450389..0000000 --- a/horusgui/habitat.py +++ /dev/null @@ -1,353 +0,0 @@ -#!/usr/bin/env python -# -# Horus Telemetry GUI - Habitat Uploader -# -# Mark Jessop -# - -import datetime -import json -import logging -import random -import requests -import time -from base64 import b64encode -from hashlib import sha256 -from queue import Queue -from threading import Thread - - -class HabitatUploader(object): - """ - Queued Habitat Telemetry Uploader class - - Packets to be uploaded to Habitat are added to a queue for uploading. - If an upload attempt times out, the packet is discarded. - If the queue fills up (probably indicating no network connection, and a fast packet downlink rate), - it is immediately emptied, to avoid upload of out-of-date packets. - """ - - HABITAT_URL = "http://habitat.habhub.org/" - HABITAT_DB = "habitat" - HABITAT_UUIDS = HABITAT_URL + "_uuids?count=%d" - HABITAT_DB_URL = HABITAT_URL + HABITAT_DB + "/" - - def __init__( - self, - user_callsign="FSK_DEMOD", - listener_lat=0.0, - listener_lon=0.0, - listener_radio="", - listener_antenna="", - queue_size=64, - upload_timeout=10, - upload_retries=5, - upload_retry_interval=0.25, - inhibit=False, - ): - """ Create a Habitat Uploader object. """ - - self.upload_timeout = upload_timeout - self.upload_retries = upload_retries - self.upload_retry_interval = upload_retry_interval - self.queue_size = queue_size - self.habitat_upload_queue = Queue(queue_size) - self.inhibit = inhibit - - # Listener information - self.user_callsign = user_callsign - self.listener_lat = listener_lat - self.listener_lon = listener_lon - self.listener_radio = listener_radio - self.listener_antenna = listener_antenna - self.position_uploaded = False - - self.last_freq_hz = None - - self.callsign_init = False - self.uuids = [] - - # Start the uploader thread. - self.habitat_uploader_running = True - self.uploadthread = Thread(target=self.habitat_upload_thread) - self.uploadthread.start() - - def habitat_upload(self, sentence): - """ Upload a UKHAS-standard telemetry sentence to Habitat """ - - # Generate payload to be uploaded - # b64encode accepts and returns bytes objects. - _sentence_b64 = b64encode(sentence.encode("ascii")) - _date = datetime.datetime.utcnow().isoformat("T") + "Z" - _user_call = self.user_callsign - - _data = { - "type": "payload_telemetry", - "data": { - "_raw": _sentence_b64.decode( - "ascii" - ) # Convert back to a string to be serialisable - }, - "receivers": { - _user_call: {"time_created": _date, "time_uploaded": _date,}, - }, - } - - if self.last_freq_hz: - # Add in frequency information if we have it. - _data["receivers"][_user_call]["rig_info"] = {"frequency": self.last_freq_hz} - - # The URl to upload to. - _url = f"{self.HABITAT_URL}{self.HABITAT_DB}/_design/payload_telemetry/_update/add_listener/{sha256(_sentence_b64).hexdigest()}" - - # Delay for a random amount of time between 0 and upload_retry_interval*2 seconds. - time.sleep(random.random() * self.upload_retry_interval * 2.0) - - _retries = 0 - - # When uploading, we have three possible outcomes: - # - Can't connect. No point re-trying in this situation. - # - The packet is uploaded successfult (201 / 403) - # - There is a upload conflict on the Habitat DB end (409). We can retry and it might work. - while _retries < self.upload_retries: - # Run the request. - try: - _req = requests.put( - _url, data=json.dumps(_data), timeout=(self.upload_timeout, 6.1) - ) - except Exception as e: - logging.error("Habitat - Upload Failed: %s" % str(e)) - break - - if _req.status_code == 201 or _req.status_code == 403: - # 201 = Success, 403 = Success, sentence has already seen by others. - logging.info(f"Habitat - Uploaded sentence: {sentence.strip()}") - _upload_success = True - break - elif _req.status_code == 409: - # 409 = Upload conflict (server busy). Sleep for a moment, then retry. - logging.debug("Habitat - Upload conflict.. retrying.") - time.sleep(random.random() * self.upload_retry_interval) - _retries += 1 - else: - logging.error( - "Habitat - Error uploading to Habitat. Status Code: %d." - % _req.status_code - ) - break - - if _retries == self.upload_retries: - logging.error( - "Habitat - Upload conflict not resolved with %d retries." - % self.upload_retries - ) - - return - - def habitat_upload_thread(self): - """ Handle uploading of packets to Habitat """ - - logging.info("Started Habitat Uploader Thread.") - - while self.habitat_uploader_running: - - if self.habitat_upload_queue.qsize() > 0: - # If the queue is completely full, jump to the most recent telemetry sentence. - if self.habitat_upload_queue.qsize() == self.queue_size: - while not self.habitat_upload_queue.empty(): - sentence = self.habitat_upload_queue.get() - - logging.warning( - "Habitat uploader queue was full - possible connectivity issue." - ) - else: - # Otherwise, get the first item in the queue. - sentence = self.habitat_upload_queue.get() - - # Attempt to upload it. - self.habitat_upload(sentence) - - else: - # Wait for a short time before checking the queue again. - time.sleep(0.5) - - # - # Habitat listener position update disabled 2022-09, due to Habitat going away... - # - # if not self.position_uploaded: - # # Validate the lat/lon entries. - # try: - # _lat = float(self.listener_lat) - # _lon = float(self.listener_lon) - - # if (_lat != 0.0) or (_lon != 0.0): - # _success = self.uploadListenerPosition( - # self.user_callsign, - # _lat, - # _lon, - # self.listener_radio, - # self.listener_antenna, - # ) - # else: - # logging.warning("Listener position set to 0.0/0.0 - not uploading.") - - # except Exception as e: - # logging.error("Error uploading listener position: %s" % str(e)) - - # # Set this flag regardless if the upload worked. - # # The user can trigger a re-upload. - # self.position_uploaded = True - - - logging.info("Stopped Habitat Uploader Thread.") - - def add(self, sentence): - """ Add a sentence to the upload queue """ - - if self.inhibit: - # We have upload inhibited. Return. - return - - # Handling of arbitrary numbers of $$'s at the start of a sentence: - # Extract the data part of the sentence (i.e. everything after the $$'s') - sentence = sentence.split("$")[-1] - # Now add the *correct* number of $$s back on. - sentence = "$$" + sentence - - if not (sentence[-1] == "\n"): - sentence += "\n" - - try: - self.habitat_upload_queue.put_nowait(sentence) - except Exception as e: - logging.error("Error adding sentence to queue: %s" % str(e)) - - def close(self): - """ Shutdown uploader thread. """ - self.habitat_uploader_running = False - - def ISOStringNow(self): - return "%sZ" % datetime.datetime.utcnow().isoformat() - - def postListenerData(self, doc, timeout=10): - - # do we have at least one uuid, if not go get more - if len(self.uuids) < 1: - self.fetchUuids() - - # Attempt to add UUID and time data to document. - try: - doc["_id"] = self.uuids.pop() - except IndexError: - logging.error( - "Habitat - Unable to post listener data - no UUIDs available." - ) - return False - - doc["time_uploaded"] = self.ISOStringNow() - - try: - _r = requests.post( - f"{self.HABITAT_URL}{self.HABITAT_DB}/", json=doc, timeout=timeout - ) - return True - except Exception as e: - logging.error("Habitat - Could not post listener data - %s" % str(e)) - return False - - def fetchUuids(self, timeout=10): - - _retries = 5 - - while _retries > 0: - try: - _r = requests.get(self.HABITAT_UUIDS % 10, timeout=timeout) - self.uuids.extend(_r.json()["uuids"]) - logging.debug("Habitat - Got UUIDs") - return - except Exception as e: - logging.error( - "Habitat - Unable to fetch UUIDs, retrying in 2 seconds - %s" - % str(e) - ) - time.sleep(2) - _retries = _retries - 1 - continue - - logging.error("Habitat - Gave up trying to get UUIDs.") - return - - def initListenerCallsign(self, callsign, radio="", antenna=""): - doc = { - "type": "listener_information", - "time_created": self.ISOStringNow(), - "data": {"callsign": callsign, "antenna": antenna, "radio": radio,}, - } - - resp = self.postListenerData(doc) - - if resp is True: - logging.debug("Habitat - Listener Callsign Initialized.") - return True - else: - logging.error("Habitat - Unable to initialize callsign.") - return False - - def uploadListenerPosition(self, callsign, lat, lon, radio="", antenna=""): - """ Initializer Listener Callsign, and upload Listener Position """ - - # Attempt to initialize the listeners callsign - resp = self.initListenerCallsign(callsign, radio=radio, antenna=antenna) - # If this fails, it means we can't contact the Habitat server, - # so there is no point continuing. - if resp is False: - return False - - doc = { - "type": "listener_telemetry", - "time_created": self.ISOStringNow(), - "data": { - "callsign": callsign, - "chase": False, - "latitude": lat, - "longitude": lon, - "altitude": 0, - "speed": 0, - }, - } - - # post position to habitat - resp = self.postListenerData(doc) - if resp is True: - logging.info("Habitat - Listener information uploaded.") - return True - else: - logging.error("Habitat - Unable to upload listener information.") - return False - - def trigger_position_upload(self): - """ Trigger a re-upload of the listener position """ - self.position_uploaded = False - - -if __name__ == "__main__": - - # Setup Logging - logging.basicConfig( - format="%(asctime)s %(levelname)s: %(message)s", level=logging.INFO - ) - - habitat = HabitatUploader( - user_callsign="HORUSGUI_TEST", - listener_lat=-34.0, - listener_lon=138.0, - listener_radio="Testing Habitat Uploader", - listener_antenna="Wet Noodle", - ) - - habitat.add("$$DUMMY,0,0.0,0.0*F000") - - time.sleep(10) - habitat.trigger_position_upload() - time.sleep(5) - habitat.close() diff --git a/horusgui/rotators.py b/horusgui/rotators.py index 20dd76b..6c8e591 100644 --- a/horusgui/rotators.py +++ b/horusgui/rotators.py @@ -9,7 +9,7 @@ import socket import time import logging import traceback -from threading import Thread +# from threading import Thread class ROTCTLD(object): """ rotctld (hamlib) communication class """ @@ -112,11 +112,11 @@ class PSTRotator(object): self.poll_rate = poll_rate self.azel_thread_running = True - self.t_rx = Thread(target=self.azel_rx_loop) - self.t_rx.start() + # self.t_rx = Thread(target=self.azel_rx_loop) + # self.t_rx.start() - self.t_poll = Thread(target=self.azel_poll_loop) - self.t_poll.start() + # self.t_poll = Thread(target=self.azel_poll_loop) + # self.t_poll.start() def close(self): @@ -157,13 +157,13 @@ class PSTRotator(object): except: pass - def azel_poll_loop(self): + def azel_poll_loop(self, info_callback=None): while self.azel_thread_running: self.poll_azel() logging.debug("Poll sent to PSTRotator.") time.sleep(self.poll_rate) - def azel_rx_loop(self): + def azel_rx_loop(self, info_callback=None): """ Listen for Azimuth and Elevation reports from PSTRotator""" s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) s.settimeout(1) diff --git a/horusgui/udpaudio.py b/horusgui/udpaudio.py index 3fb9132..b245678 100644 --- a/horusgui/udpaudio.py +++ b/horusgui/udpaudio.py @@ -1,7 +1,8 @@ # UDP Audio Source (Obtaining audio from GQRX) import socket import traceback -from threading import Thread +#from threading import Thread + class UDPStream(object): """ Listen for UDP Audio data from GQRX (s16, 48kHz), and pass data around to different callbacks """ @@ -19,13 +20,16 @@ class UDPStream(object): # Start audio stream self.listen_thread_running = True - self.listen_thread = Thread(target=self.udp_listen_thread) - self.listen_thread.start() + #self.listen_thread = Thread(target=self.udp_listen_thread) + #self.listen_thread.start() - def udp_listen_thread(self): + def udp_listen_thread(self, info_callback=None): """ Open a UDP socket and listen for incoming data """ + if info_callback: + self.stats_callback = info_callback + self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) self.s.settimeout(1) self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -50,7 +54,6 @@ class UDPStream(object): self.s.close() - def handle_samples(self, data, frame_count, time_info="", status_flags=""): """ Handle incoming samples from pyaudio """ @@ -64,7 +67,7 @@ class UDPStream(object): # Send any stats data back to the stats callback if _stats: if self.stats_callback: - self.stats_callback(_stats) + self.stats_callback.emit(_stats) return (None, None) From f67fb49c9557088b458d3f53301f9289a0160245 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 02:12:31 -0600 Subject: [PATCH 14/32] Graduation --- horusgui/gui.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 4f14591..433f1ce 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -150,10 +150,6 @@ class MainWindow(QMainWindow): self.initialize() def initialize(self): - # - # GUI Creation - The Bad way. - # - # GUI LAYOUT - Gtk Style! self.setWindowTitle(f"Horus Telemetry GUI - v{__version__}") self.setWindowIcon(getHorusIcon()) From 826c6698ca4bb4e6e777d541fd07ffba4e22e983 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 10:30:13 -0600 Subject: [PATCH 15/32] Fix rotatorRangeInhibit persistent config parser --- horusgui/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horusgui/config.py b/horusgui/config.py index 86a19b1..7fb987d 100644 --- a/horusgui/config.py +++ b/horusgui/config.py @@ -125,7 +125,7 @@ def read_config(widgets): widgets["rotatorTypeSelector"].setCurrentText(default_config["rotator_type"]) widgets["rotatorHostEntry"].setText(str(default_config["rotator_host"])) widgets["rotatorPortEntry"].setText(str(default_config["rotator_port"])) - widgets["rotatorRangeInhibit"].setChecked(default_config["rotator_rangeinhibit"]) + widgets["rotatorRangeInhibit"].setChecked(ValueToBool(default_config["rotator_rangeinhibit"])) # Logging Settings widgets["loggingPathEntry"].setText(str(default_config["log_directory"])) From 9b991215addeb85489425e58bd0d237db36c89d8 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 10:41:47 -0600 Subject: [PATCH 16/32] Change right column row stretch to log box --- horusgui/gui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 433f1ce..5c4b940 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -672,7 +672,7 @@ class MainWindow(QMainWindow): w4_position.addWidget(self.widgets["latestPacketElevationValue"], 1, 8, 1, 1) w4_position.addWidget(self.widgets["latestPacketRangeLabel"], 0, 9, 1, 1) w4_position.addWidget(self.widgets["latestPacketRangeValue"], 1, 9, 1, 1) - w4_position.setRowStretch(1, 6) + #w4_position.setRowStretch(1, 6) w4_position_groupbox.setLayout(w4_position) @@ -729,7 +729,7 @@ class MainWindow(QMainWindow): self.w5_telemetry.addWidget(self.widgets["latestTelem7Value"], 1, 7, 1, 1) self.w5_telemetry.addWidget(self.widgets["latestTelem8Label"], 0, 8, 1, 1) self.w5_telemetry.addWidget(self.widgets["latestTelem8Value"], 1, 8, 1, 1) - self.w5_telemetry.setRowStretch(1, 6) + #self.w5_telemetry.setRowStretch(1, 6) w6_groupbox = QGroupBox("Log") w6_groupbox.setObjectName("b1") @@ -738,6 +738,7 @@ class MainWindow(QMainWindow): self.widgets["console"] = QPlainTextEdit() self.widgets["console"].setReadOnly(True) w6.addWidget(self.widgets["console"]) + w6.setRowStretch(0, 1) w6_groupbox.setLayout(w6) From 7f87805537d4d4342695d07aff0ed8a8052bf9bc Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 10:55:49 -0600 Subject: [PATCH 17/32] Horizontal stretching improvements --- horusgui/gui.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 5c4b940..7c5dcd6 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -484,7 +484,7 @@ class MainWindow(QMainWindow): w1_tab_widget.setStyleSheet("QTabBar {font: bold 14px;}") # Add widgets to left column - # w1_audio_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) + # w1_audio_groupbox.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) # w1_modem_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) # w1_tab_widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) # w1_habitat_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) @@ -494,8 +494,6 @@ class MainWindow(QMainWindow): left_column.addWidget(w1_audio_groupbox) #, 0, 0, 1, 1) left_column.addWidget(w1_modem_groupbox) #, 1, 0, 1, 1) left_column.addWidget(w1_tab_widget) #, 2, 0, 1, 1) - #left_column. - # left_column.maximumSize(QSize.setWidth(225)) # Right Column QGrid (Grid for merged cells) right_column = QGridLayout() @@ -752,24 +750,14 @@ class MainWindow(QMainWindow): right_column.setColumnStretch(0, 10) right_column.setColumnStretch(1, 6) - right_column.setColumnStretch(2, 1) - - # right_column.setRowStretch(0, 5) - # right_column.setRowStretch(1, 3) - # right_column.setRowStretch(2, 3) - # right_column.setRowStretch(3, 3) + right_column.setColumnStretch(2, 1) + # Grid: (Row, Column, RowSpan, ColumnSpan) self.mainLayout.addLayout(left_column, 0, 0, 1, 1) self.mainLayout.addLayout(right_column, 0, 1, 1, 1) - # Grid: (Row, Column, RowSpan, ColumnSpan) - # self.mainLayout.setVerticalSpacing(4) - # self.mainLayout.addWidget(self.titleLabel, 0, 0, 1, 1) - # self.mainLayout.addWidget(self.tabWidget, 1, 0, 1, 1) - # self.mainLayout.addLayout(self.rightLayout, 1, 1, 1, 1) - # self.mainLayout.setContentsMargins(0, 25, 25, 25) - self.mainLayout.setColumnStretch(0, 0) - self.mainLayout.setColumnStretch(1, 1) + self.mainLayout.setColumnStretch(0, 1) + self.mainLayout.setColumnStretch(1, 10) # Resize window to final resolution, and display. logging.info("Starting GUI.") From e4d3c34a94257e23d64eea99f0d4f403ef0063b0 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 11:02:59 -0600 Subject: [PATCH 18/32] Limit to 1000 entries in log box --- horusgui/gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/horusgui/gui.py b/horusgui/gui.py index 7c5dcd6..230ab32 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -735,6 +735,7 @@ class MainWindow(QMainWindow): w6 = QGridLayout(w6_groupbox) self.widgets["console"] = QPlainTextEdit() self.widgets["console"].setReadOnly(True) + self.widgets["console"].setMaximumBlockCount(1000) w6.addWidget(self.widgets["console"]) w6.setRowStretch(0, 1) From fa7fa86bc0c719c209142f358f1b604d3611e779 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 12:35:02 -0600 Subject: [PATCH 19/32] Added proper signaling for new packet handler --- horusgui/gui.py | 63 +++++++++++++------------------------------------ 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 230ab32..8bb6d6f 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -122,7 +122,6 @@ class MainWindow(QMainWindow): self.DEFAULT_ESTIMATOR_MIN = 100 self.DEFAULT_ESTIMATOR_MAX = 4000 - # Global widget store self.widgets = {} @@ -143,10 +142,13 @@ class MainWindow(QMainWindow): self.rotator_current_az = 0.0 self.rotator_current_el = 0.0 - # Global running indicator self.running = False + # Decoded packet signal + self.new_packet_signal = WorkerSignals() + self.new_packet_signal.info.connect(self.handle_new_packet) + self.initialize() def initialize(self): @@ -471,7 +473,7 @@ class MainWindow(QMainWindow): w1_rotator.addWidget(self.widgets["rotatorCurrentStatusValue"], 7, 1, 1, 1) w1_rotator.addWidget(self.widgets["rotatorCurrentPositionLabel"], 8, 0, 1, 1) w1_rotator.addWidget(self.widgets["rotatorCurrentPositionValue"], 8, 1, 1, 1) - w1_rotator.setRowStretch(7, 1) + w1_rotator.setRowStretch(9, 1) w1_rotator_groupbox.setLayout(w1_rotator) @@ -484,13 +486,6 @@ class MainWindow(QMainWindow): w1_tab_widget.setStyleSheet("QTabBar {font: bold 14px;}") # Add widgets to left column - # w1_audio_groupbox.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) - # w1_modem_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) - # w1_tab_widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) - # w1_habitat_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) - # w1_other_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) - # w1_rotator_groupbox.setSizePolicy(QSizePolicy.Policy.MinimumExpanding) - left_column.addWidget(w1_audio_groupbox) #, 0, 0, 1, 1) left_column.addWidget(w1_modem_groupbox) #, 1, 0, 1, 1) left_column.addWidget(w1_tab_widget) #, 2, 0, 1, 1) @@ -693,40 +688,15 @@ class MainWindow(QMainWindow): self.widgets["latestTelem3Label"] = QLabel("External Pressure") self.widgets["latestTelem3Value"] = QLabel("---") self.widgets["latestTelem3Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) - self.widgets["latestTelem4Label"] = QLabel() - self.widgets["latestTelem4Value"] = QLabel() - self.widgets["latestTelem4Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) - self.widgets["latestTelem5Label"] = QLabel() - self.widgets["latestTelem5Value"] = QLabel() - self.widgets["latestTelem5Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) - self.widgets["latestTelem6Label"] = QLabel() - self.widgets["latestTelem6Value"] = QLabel() - self.widgets["latestTelem6Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) - self.widgets["latestTelem7Label"] = QLabel() - self.widgets["latestTelem7Value"] = QLabel() - self.widgets["latestTelem7Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) - self.widgets["latestTelem8Label"] = QLabel() - self.widgets["latestTelem8Value"] = QLabel() - self.widgets["latestTelem8Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + for i in range(4,9): + self.widgets[f"latestTelem{i}Label"] = QLabel("") + self.widgets[f"latestTelem{i}Value"] = QLabel("") + self.widgets[f"latestTelem{i}Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + + for i in range(0,9): + self.w5_telemetry.addWidget(self.widgets[f"latestTelem{i}Label"], 0, i, 1, 1) + self.w5_telemetry.addWidget(self.widgets[f"latestTelem{i}Value"], 1, i, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem0Label"], 0, 0, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem0Value"], 1, 0, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem1Label"], 0, 1, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem1Value"], 1, 1, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem2Label"], 0, 2, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem2Value"], 1, 2, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem3Label"], 0, 3, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem3Value"], 1, 3, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem4Label"], 0, 4, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem4Value"], 1, 4, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem5Label"], 0, 5, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem5Value"], 1, 5, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem6Label"], 0, 6, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem6Value"], 1, 6, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem7Label"], 0, 7, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem7Value"], 1, 7, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem8Label"], 0, 8, 1, 1) - self.w5_telemetry.addWidget(self.widgets["latestTelem8Value"], 1, 8, 1, 1) #self.w5_telemetry.setRowStretch(1, 6) w6_groupbox = QGroupBox("Log") @@ -1095,6 +1065,8 @@ class MainWindow(QMainWindow): else: return np.max(self.widgets["snrPlotSNR"]) + def handle_new_packet_emit(self, frame): + self.new_packet_signal.info.emit(frame) def handle_new_packet(self, frame): """ Handle receipt of a newly decoded packet """ @@ -1108,15 +1080,12 @@ class MainWindow(QMainWindow): # RTTY packets are provided as a string, and can be displayed directly _packet = frame.data - - _decoded = None # Grab SNR. _snr = self.get_latest_snr() #logging.info(f"Packet SNR: {_snr:.2f}") - # Grab other metadata out of the GUI _radio_dial = None @@ -1363,7 +1332,7 @@ class MainWindow(QMainWindow): mode=_modem_id, rate=_modem_rate, tone_spacing=_modem_tone_spacing, - callback=self.handle_new_packet, + callback=self.handle_new_packet_emit, sample_rate=_sample_rate ) From 5e2a50c52b799a48b7d159ba94cb11550e130511 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 15:20:15 -0600 Subject: [PATCH 20/32] Revert all builds to Python 3.11 --- .github/workflows/pull-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 8d3adc9..414e2fb 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -88,7 +88,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.13' + python-version: '3.11' cache: 'pip' # caching pip dependencies - name: Install Homebrew dependencies @@ -148,7 +148,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.13' + python-version: '3.11' cache: 'pip' # caching pip dependencies - name: Install Homebrew dependencies From 549aa3ba3a64cbcca27e4ea38229573398623627 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 17:22:52 -0600 Subject: [PATCH 21/32] Fixed width on settings column, smoothing on FFT --- horusgui/gui.py | 52 ++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 8bb6d6f..350f1e1 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -15,7 +15,7 @@ if sys.version_info < (3, 0): import argparse import datetime -import glob +# import glob import logging import platform import time @@ -25,8 +25,6 @@ from PyQt6.QtWidgets import * from PyQt6.QtGui import * from PyQt6.QtCore import * from pyqtgraph.dockarea import * -# import qdarktheme -from threading import Thread from .widgets import * from .audio import * @@ -173,7 +171,8 @@ class MainWindow(QMainWindow): # Audio Parameters self.widgets["audioDeviceLabel"] = QLabel("Audio Device:") self.widgets["audioDeviceSelector"] = QComboBox() - self.widgets["audioDeviceSelector"].setFixedWidth(300) # Dirty, but it needed to be done + # self.widgets["audioDeviceSelector"].setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) + self.widgets["audioDeviceSelector"].setFixedWidth(225) # Dirty, but it needed to be done self.widgets["audioDeviceSelector"].currentIndexChanged.connect(self.update_audio_sample_rates) self.widgets["audioSampleRateLabel"] = QLabel("Sample Rate (Hz):") @@ -249,11 +248,11 @@ class MainWindow(QMainWindow): w1_modem_groupbox.setLayout(w1_modem) - w1_habitat_groupbox = QGroupBox('SondeHub') - w1_habitat = QGridLayout(w1_habitat_groupbox) + w1_habitat_widget = QWidget() + w1_habitat = QGridLayout(w1_habitat_widget) # Listener Information self.widgets["habitatHeading"] = QLabel("SondeHub Settings") - self.widgets["sondehubUploadLabel"] = QLabel("Enable SondeHub-Ham Upload:") + self.widgets["sondehubUploadLabel"] = QLabel("Enable SondeHub Upload:") self.widgets["sondehubUploadSelector"] = QCheckBox() self.widgets["sondehubUploadSelector"].setChecked(True) self.widgets["sondehubUploadSelector"].clicked.connect(self.habitat_inhibit) @@ -324,10 +323,10 @@ class MainWindow(QMainWindow): w1_habitat.addWidget(self.widgets["sondehubPositionNotesLabel"], 8, 0, 1, 3) w1_habitat.setRowStretch(9, 1) w1_habitat.addWidget(self.widgets["saveSettingsButton"], 10, 0, 1, 3) - w1_habitat_groupbox.setLayout(w1_habitat) + w1_habitat_widget.setLayout(w1_habitat) - w1_other_groupbox = QGroupBox("Other") - w1_other = QGridLayout(w1_other_groupbox) + w1_other_widget = QWidget() + w1_other = QGridLayout(w1_other_widget) self.widgets["horusHeaderLabel"] = QLabel("Telemetry Forwarding") self.widgets["horusUploadLabel"] = QLabel("Enable Horus UDP Output:") self.widgets["horusUploadSelector"] = QCheckBox() @@ -405,11 +404,11 @@ class MainWindow(QMainWindow): w1_other.addWidget(self.widgets["inhibitCRCLabel"], 11, 0, 1, 1) w1_other.addWidget(self.widgets["inhibitCRCSelector"], 11, 1, 1, 1) w1_other.setRowStretch(12, 1) - w1_other_groupbox.setLayout(w1_other) + w1_other_widget.setLayout(w1_other) - w1_rotator_groupbox = QGroupBox("Rotator") - w1_rotator = QGridLayout(w1_rotator_groupbox) + w1_rotator_widget = QWidget() + w1_rotator = QGridLayout(w1_rotator_widget) self.widgets["rotatorHeaderLabel"] = QLabel("Rotator Control") self.widgets["rotatorTypeLabel"] = QLabel("Rotator Type:") @@ -475,14 +474,14 @@ class MainWindow(QMainWindow): w1_rotator.addWidget(self.widgets["rotatorCurrentPositionValue"], 8, 1, 1, 1) w1_rotator.setRowStretch(9, 1) - w1_rotator_groupbox.setLayout(w1_rotator) + w1_rotator_widget.setLayout(w1_rotator) w1_tab_widget = QTabWidget() w1_tab_widget.setTabPosition(QTabWidget.TabPosition.North) w1_tab_widget.tabBar().setExpanding(True) - w1_tab_widget.addTab(w1_habitat_groupbox, "SondeHub") - w1_tab_widget.addTab(w1_other_groupbox, "Other") - w1_tab_widget.addTab(w1_rotator_groupbox, "Rotator") + w1_tab_widget.addTab(w1_habitat_widget, "SondeHub") + w1_tab_widget.addTab(w1_other_widget, "Other") + w1_tab_widget.addTab(w1_rotator_widget, "Rotator") w1_tab_widget.setStyleSheet("QTabBar {font: bold 14px;}") # Add widgets to left column @@ -497,31 +496,31 @@ class MainWindow(QMainWindow): self.widgets["spectrumPlot"] = pg.PlotWidget(title="Spectra") self.widgets["spectrumPlot"].setLabel("left", "Power (dB)") self.widgets["spectrumPlot"].setLabel("bottom", "Frequency (Hz)") - self.widgets["spectrumPlotData"] = self.widgets["spectrumPlot"].plot([0]) + self.widgets["spectrumPlotData"] = self.widgets["spectrumPlot"].plot([0], pen=pg.mkPen(width=2)) # Frequency Estiator Outputs self.widgets["estimatorLines"] = [ pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="w", width=3, style=QtCore.Qt.PenStyle.DashLine), label="F1", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="w", width=3, style=QtCore.Qt.PenStyle.DashLine), label="F2", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="w", width=3, style=QtCore.Qt.PenStyle.DashLine), label="F3", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="w", width=3, style=QtCore.Qt.PenStyle.DashLine), label="F4", labelOpts={'position':0.9} ), @@ -584,7 +583,7 @@ class MainWindow(QMainWindow): self.widgets["snrPlotRange"] = [-10, 30] self.widgets["snrPlotTime"] = np.array([]) self.widgets["snrPlotSNR"] = np.array([]) - self.widgets["snrPlotData"] = self.widgets["snrPlot"].plot(self.widgets["snrPlotTime"], self.widgets["snrPlotSNR"]) + self.widgets["snrPlotData"] = self.widgets["snrPlot"].plot(self.widgets["snrPlotTime"], self.widgets["snrPlotSNR"], pen=pg.mkPen(width=2)) w3_snr.addWidget(self.widgets["snrPlot"]) w3_snr_groupbox.setLayout(w3_snr) @@ -721,7 +720,7 @@ class MainWindow(QMainWindow): right_column.setColumnStretch(0, 10) right_column.setColumnStretch(1, 6) - right_column.setColumnStretch(2, 1) + right_column.setColumnStretch(2, 1) # Grid: (Row, Column, RowSpan, ColumnSpan) self.mainLayout.addLayout(left_column, 0, 0, 1, 1) @@ -971,7 +970,9 @@ class MainWindow(QMainWindow): _data = data["fft"] _dbfs = data["dbfs"] - self.widgets["spectrumPlotData"].setData(_scale, _data) + _tc = 0.25 + _plot_data = (self.widgets["spectrumPlotData"].getData()[1] * (1 - _tc) + (_data * _tc)) + self.widgets["spectrumPlotData"].setData(_scale, _plot_data) # Really basic IIR to smoothly adjust scale _old_max = self.widgets["spectrumPlotRange"][1] @@ -1547,7 +1548,6 @@ class ConsoleHandler(logging.Handler): # Main def main(): app = QApplication(sys.argv) - # app.setStyleSheet(qdarktheme.load_stylesheet()) window = MainWindow() app.aboutToQuit.connect(window.cleanup) window.show() From d0869733ca080cd379e809c104bee6d765d21164 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 19:50:28 -0600 Subject: [PATCH 22/32] Add ldconfig to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 27dbe88..3997966 100755 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ $ cd horusdemodlib && mkdir build && cd build $ cmake .. $ make $ sudo make install +$ sudo ldconfig ``` ### Grab this Repo From 9cb9542a3b887f693c1df99dc6a8c0542322e08c Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 19:52:38 -0600 Subject: [PATCH 23/32] Spectrum plot adjustments --- horusgui/gui.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 350f1e1..9d332d4 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -68,6 +68,8 @@ if 'Windows' in platform.system(): else: POSITION_LABEL_FONT_SIZE = 16 +PEN_WIDTH=1 + # Establish signals and worker for multi-threaded use class WorkerSignals(QObject): # finished = pyqtSignal() @@ -172,7 +174,7 @@ class MainWindow(QMainWindow): self.widgets["audioDeviceLabel"] = QLabel("Audio Device:") self.widgets["audioDeviceSelector"] = QComboBox() # self.widgets["audioDeviceSelector"].setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) - self.widgets["audioDeviceSelector"].setFixedWidth(225) # Dirty, but it needed to be done + self.widgets["audioDeviceSelector"].setFixedWidth(275) # Dirty, but it needed to be done self.widgets["audioDeviceSelector"].currentIndexChanged.connect(self.update_audio_sample_rates) self.widgets["audioSampleRateLabel"] = QLabel("Sample Rate (Hz):") @@ -182,12 +184,12 @@ class MainWindow(QMainWindow): self.widgets["audioDbfsValue"] = QLabel("--") self.widgets["audioDbfsValue_float"] = 0.0 - w1_audio.addWidget(self.widgets["audioDeviceLabel"], 0, 0, 1, 1) - w1_audio.addWidget(self.widgets["audioDeviceSelector"], 0, 1, 1, 2) - w1_audio.addWidget(self.widgets["audioSampleRateLabel"], 1, 0, 1, 1) - w1_audio.addWidget(self.widgets["audioSampleRateSelector"], 1, 1, 1, 2) - w1_audio.addWidget(self.widgets["audioDbfsLabel"], 2, 0, 1, 1) - w1_audio.addWidget(self.widgets["audioDbfsValue"], 2, 1, 1, 2) + w1_audio.addWidget(self.widgets["audioDeviceLabel"], 0, 0, 1, 3) + w1_audio.addWidget(self.widgets["audioDeviceSelector"], 1, 0, 1, 3) + w1_audio.addWidget(self.widgets["audioSampleRateLabel"], 2, 0, 1, 1) + w1_audio.addWidget(self.widgets["audioSampleRateSelector"], 2, 1, 1, 2) + w1_audio.addWidget(self.widgets["audioDbfsLabel"], 3, 0, 1, 1) + w1_audio.addWidget(self.widgets["audioDbfsValue"], 3, 1, 1, 2) w1_audio_groupbox.setLayout(w1_audio) # Modem Parameters @@ -496,31 +498,31 @@ class MainWindow(QMainWindow): self.widgets["spectrumPlot"] = pg.PlotWidget(title="Spectra") self.widgets["spectrumPlot"].setLabel("left", "Power (dB)") self.widgets["spectrumPlot"].setLabel("bottom", "Frequency (Hz)") - self.widgets["spectrumPlotData"] = self.widgets["spectrumPlot"].plot([0], pen=pg.mkPen(width=2)) + self.widgets["spectrumPlotData"] = self.widgets["spectrumPlot"].plot([0], pen=pg.mkPen(width=PEN_WIDTH)) # Frequency Estiator Outputs self.widgets["estimatorLines"] = [ pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=3, style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="w", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), label="F1", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=3, style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="w", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), label="F2", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=3, style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="w", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), label="F3", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=3, style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="w", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), label="F4", labelOpts={'position':0.9} ), @@ -583,7 +585,7 @@ class MainWindow(QMainWindow): self.widgets["snrPlotRange"] = [-10, 30] self.widgets["snrPlotTime"] = np.array([]) self.widgets["snrPlotSNR"] = np.array([]) - self.widgets["snrPlotData"] = self.widgets["snrPlot"].plot(self.widgets["snrPlotTime"], self.widgets["snrPlotSNR"], pen=pg.mkPen(width=2)) + self.widgets["snrPlotData"] = self.widgets["snrPlot"].plot(self.widgets["snrPlotTime"], self.widgets["snrPlotSNR"], pen=pg.mkPen(width=PEN_WIDTH)) w3_snr.addWidget(self.widgets["snrPlot"]) w3_snr_groupbox.setLayout(w3_snr) @@ -970,9 +972,11 @@ class MainWindow(QMainWindow): _data = data["fft"] _dbfs = data["dbfs"] - _tc = 0.25 - _plot_data = (self.widgets["spectrumPlotData"].getData()[1] * (1 - _tc) + (_data * _tc)) - self.widgets["spectrumPlotData"].setData(_scale, _plot_data) + # _tc = 0.25 + # _plot_data = (self.widgets["spectrumPlotData"].getData()[1] * (1 - _tc) + (_data * _tc)) + # self.widgets["spectrumPlotData"].setData(_scale, _plot_data) + + self.widgets["spectrumPlotData"].setData(_scale, _data) # Really basic IIR to smoothly adjust scale _old_max = self.widgets["spectrumPlotRange"][1] From f613976e6ccb35d34756954747426c57b3727268 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 20:17:33 -0600 Subject: [PATCH 24/32] Testing QSplitter to allow hiding of config column --- horusgui/gui.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 9d332d4..9638827 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -156,11 +156,6 @@ class MainWindow(QMainWindow): self.setWindowTitle(f"Horus Telemetry GUI - v{__version__}") self.setWindowIcon(getHorusIcon()) - self.mainWidget = QWidget() - self.setCentralWidget(self.mainWidget) - self.mainLayout = QGridLayout() - self.mainWidget.setLayout(self.mainLayout) - # Left Column VBox left_column = QVBoxLayout() @@ -174,7 +169,7 @@ class MainWindow(QMainWindow): self.widgets["audioDeviceLabel"] = QLabel("Audio Device:") self.widgets["audioDeviceSelector"] = QComboBox() # self.widgets["audioDeviceSelector"].setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) - self.widgets["audioDeviceSelector"].setFixedWidth(275) # Dirty, but it needed to be done + # self.widgets["audioDeviceSelector"].setFixedWidth(275) # Dirty, but it needed to be done self.widgets["audioDeviceSelector"].currentIndexChanged.connect(self.update_audio_sample_rates) self.widgets["audioSampleRateLabel"] = QLabel("Sample Rate (Hz):") @@ -724,12 +719,32 @@ class MainWindow(QMainWindow): right_column.setColumnStretch(1, 6) right_column.setColumnStretch(2, 1) - # Grid: (Row, Column, RowSpan, ColumnSpan) - self.mainLayout.addLayout(left_column, 0, 0, 1, 1) - self.mainLayout.addLayout(right_column, 0, 1, 1, 1) + left_column_widget = QWidget() + left_column_widget.setLayout(left_column) - self.mainLayout.setColumnStretch(0, 1) - self.mainLayout.setColumnStretch(1, 10) + right_column_widget = QWidget() + right_column_widget.setLayout(right_column) + + splitter = QSplitter(Qt.Orientation.Horizontal) + splitter.addWidget(left_column_widget) + splitter.addWidget(right_column_widget) + + self.mainWidget = QWidget() + self.setCentralWidget(self.mainWidget) + self.mainLayout = QHBoxLayout() + self.mainWidget.setLayout(self.mainLayout) + + self.mainLayout.addWidget(splitter) + + # self.mainLayout = QGridLayout() + # self.mainWidget.setLayout(self.mainLayout)? + + # # Grid: (Row, Column, RowSpan, ColumnSpan) + # self.mainLayout.addLayout(left_column, 0, 0, 1, 1) + # self.mainLayout.addLayout(right_column, 0, 1, 1, 1) + + # self.mainLayout.setColumnStretch(0, 1) + # self.mainLayout.setColumnStretch(1, 10) # Resize window to final resolution, and display. logging.info("Starting GUI.") From 0f2cac724cf0ac8be2ba270ddbb495dece9423b6 Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 20:40:30 -0600 Subject: [PATCH 25/32] Added FFT smoothing as an option --- horusgui/config.py | 4 ++++ horusgui/gui.py | 29 +++++++++++++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/horusgui/config.py b/horusgui/config.py index 7fb987d..49bb497 100644 --- a/horusgui/config.py +++ b/horusgui/config.py @@ -38,6 +38,7 @@ default_config = { "logging_enabled": False, "log_format": "CSV", "log_directory": "", + "fft_smoothing": False, "payload_list": json.dumps(horusdemodlib.payloads.HORUS_PAYLOAD_LIST), "custom_field_list": json.dumps({}) } @@ -132,6 +133,8 @@ def read_config(widgets): widgets["loggingFormatSelector"].setCurrentText(default_config["log_format"]) widgets["enableLoggingSelector"].setChecked(ValueToBool(default_config["logging_enabled"])) + widgets["fftSmoothingSelector"].setChecked(ValueToBool(default_config["fft_smoothing"])) + if default_config['baud_rate'] != -1: widgets["horusModemRateSelector"].setCurrentText(str(default_config['baud_rate'])) @@ -179,6 +182,7 @@ def save_config(widgets): default_config["logging_enabled"] = widgets["enableLoggingSelector"].isChecked() default_config["log_directory"] = widgets["loggingPathEntry"].text() default_config["log_format"] = widgets["loggingFormatSelector"].currentText() + default_config["fft_smoothing"] = widgets["fftSmoothingSelector"].isChecked() default_config["payload_list"] = json.dumps(horusdemodlib.payloads.HORUS_PAYLOAD_LIST) default_config["custom_field_list"] = json.dumps(horusdemodlib.payloads.HORUS_CUSTOM_FIELDS) diff --git a/horusgui/gui.py b/horusgui/gui.py index 9638827..16eec06 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -379,6 +379,12 @@ class MainWindow(QMainWindow): self.widgets["inhibitCRCSelector"].setToolTip( "Hide CRC Failed error messages." ) + self.widgets["fftSmoothingLabel"] = QLabel("Enable FFT smoothing:") + self.widgets["fftSmoothingSelector"] = QCheckBox() + self.widgets["fftSmoothingSelector"].setChecked(False) + self.widgets["fftSmoothingSelector"].setToolTip( + "Enable IIR filter on FFT with tc=0.25." + ) w1_other.addWidget(self.widgets["horusHeaderLabel"], 0, 0, 1, 2) w1_other.addWidget(self.widgets["horusUploadLabel"], 1, 0, 1, 1) @@ -400,7 +406,9 @@ class MainWindow(QMainWindow): w1_other.addWidget(self.widgets["otherHeaderLabel"], 10, 0, 1, 2) w1_other.addWidget(self.widgets["inhibitCRCLabel"], 11, 0, 1, 1) w1_other.addWidget(self.widgets["inhibitCRCSelector"], 11, 1, 1, 1) - w1_other.setRowStretch(12, 1) + w1_other.addWidget(self.widgets["fftSmoothingLabel"], 12, 0, 1, 1) + w1_other.addWidget(self.widgets["fftSmoothingSelector"], 12, 1, 1, 1) + w1_other.setRowStretch(13, 1) w1_other_widget.setLayout(w1_other) @@ -499,25 +507,25 @@ class MainWindow(QMainWindow): self.widgets["estimatorLines"] = [ pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="grey", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), label="F1", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="grey", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), label="F2", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="grey", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), label="F3", labelOpts={'position':0.9} ), pg.InfiniteLine( pos=-1000, - pen=pg.mkPen(color="w", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), + pen=pg.mkPen(color="grey", width=(PEN_WIDTH + 1), style=QtCore.Qt.PenStyle.DashLine), label="F4", labelOpts={'position':0.9} ), @@ -987,11 +995,12 @@ class MainWindow(QMainWindow): _data = data["fft"] _dbfs = data["dbfs"] - # _tc = 0.25 - # _plot_data = (self.widgets["spectrumPlotData"].getData()[1] * (1 - _tc) + (_data * _tc)) - # self.widgets["spectrumPlotData"].setData(_scale, _plot_data) - - self.widgets["spectrumPlotData"].setData(_scale, _data) + if self.widgets["fftSmoothingSelector"].isChecked(): + _tc = 0.25 + _plot_data = (self.widgets["spectrumPlotData"].getData()[1] * (1 - _tc) + (_data * _tc)) + self.widgets["spectrumPlotData"].setData(_scale, _plot_data) + else: + self.widgets["spectrumPlotData"].setData(_scale, _data) # Really basic IIR to smoothly adjust scale _old_max = self.widgets["spectrumPlotRange"][1] From 8feb9378997b7d2d2bfbbcb3303614e2cdb3fead Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 21:07:48 -0600 Subject: [PATCH 26/32] Added default telem fields to GUI (batt, satellites, temperature) --- horusgui/gui.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 16eec06..5867ba9 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -680,7 +680,24 @@ class MainWindow(QMainWindow): w5_telemetry_groupbox.setLayout(self.w5_telemetry) # These are placeholders and will be updated when telemetry is received. - self.widgets["latestTelem0Label"] = QLabel("Battery Voltage") + self.widgets["latestTelemBattVoltageLabel"] = QLabel("Batt Voltage") + self.widgets["latestTelemBattVoltageValue"] = QLabel("---") + self.widgets["latestTelemBattVoltageValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelemSatellitesLabel"] = QLabel("Satellites") + self.widgets["latestTelemSatellitesValue"] = QLabel("---") + self.widgets["latestTelemSatellitesValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + self.widgets["latestTelemTemperatureLabel"] = QLabel("Temperature") + self.widgets["latestTelemTemperatureValue"] = QLabel("---") + self.widgets["latestTelemTemperatureValue"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) + + self.w5_telemetry.addWidget(self.widgets[f"latestTelemBattVoltageLabel"], 0, 0, 1, 1) + self.w5_telemetry.addWidget(self.widgets[f"latestTelemBattVoltageValue"], 1, 0, 1, 1) + self.w5_telemetry.addWidget(self.widgets[f"latestTelemSatellitesLabel"], 0, 1, 1, 1) + self.w5_telemetry.addWidget(self.widgets[f"latestTelemSatellitesValue"], 1, 1, 1, 1) + self.w5_telemetry.addWidget(self.widgets[f"latestTelemTemperatureLabel"], 0, 2, 1, 1) + self.w5_telemetry.addWidget(self.widgets[f"latestTelemTemperatureValue"], 1, 2, 1, 1) + + self.widgets["latestTelem0Label"] = QLabel("Ascent Rate") self.widgets["latestTelem0Value"] = QLabel("---") self.widgets["latestTelem0Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) self.widgets["latestTelem1Label"] = QLabel("External Temperature") @@ -698,8 +715,8 @@ class MainWindow(QMainWindow): self.widgets[f"latestTelem{i}Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) for i in range(0,9): - self.w5_telemetry.addWidget(self.widgets[f"latestTelem{i}Label"], 0, i, 1, 1) - self.w5_telemetry.addWidget(self.widgets[f"latestTelem{i}Value"], 1, i, 1, 1) + self.w5_telemetry.addWidget(self.widgets[f"latestTelem{i}Label"], 0, i+3, 1, 1) + self.w5_telemetry.addWidget(self.widgets[f"latestTelem{i}Value"], 1, i+3, 1, 1) #self.w5_telemetry.setRowStretch(1, 6) @@ -1199,6 +1216,13 @@ class MainWindow(QMainWindow): self.widgets["latestPacketAltitudeValue"].setText(f"{_decoded['altitude']}") # Update telemetry fields + if 'batteryvoltage' in _decoded: + self.widgets["latestTelemBattVoltageValue"].setText(f"{_decoded['batteryvoltage']:.2f}") + if 'satellites' in _decoded: + self.widgets["latestTelemSatellitesValue"].setText(f"{_decoded['satellites']}") + if 'temperature' in _decoded: + self.widgets["latestTelemTemperatureValue"].setText(f"{_decoded['temperature']:.1f}") + if len(_decoded['custom_field_names']) > 0: column = 0 for field in _decoded['custom_field_names']: From fab463334b93f09c25dde9c0f8bc6eab8b659b8d Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 21:25:46 -0600 Subject: [PATCH 27/32] Fix typo in battery_voltage --- horusgui/gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 5867ba9..3655de1 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -1216,8 +1216,8 @@ class MainWindow(QMainWindow): self.widgets["latestPacketAltitudeValue"].setText(f"{_decoded['altitude']}") # Update telemetry fields - if 'batteryvoltage' in _decoded: - self.widgets["latestTelemBattVoltageValue"].setText(f"{_decoded['batteryvoltage']:.2f}") + if 'battery_voltage' in _decoded: + self.widgets["latestTelemBattVoltageValue"].setText(f"{_decoded['battery_voltage']:.2f}") if 'satellites' in _decoded: self.widgets["latestTelemSatellitesValue"].setText(f"{_decoded['satellites']}") if 'temperature' in _decoded: From c1c76e9dc1c342f66025ad1cbdcce11fcc496e1b Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Sat, 25 Jan 2025 22:02:08 -0600 Subject: [PATCH 28/32] Improved Telemetry display --- horusgui/gui.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/horusgui/gui.py b/horusgui/gui.py index 3655de1..a62fee8 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -718,6 +718,12 @@ class MainWindow(QMainWindow): self.w5_telemetry.addWidget(self.widgets[f"latestTelem{i}Label"], 0, i+3, 1, 1) self.w5_telemetry.addWidget(self.widgets[f"latestTelem{i}Value"], 1, i+3, 1, 1) + for i in range(0,7): + self.w5_telemetry.setColumnStretch(i, 10) + + for i in range(7, 12): + self.w5_telemetry.setColumnStretch(i, 1) + #self.w5_telemetry.setRowStretch(1, 6) w6_groupbox = QGroupBox("Log") @@ -1218,16 +1224,29 @@ class MainWindow(QMainWindow): # Update telemetry fields if 'battery_voltage' in _decoded: self.widgets["latestTelemBattVoltageValue"].setText(f"{_decoded['battery_voltage']:.2f}") + else: + self.widgets["latestTelemBattVoltageValue"].setText("---") + if 'satellites' in _decoded: self.widgets["latestTelemSatellitesValue"].setText(f"{_decoded['satellites']}") + else: + self.widgets["latestTelemSatellitesValue"].setText("---") + if 'temperature' in _decoded: self.widgets["latestTelemTemperatureValue"].setText(f"{_decoded['temperature']:.1f}") + else: + self.widgets["latestTelemTemperatureValue"].setText("---") if len(_decoded['custom_field_names']) > 0: column = 0 for field in _decoded['custom_field_names']: - self.widgets[f"latestTelem{column}Label"].setText(f"{field}") + field_nice = field.replace('_', ' ').title() + self.widgets[f"latestTelem{column}Label"].setText(f"{field_nice}") self.widgets[f"latestTelem{column}Value"].setText(f"{_decoded[field]}") + self.widgets[f"latestTelem{column}Label"].show() + self.widgets[f"latestTelem{column}Value"].show() + + self.w5_telemetry.setColumnStretch((column + 3), 10) column += 1 @@ -1236,7 +1255,7 @@ class MainWindow(QMainWindow): for i in range(column, 9): self.widgets[f"latestTelem{column}Label"].hide() self.widgets[f"latestTelem{column}Value"].hide() - + self.w5_telemetry.setColumnStretch((i + 3), 1) # Attempt to update the range/elevation/bearing fields. try: From f261d801c0d95262f4db0bb3cceffa23a8455a4b Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Wed, 29 Jan 2025 22:07:39 -0600 Subject: [PATCH 29/32] Reset telemetry fields --- horusgui/gui.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/horusgui/gui.py b/horusgui/gui.py index a62fee8..7a2819b 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -712,6 +712,8 @@ class MainWindow(QMainWindow): for i in range(4,9): self.widgets[f"latestTelem{i}Label"] = QLabel("") self.widgets[f"latestTelem{i}Value"] = QLabel("") + self.widgets[f"latestTelem{i}Label"].hide() + self.widgets[f"latestTelem{i}Value"].hide() self.widgets[f"latestTelem{i}Value"].setFont(QFont("Courier New", POSITION_LABEL_FONT_SIZE, QFont.Weight.Bold)) for i in range(0,9): @@ -1373,6 +1375,13 @@ class MainWindow(QMainWindow): self.widgets["latestPacketBearingValue"].setText("---") self.widgets["latestPacketRangeValue"].setText("---") + self.widgets["latestTelemBattVoltageValue"].setText("---") + self.widgets["latestTelemSatellitesValue"].setText("---") + self.widgets["latestTelemTemperatureValue"].setText("---") + + for column in range(0,9): + self.widgets[f"latestTelem{column}Value"].setText(f"---") + # Ensure the SondeHub upload is set correctly. self.sondehub_uploader.inhibit = not self.widgets["sondehubUploadSelector"].isChecked() From 454892b8b9fa41c36596ae6aaf23501ad30b4a2e Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Wed, 29 Jan 2025 22:08:07 -0600 Subject: [PATCH 30/32] GitHub Actions change --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 414e2fb..8e99860 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -2,7 +2,7 @@ name: Build on: push: - branches: ["master", "pyqt6"] + branches: ["master"] pull_request: branches: ["master"] From 32137cf56e73faa4e485fe142bc701e30fab0e1a Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Wed, 29 Jan 2025 22:08:25 -0600 Subject: [PATCH 31/32] Version 0.4.0 --- horusgui/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horusgui/__init__.py b/horusgui/__init__.py index 151610c..6a9beea 100755 --- a/horusgui/__init__.py +++ b/horusgui/__init__.py @@ -1 +1 @@ -__version__ = "0.4.0-beta0" +__version__ = "0.4.0" From c41580274143069d04624991f9c3a0e3bba6875a Mon Sep 17 00:00:00 2001 From: Andrew Koenig Date: Wed, 29 Jan 2025 22:10:03 -0600 Subject: [PATCH 32/32] Added 0.3.19 to the ok-config list --- horusgui/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horusgui/config.py b/horusgui/config.py index 49bb497..7054be7 100644 --- a/horusgui/config.py +++ b/horusgui/config.py @@ -78,7 +78,7 @@ def read_config(widgets): global qt_settings, default_config # This is getting a bit ridiculous, need to re-think this approach. - OK_VERSIONS = [__version__, '0.3.18', '0.3.17', '0.3.16', '0.3.15', '0.3.14', '0.3.13', '0.3.12', '0.3.11', '0.3.10', '0.3.9', '0.3.8', '0.3.7', '0.3.6', '0.3.5', '0.3.4', '0.3.1', '0.2.1'] + OK_VERSIONS = [__version__,'0.3.19', '0.3.18', '0.3.17', '0.3.16', '0.3.15', '0.3.14', '0.3.13', '0.3.12', '0.3.11', '0.3.10', '0.3.9', '0.3.8', '0.3.7', '0.3.6', '0.3.5', '0.3.4', '0.3.1', '0.2.1'] # Try and read in the version parameter from QSettings if qt_settings.value("version") not in OK_VERSIONS: