diff --git a/horusgui/__init__.py b/horusgui/__init__.py index 74acd0e..fb69db9 100755 --- a/horusgui/__init__.py +++ b/horusgui/__init__.py @@ -1 +1 @@ -__version__ = "0.1.12" +__version__ = "0.1.14" diff --git a/horusgui/config.py b/horusgui/config.py index e35f813..3420cd8 100644 --- a/horusgui/config.py +++ b/horusgui/config.py @@ -29,6 +29,8 @@ default_config = { "habitat_radio": "", "horus_udp_enabled": True, "horus_udp_port": 55672, + "ozimux_enabled": False, + "ozimux_udp_port": 55683, "payload_list": json.dumps(horusdemodlib.payloads.HORUS_PAYLOAD_LIST), "custom_field_list": json.dumps({}) } @@ -87,6 +89,8 @@ def read_config(widgets): # Horus Settings widgets["horusUploadSelector"].setChecked(ValueToBool(default_config["horus_udp_enabled"])) widgets["horusUDPEntry"].setText(str(default_config["horus_udp_port"])) + widgets["ozimuxUploadSelector"].setChecked(ValueToBool(default_config["ozimux_enabled"])) + widgets["ozimuxUDPEntry"].setText(str(default_config["ozimux_udp_port"])) # Try and set the audio device. # If the audio device is not in the available list of devices, this will fail silently. @@ -133,6 +137,8 @@ def save_config(widgets): default_config["habitat_radio"] = widgets["userRadioEntry"].text() default_config["horus_udp_enabled"] = widgets["horusUploadSelector"].isChecked() default_config["horus_udp_port"] = int(widgets["horusUDPEntry"].text()) + default_config["ozimux_enabled"] = widgets["ozimuxUploadSelector"].isChecked() + default_config["ozimux_udp_port"] = int(widgets["ozimuxUDPEntry"].text()) default_config["audio_device"] = widgets["audioDeviceSelector"].currentText() default_config["audio_sample_rate"] = widgets["audioSampleRateSelector"].currentText() default_config["modem"] = widgets["horusModemSelector"].currentText() @@ -150,7 +156,7 @@ def init_payloads(): global default_config # Attempt to grab the payload list. - _payload_list = download_latest_payload_id_list() + _payload_list = download_latest_payload_id_list(timeout=3) if _payload_list: # Sanity check the result if 0 in _payload_list: @@ -175,7 +181,7 @@ def init_payloads(): logging.info(f"Payload List contains {len(list(horusdemodlib.payloads.HORUS_PAYLOAD_LIST.keys()))} entries.") - _custom_fields = download_latest_custom_field_list() + _custom_fields = download_latest_custom_field_list(timeout=3) if _custom_fields: # Sanity Check if 'HORUSTEST' in _custom_fields: diff --git a/horusgui/gui.py b/horusgui/gui.py index 6665b81..9ccce7c 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -16,6 +16,7 @@ if sys.version_info < (3, 0): import datetime import glob import logging +import platform import pyqtgraph as pg import numpy as np from queue import Queue @@ -35,7 +36,7 @@ from .icon import getHorusIcon from horusdemodlib.demod import HorusLib, Mode from horusdemodlib.decoder import decode_packet, parse_ukhas_string from horusdemodlib.payloads import * -from horusdemodlib.horusudp import send_payload_summary +from horusdemodlib.horusudp import send_payload_summary, send_ozimux_message from . import __version__ # Setup Logging @@ -125,6 +126,7 @@ d0.addWidget(w1_audio) w1_modem = pg.LayoutWidget() + # Modem Parameters widgets["horusModemLabel"] = QtGui.QLabel("Mode:") widgets["horusModemSelector"] = QtGui.QComboBox() @@ -134,11 +136,28 @@ widgets["horusModemRateSelector"] = QtGui.QComboBox() widgets["horusMaskEstimatorLabel"] = QtGui.QLabel("Enable Mask Estim.:") widgets["horusMaskEstimatorSelector"] = QtGui.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"] = QtGui.QLabel("Tone Spacing (Hz):") widgets["horusMaskSpacingEntry"] = QtGui.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"] = QtGui.QLabel("Manual Estim. Limits:") widgets["horusManualEstimatorSelector"] = QtGui.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"] = QtGui.QPushButton("Start") @@ -168,14 +187,30 @@ widgets["habitatUploadSelector"].setChecked(True) widgets["userCallLabel"] = QtGui.QLabel("Callsign:") widgets["userCallEntry"] = QtGui.QLineEdit("N0CALL") widgets["userCallEntry"].setMaxLength(20) +widgets["userCallEntry"].setToolTip( + "Your station callsign, which doesn't necessarily need to be an\n"\ + "amateur radio callsign." +) widgets["userLocationLabel"] = QtGui.QLabel("Lat/Lon:") widgets["userLatEntry"] = QtGui.QLineEdit("0.0") +widgets["userLatEntry"].setToolTip("Station Latitude in Decimal Degrees, e.g. -34.123456") widgets["userLonEntry"] = QtGui.QLineEdit("0.0") +widgets["userLonEntry"].setToolTip("Station Longitude in Decimal Degrees, e.g. 138.123456") widgets["userAntennaLabel"] = QtGui.QLabel("Antenna:") widgets["userAntennaEntry"] = QtGui.QLineEdit("") +widgets["userAntennaEntry"].setToolTip("A text description of your station's antenna.") widgets["userRadioLabel"] = QtGui.QLabel("Radio:") widgets["userRadioEntry"] = QtGui.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." +) widgets["habitatUploadPosition"] = QtGui.QPushButton("Upload Position") +widgets["habitatUploadPosition"].setToolTip( + "Manually re-upload your position information to HabHub.\n"\ + "Note that it can take a few minutes for your new information to\n"\ + "appear on the map." +) widgets["saveSettingsButton"] = QtGui.QPushButton("Save Settings") w1_habitat.addWidget(widgets["habitatUploadLabel"], 0, 0, 1, 1) @@ -199,14 +234,38 @@ w1_other = pg.LayoutWidget() widgets["horusUploadLabel"] = QtGui.QLabel("Enable Horus UDP Output:") widgets["horusUploadSelector"] = QtGui.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"] = QtGui.QLabel("Horus UDP Port:") widgets["horusUDPEntry"] = QtGui.QLineEdit("55672") widgets["horusUDPEntry"].setMaxLength(5) +widgets["horusUDPEntry"].setToolTip( + "UDP Port to output 'Horus UDP' JSON messages to." +) +widgets["ozimuxUploadLabel"] = QtGui.QLabel("Enable OziMux UDP Output:") +widgets["ozimuxUploadSelector"] = QtGui.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"] = QtGui.QLabel("Ozimux UDP Port:") +widgets["ozimuxUDPEntry"] = QtGui.QLineEdit("55683") +widgets["ozimuxUDPEntry"].setMaxLength(5) +widgets["ozimuxUDPEntry"].setToolTip( + "UDP Port to output 'OziMux' UDP messages to." +) w1_other.addWidget(widgets["horusUploadLabel"], 0, 0, 1, 1) w1_other.addWidget(widgets["horusUploadSelector"], 0, 1, 1, 1) w1_other.addWidget(widgets["horusUDPLabel"], 1, 0, 1, 1) w1_other.addWidget(widgets["horusUDPEntry"], 1, 1, 1, 1) +w1_other.addWidget(widgets["ozimuxUploadLabel"], 2, 0, 1, 1) +w1_other.addWidget(widgets["ozimuxUploadSelector"], 2, 1, 1, 1) +w1_other.addWidget(widgets["ozimuxUDPLabel"], 3, 0, 1, 1) +w1_other.addWidget(widgets["ozimuxUDPEntry"], 3, 1, 1, 1) w1_other.layout.setRowStretch(5, 1) d0_other.addWidget(w1_other) @@ -308,13 +367,9 @@ w4_data = pg.LayoutWidget() widgets["latestRawSentenceLabel"] = QtGui.QLabel("Latest Packet (Raw):") widgets["latestRawSentenceData"] = QtGui.QLineEdit("NO DATA") widgets["latestRawSentenceData"].setReadOnly(True) -#widgets["latestRawSentenceData"].setFont(QtGui.QFont("Courier New", 18, QtGui.QFont.Bold)) -#widgets["latestRawSentenceData"].setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) widgets["latestDecodedSentenceLabel"] = QtGui.QLabel("Latest Packet (Decoded):") widgets["latestDecodedSentenceData"] = QtGui.QLineEdit("NO DATA") widgets["latestDecodedSentenceData"].setReadOnly(True) -#widgets["latestDecodedSentenceData"].setFont(QtGui.QFont("Courier New", 18, QtGui.QFont.Bold)) -#widgets["latestDecodedSentenceData"].setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) 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) @@ -322,7 +377,12 @@ w4_data.addWidget(widgets["latestDecodedSentenceData"], 1, 1, 1, 6) d3_data.addWidget(w4_data) w4_position = pg.LayoutWidget() -POSITION_LABEL_FONT_SIZE = 16 +# 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"] = QtGui.QLabel("Callsign") widgets["latestPacketCallsignValue"] = QtGui.QLabel("---") widgets["latestPacketCallsignValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold)) @@ -641,6 +701,11 @@ def handle_new_packet(frame): _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) @@ -743,6 +808,14 @@ def start_decoding(): 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() @@ -768,6 +841,14 @@ def start_decoding(): 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) diff --git a/horusgui/modem.py b/horusgui/modem.py index b4686c1..0e8d6cf 100644 --- a/horusgui/modem.py +++ b/horusgui/modem.py @@ -7,7 +7,7 @@ from horusdemodlib.demod import Mode HORUS_MODEM_LIST = { "Horus Binary v1 (Legacy)": { "id": Mode.BINARY_V1, - "baud_rates": [25, 50, 100, 300], + "baud_rates": [50, 100, 300], # Note: 25 Baud removed until issues in underlying modem are fixed. "default_baud_rate": 100, "default_tone_spacing": 270, "use_mask_estimator": False, diff --git a/pyproject.toml b/pyproject.toml index 10ea770..0fbd5f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "horusgui" -version = "0.1.12" +version = "0.1.14" description = "" authors = ["Mark Jessop "] @@ -12,7 +12,7 @@ PyQt5 = "^5.13.0" pyqtgraph = "^0.11.0" pyaudio = "^0.2.11" "ruamel.yaml" = "^0.16.10" -horusdemodlib = "^0.1.1" +horusdemodlib = "^0.1.19" [tool.poetry.dev-dependencies] diff --git a/requirements.txt b/requirements.txt index 8de2b6f..26602ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ PyQt5 pyqtgraph ruamel.yaml requests -horusdemodlib \ No newline at end of file +horusdemodlib>=0.1.20 \ No newline at end of file