Porównaj commity

...

25 Commity

Autor SHA1 Wiadomość Data
Mark Jessop edb3ccfbd1 Fix re-upload button 2024-05-17 19:46:09 +09:30
Mark Jessop 0ef8cd6fb1
Merge pull request #39 from projecthorus/actions_tests
Github actions for OSX builds
2024-04-27 09:47:22 +09:30
Mark Jessop 655279e26b Remote OSX Intel build 2024-04-26 14:44:56 +09:30
Mark Jessop 38df0377c8 Add test intel build using macos-13 2024-04-26 14:32:07 +09:30
Mark Jessop 7e8316e45e Try making a DMG 2024-04-26 14:22:54 +09:30
Mark Jessop 8d4918b460 another try 2024-04-26 14:05:13 +09:30
Mark Jessop 5cd4fff720 Try using homebrew to get dependencies 2024-04-26 14:00:02 +09:30
Mark Jessop 24bdf69360 Test OSX workflow build 2024-04-26 13:52:13 +09:30
Mark Jessop 35d42cf99e Merge branch 'master' of github.com:projecthorus/horus-gui 2024-04-25 17:40:19 +09:30
Mark Jessop f343e2a6a1 Increase SNR max window for RTTY modems 2024-04-25 17:39:56 +09:30
Mark Jessop f1d86851c1
Merge pull request #37 from teamtoadprojects/github-actions
Add initial Windows Github Actions binary build
2024-04-06 18:47:04 +10:30
Tom Wardill 608f0897a3
Fix generated filename
This is a zip file, so it should have a zip extension
2024-04-04 09:47:17 +01:00
Tom Wardill e92264923b
Fix capitalisation
pyAudio != pyaudio
2024-04-04 09:42:20 +01:00
Tom Wardill b364e842f0
Use python 3.11
Install pyaudio directly from pip now.
2024-04-04 09:40:01 +01:00
Tom Wardill e8e0e210ad Add artifact upload 2024-04-03 16:18:23 +01:00
Tom Wardill c9a208e700 PyInstaller steps 2024-04-03 16:18:20 +01:00
Tom Wardill 1c38f331bd Install python dependencies 2024-04-03 16:18:17 +01:00
Tom Wardill fd1886caff Build windows binary using Github Actions 2024-04-03 16:18:13 +01:00
Mark Jessop 55188fcca1 Automatically add microphone entry to Info.plist 2024-04-01 14:01:13 +10:30
Mark Jessop b0bb51cf2a Revert back to PyQt5 2024-03-31 14:10:13 +10:30
Mark Jessop f5d9e87db8 Remove ruamel.yaml dependency 2024-03-31 12:56:37 +10:30
Mark Jessop 356870b7b7 Fix audio error on startup 2024-03-31 11:20:32 +10:30
Mark Jessop 0bb9088917 Move to using Qt6 2024-03-31 10:35:46 +10:30
Mark Jessop e9a7b31dfb Add warning on starting with a uploader callsign of N0CALL 2024-02-03 16:31:33 +10:30
Mark Jessop ab470cb7c5 Fix issues with overrunning console log buffer 2024-01-29 10:32:16 +10:30
11 zmienionych plików z 423 dodań i 147 usunięć

Wyświetl plik

@ -0,0 +1,183 @@
name: Build
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]
jobs:
build-windows:
runs-on: [windows-latest]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Checkout horusdemodlib
uses: actions/checkout@v4
with:
repository: "projecthorus/horusdemodlib"
ref: "master"
path: "horusdemodlib"
- name: Build horusdemodlib
run: |
cd horusdemodlib
mkdir build
cd build
cmake .. -G "MinGW Makefiles"
mingw32-make
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip' # caching pip dependencies
- name: Install pyAudio wheel
run: pip install pyaudio
- name: Install other dependencies
run: pip install -r requirements.txt
- name: Install pyinstaller
run: pip install pyinstaller
- name: Prep file locations
shell: bash
run: |
mkdir -p dist
cp horusdemodlib/build/src/libhorus.dll .
cp "C:\Program Files\Git\mingw64\bin\libgcc_s_seh-1.dll" .
cp "C:\Program Files\Git\mingw64\bin\libstdc++-6.dll" .
cp "C:\Program Files\Git\mingw64\bin\libwinpthread-1.dll" .
- name: Run pyinstaller
run: pyinstaller horus-gui_win.spec
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: horus-gui_WIN64.zip
path: dist/horus-gui.exe
retention-days: 2
build-osx:
runs-on: [macos-14]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Checkout horusdemodlib
uses: actions/checkout@v4
with:
repository: "projecthorus/horusdemodlib"
ref: "master"
path: "horusdemodlib"
- name: Build horusdemodlib
run: |
cd horusdemodlib
mkdir build
cd build
cmake ..
make
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip' # caching pip dependencies
- name: Install Homebrew dependencies
run: brew install portaudio
- name: Install pyAudio wheel
run: pip install pyaudio
- name: Install other dependencies
run: pip install -r requirements.txt
- name: Install pyinstaller
run: pip install pyinstaller
- name: Prep file locations
shell: bash
run: |
mkdir -p dist
cp horusdemodlib/build/src/libhorus.dylib .
- name: Run pyinstaller
run: pyinstaller horus-gui_osx_runner.spec
- name: Create the DMG file
run: hdiutil create -format UDZO -srcfolder dist/horus-gui.app dist/horus-gui_OSX-M1.dmg
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: horus-gui_OSX-M1.zip
path: dist/horus-gui_OSX-M1.dmg
retention-days: 2
# Currently having issues with portaudio and these builds...
# build-osx-intel:
# runs-on: [macos-13]
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
# - name: Checkout horusdemodlib
# uses: actions/checkout@v4
# with:
# repository: "projecthorus/horusdemodlib"
# ref: "master"
# path: "horusdemodlib"
# - name: Build horusdemodlib
# run: |
# cd horusdemodlib
# mkdir build
# cd build
# cmake ..
# make
# - uses: actions/setup-python@v5
# with:
# python-version: '3.11'
# cache: 'pip' # caching pip dependencies
# - name: Install Homebrew dependencies
# run: brew install portaudio
# - name: Install pyAudio wheel
# run: pip install pyaudio
# - name: Install other dependencies
# run: pip install -r requirements.txt
# - name: Install pyinstaller
# run: pip install pyinstaller
# - name: Prep file locations
# shell: bash
# run: |
# mkdir -p dist
# cp horusdemodlib/build/src/libhorus.dylib .
# - name: Run pyinstaller
# run: pyinstaller horus-gui_osx_runner.spec
# - name: Create the DMG file
# run: hdiutil create -format UDZO -srcfolder dist/horus-gui.app dist/horus-gui_OSX-Intel.dmg
# - name: Upload Artifact
# uses: actions/upload-artifact@v4
# with:
# name: horus-gui_OSX-Intel.zip
# path: dist/horus-gui_OSX-Intel.dmg
# retention-days: 2

Wyświetl plik

@ -66,8 +66,6 @@ $ cd horus-gui
### (Optional) Create a Virtual Environment
**Warning - Python 3.10 will not work until a known compatability issue with pyaudio has been fixed. Use Python 3.9.**
Create a virtual environment and install dependencies.
```console
$ python3 -m venv venv
@ -83,10 +81,28 @@ $ pip install -r requirements.txt
```
NOTE: Under linux based distros, you may also need to install `python3-distutils` and `python-setuptools`. If you get errors relating to pyaudio when trying to install into a venv, make sure that portaudio is installed (`libportaudio-dev` or `portaudio19-dev` under Linux distros, or `portaudio` under Macports), and then install pyaudio pointing to the portaudio lib by running:
On Linux:
```
(Linux) $ pip install --global-option='build_ext' --global-option='-I/usr/include' --global-option='-L/usr/lib' pyaudio
(OSX) $ pip install --global-option='build_ext' --global-option='-I/opt/local/include' --global-option='-L/opt/local/lib' pyaudio
$ export CFLAGS="-I/usr/include"
$ export LDFLAGS="-L/usr/lib"
(venv) $ pip install pyaudio
```
On OSX using Macports:
```
$ export CFLAGS="-I/opt/local/include"
$ export LDFLAGS="-L/opt/local/lib"
(venv) $ pip install pyaudio
```
On OSX using Homebrew
```
$ export CFLAGS="-I/opt/homebrew/include"
$ export LDFLAGS="-L/opt/homebrew/lib"
(venv) $ pip install pyaudio
```
You should then be able to re-run the install requirements command above.
### Install Package
@ -101,12 +117,12 @@ entry points so it can be used like a normal install.
### Run
```console
$ python -m horusgui.gui
$ (venv) python -m horusgui.gui
```
Or run the helper startup script:
```console
$ python horus-gui.py
$ (venv) python horus-gui.py
```
### Updating
@ -127,4 +143,4 @@ $ . venv/bin/activate (if using a venv)
$ pip install horusdemodlib --upgrade
```
You should then be OK to run horusgui. Configuration settings will be reset when the version number of horus-gui is incremented, until I settle on on a configuration parameter set.
You should then be OK to run horusgui.

Wyświetl plik

@ -4,7 +4,7 @@ block_cipher = None
a = Analysis(['horus-gui.py'],
pathex=['/Users/darkside/Dev/horus-gui'],
pathex=['.'],
binaries=[('../horusdemodlib/build/src/libhorus.dylib','.')],
datas=[],
hiddenimports=[],
@ -38,4 +38,8 @@ coll = COLLECT(exe,
app = BUNDLE(coll,
name='horus-gui.app',
icon='doc/horus_logo.icns',
bundle_identifier=None)
bundle_identifier=None,
info_plist={
'NSMicrophoneUsageDescription': 'Horus-GUI needs audio access to receive telemetry.'
},
)

Wyświetl plik

@ -0,0 +1,45 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['horus-gui.py'],
pathex=['.'],
binaries=[('libhorus.dylib','.')],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='horus-gui',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False , icon='doc/horus_logo.icns')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='horus-gui')
app = BUNDLE(coll,
name='horus-gui.app',
icon='doc/horus_logo.icns',
bundle_identifier=None,
info_plist={
'NSMicrophoneUsageDescription': 'Horus-GUI needs audio access to receive telemetry.'
},
)

Wyświetl plik

@ -1 +1 @@
__version__ = "0.3.14"
__version__ = "0.3.18"

Wyświetl plik

@ -48,6 +48,7 @@ def init_audio(widgets):
audioDevices[_name] = _dev
# Add to audio device selection list.
widgets["audioDeviceSelector"].addItem(_name)
logging.debug(f"Found audio device: {_name}")
# Select first item.
if len(list(audioDevices.keys())) > 0:
@ -75,6 +76,8 @@ def populate_sample_rates(widgets):
widgets["audioSampleRateSelector"].addItem(str(48000))
widgets["audioSampleRateSelector"].setCurrentIndex(0)
return
if _dev_name in audioDevices:
# Determine which sample rates from a common list are valid for this device.
_possible_rates = [8000.0, 22050.0, 44100.0, 48000.0, 96000.0]
@ -105,7 +108,7 @@ def populate_sample_rates(widgets):
_default_samp_rate = int(audioDevices[_dev_name]["defaultSampleRate"])
widgets["audioSampleRateSelector"].setCurrentText(str(_default_samp_rate))
else:
logging.error("Audio - Unknown Audio Device")
logging.error(f"Audio - Unknown Audio Device ({_dev_name})")
class AudioStream(object):

Wyświetl plik

@ -8,7 +8,6 @@ import json
import logging
import os
from pyqtgraph.Qt import QtCore
from ruamel.yaml import YAML
from . import __version__
from .modem import populate_modem_settings
from .audio import populate_sample_rates
@ -76,7 +75,8 @@ def read_config(widgets):
""" Read in configuration settings from Qt """
global qt_settings, default_config
OK_VERSIONS = [__version__, '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']
# This is getting a bit ridiculous, need to re-think this approach.
OK_VERSIONS = [__version__, '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:

Wyświetl plik

@ -22,7 +22,8 @@ import time
import pyqtgraph as pg
import numpy as np
from queue import Queue
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
#from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
from PyQt5 import QtWidgets, QtGui
from pyqtgraph.dockarea import *
from threading import Thread
@ -53,9 +54,9 @@ DEFAULT_ESTIMATOR_MAX = 4000
widgets = {}
# Queues for handling updates to image / status indications.
fft_update_queue = Queue(256)
status_update_queue = Queue(256)
log_update_queue = Queue(256)
fft_update_queue = Queue(1024)
status_update_queue = Queue(1024)
log_update_queue = Queue(2048)
# List of audio devices and their info
audio_devices = {}
@ -107,7 +108,7 @@ logging.basicConfig(
pg.mkQApp()
# GUI LAYOUT - Gtk Style!
win = QtGui.QMainWindow()
win = QtWidgets.QMainWindow()
area = DockArea()
win.setCentralWidget(area)
win.setWindowTitle(f"Horus Telemetry GUI - v{__version__}")
@ -143,14 +144,14 @@ d0_habitat.raiseDock()
# Controls
w1_audio = pg.LayoutWidget()
# TNC Connection
widgets["audioDeviceLabel"] = QtGui.QLabel("<b>Audio Device:</b>")
widgets["audioDeviceSelector"] = QtGui.QComboBox()
widgets["audioDeviceLabel"] = QtWidgets.QLabel("<b>Audio Device:</b>")
widgets["audioDeviceSelector"] = QtWidgets.QComboBox()
widgets["audioSampleRateLabel"] = QtGui.QLabel("<b>Sample Rate (Hz):</b>")
widgets["audioSampleRateSelector"] = QtGui.QComboBox()
widgets["audioSampleRateLabel"] = QtWidgets.QLabel("<b>Sample Rate (Hz):</b>")
widgets["audioSampleRateSelector"] = QtWidgets.QComboBox()
widgets["audioDbfsLabel"] = QtGui.QLabel("<b>Input Level (dBFS):</b>")
widgets["audioDbfsValue"] = QtGui.QLabel("--")
widgets["audioDbfsLabel"] = QtWidgets.QLabel("<b>Input Level (dBFS):</b>")
widgets["audioDbfsValue"] = QtWidgets.QLabel("--")
widgets["audioDbfsValue_float"] = 0.0
w1_audio.addWidget(widgets["audioDeviceLabel"], 0, 0, 1, 1)
@ -165,29 +166,29 @@ w1_modem = pg.LayoutWidget()
# Modem Parameters
widgets["horusModemLabel"] = QtGui.QLabel("<b>Mode:</b>")
widgets["horusModemSelector"] = QtGui.QComboBox()
widgets["horusModemLabel"] = QtWidgets.QLabel("<b>Mode:</b>")
widgets["horusModemSelector"] = QtWidgets.QComboBox()
widgets["horusModemRateLabel"] = QtGui.QLabel("<b>Baudrate:</b>")
widgets["horusModemRateSelector"] = QtGui.QComboBox()
widgets["horusModemRateLabel"] = QtWidgets.QLabel("<b>Baudrate:</b>")
widgets["horusModemRateSelector"] = QtWidgets.QComboBox()
widgets["horusMaskEstimatorLabel"] = QtGui.QLabel("<b>Enable Mask Estim.:</b>")
widgets["horusMaskEstimatorSelector"] = QtGui.QCheckBox()
widgets["horusMaskEstimatorLabel"] = QtWidgets.QLabel("<b>Enable Mask Estim.:</b>")
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"] = QtGui.QLabel("<b>Tone Spacing (Hz):</b>")
widgets["horusMaskSpacingEntry"] = QtGui.QLineEdit("270")
widgets["horusMaskSpacingLabel"] = QtWidgets.QLabel("<b>Tone Spacing (Hz):</b>")
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"] = QtGui.QLabel("<b>Manual Estim. Limits:</b>")
widgets["horusManualEstimatorSelector"] = QtGui.QCheckBox()
widgets["horusManualEstimatorLabel"] = QtWidgets.QLabel("<b>Manual Estim. Limits:</b>")
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"\
@ -197,7 +198,7 @@ widgets["horusManualEstimatorSelector"].setToolTip(
)
# Start/Stop
widgets["startDecodeButton"] = QtGui.QPushButton("Start")
widgets["startDecodeButton"] = QtWidgets.QPushButton("Start")
widgets["startDecodeButton"].setEnabled(False)
w1_modem.addWidget(widgets["horusModemLabel"], 0, 0, 1, 1)
@ -217,48 +218,48 @@ d0_modem.addWidget(w1_modem)
w1_habitat = pg.LayoutWidget()
# Listener Information
widgets["habitatHeading"] = QtGui.QLabel("<b>SondeHub Settings</b>")
widgets["sondehubUploadLabel"] = QtGui.QLabel("<b>Enable SondeHub-Ham Upload:</b>")
widgets["sondehubUploadSelector"] = QtGui.QCheckBox()
widgets["habitatHeading"] = QtWidgets.QLabel("<b>SondeHub Settings</b>")
widgets["sondehubUploadLabel"] = QtWidgets.QLabel("<b>Enable SondeHub-Ham Upload:</b>")
widgets["sondehubUploadSelector"] = QtWidgets.QCheckBox()
widgets["sondehubUploadSelector"].setChecked(True)
widgets["userCallLabel"] = QtGui.QLabel("<b>Callsign:</b>")
widgets["userCallEntry"] = QtGui.QLineEdit("N0CALL")
widgets["userCallLabel"] = QtWidgets.QLabel("<b>Callsign:</b>")
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."
"amateur radio callsign, just something unique!"
)
widgets["userLocationLabel"] = QtGui.QLabel("<b>Lat/Lon:</b>")
widgets["userLatEntry"] = QtGui.QLineEdit("0.0")
widgets["userLocationLabel"] = QtWidgets.QLabel("<b>Lat/Lon:</b>")
widgets["userLatEntry"] = QtWidgets.QLineEdit("0.0")
widgets["userLatEntry"].setToolTip("Station Latitude in Decimal Degrees, e.g. -34.123456")
widgets["userLonEntry"] = QtGui.QLineEdit("0.0")
widgets["userLonEntry"] = QtWidgets.QLineEdit("0.0")
widgets["userLonEntry"].setToolTip("Station Longitude in Decimal Degrees, e.g. 138.123456")
widgets["userAltitudeLabel"] = QtGui.QLabel("<b>Altitude:</b>")
widgets["userAltEntry"] = QtGui.QLineEdit("0.0")
widgets["userAltitudeLabel"] = QtWidgets.QLabel("<b>Altitude:</b>")
widgets["userAltEntry"] = QtWidgets.QLineEdit("0.0")
widgets["userAltEntry"].setToolTip("Station Altitude in Metres Above Sea Level.")
widgets["userAntennaLabel"] = QtGui.QLabel("<b>Antenna:</b>")
widgets["userAntennaEntry"] = QtGui.QLineEdit("")
widgets["userAntennaLabel"] = QtWidgets.QLabel("<b>Antenna:</b>")
widgets["userAntennaEntry"] = QtWidgets.QLineEdit("")
widgets["userAntennaEntry"].setToolTip("A text description of your station's antenna.")
widgets["userRadioLabel"] = QtGui.QLabel("<b>Radio:</b>")
widgets["userRadioEntry"] = QtGui.QLineEdit("Horus-GUI " + __version__)
widgets["userRadioLabel"] = QtWidgets.QLabel("<b>Radio:</b>")
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."
)
widgets["habitatUploadPosition"] = QtGui.QPushButton("Re-upload Position")
widgets["habitatUploadPosition"] = QtWidgets.QPushButton("Re-upload Position")
widgets["habitatUploadPosition"].setToolTip(
"Manually re-upload your position information to SondeHub-Amateur.\n"\
"Note that it can take a few minutes for your new information to\n"\
"appear on the map."
)
widgets["dialFreqLabel"] = QtGui.QLabel("<b>Radio Dial Freq (MHz):</b>")
widgets["dialFreqEntry"] = QtGui.QLineEdit("")
widgets["dialFreqLabel"] = QtWidgets.QLabel("<b>Radio Dial Freq (MHz):</b>")
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["saveSettingsButton"] = QtGui.QPushButton("Save Settings")
widgets["saveSettingsButton"] = QtWidgets.QPushButton("Save Settings")
w1_habitat.addWidget(widgets["sondehubUploadLabel"], 0, 0, 1, 1)
w1_habitat.addWidget(widgets["sondehubUploadSelector"], 0, 1, 1, 1)
@ -282,54 +283,54 @@ w1_habitat.addWidget(widgets["saveSettingsButton"], 9, 0, 1, 3)
d0_habitat.addWidget(w1_habitat)
w1_other = pg.LayoutWidget()
widgets["horusHeaderLabel"] = QtGui.QLabel("<b><u>Telemetry Forwarding</u></b>")
widgets["horusUploadLabel"] = QtGui.QLabel("<b>Enable Horus UDP Output:</b>")
widgets["horusUploadSelector"] = QtGui.QCheckBox()
widgets["horusHeaderLabel"] = QtWidgets.QLabel("<b><u>Telemetry Forwarding</u></b>")
widgets["horusUploadLabel"] = QtWidgets.QLabel("<b>Enable Horus UDP Output:</b>")
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"] = QtGui.QLabel("<b>Horus UDP Port:</b>")
widgets["horusUDPEntry"] = QtGui.QLineEdit("55672")
widgets["horusUDPLabel"] = QtWidgets.QLabel("<b>Horus UDP Port:</b>")
widgets["horusUDPEntry"] = QtWidgets.QLineEdit("55672")
widgets["horusUDPEntry"].setMaxLength(5)
widgets["horusUDPEntry"].setToolTip(
"UDP Port to output 'Horus UDP' JSON messages to."
)
widgets["ozimuxUploadLabel"] = QtGui.QLabel("<b>Enable OziMux UDP Output:</b>")
widgets["ozimuxUploadSelector"] = QtGui.QCheckBox()
widgets["ozimuxUploadLabel"] = QtWidgets.QLabel("<b>Enable OziMux UDP Output:</b>")
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"] = QtGui.QLabel("<b>Ozimux UDP Port:</b>")
widgets["ozimuxUDPEntry"] = QtGui.QLineEdit("55683")
widgets["ozimuxUDPLabel"] = QtWidgets.QLabel("<b>Ozimux UDP Port:</b>")
widgets["ozimuxUDPEntry"] = QtWidgets.QLineEdit("55683")
widgets["ozimuxUDPEntry"].setMaxLength(5)
widgets["ozimuxUDPEntry"].setToolTip(
"UDP Port to output 'OziMux' UDP messages to."
)
widgets["loggingHeaderLabel"] = QtGui.QLabel("<b><u>Logging</u></b>")
widgets["enableLoggingLabel"] = QtGui.QLabel("<b>Enable Logging:</b>")
widgets["enableLoggingSelector"] = QtGui.QCheckBox()
widgets["loggingHeaderLabel"] = QtWidgets.QLabel("<b><u>Logging</u></b>")
widgets["enableLoggingLabel"] = QtWidgets.QLabel("<b>Enable Logging:</b>")
widgets["enableLoggingSelector"] = QtWidgets.QCheckBox()
widgets["enableLoggingSelector"].setChecked(False)
widgets["enableLoggingSelector"].setToolTip(
"Enable logging of received telemetry to disk (JSON)"
)
widgets["loggingFormatLabel"] = QtGui.QLabel("<b>Log Format:</b>")
widgets["loggingFormatSelector"] = QtGui.QComboBox()
widgets["loggingFormatLabel"] = QtWidgets.QLabel("<b>Log Format:</b>")
widgets["loggingFormatSelector"] = QtWidgets.QComboBox()
widgets["loggingFormatSelector"].addItem("CSV")
widgets["loggingFormatSelector"].addItem("JSON")
widgets["loggingPathLabel"] = QtGui.QLabel("<b>Log Directory:</b>")
widgets["loggingPathEntry"] = QtGui.QLineEdit("")
widgets["loggingPathLabel"] = QtWidgets.QLabel("<b>Log Directory:</b>")
widgets["loggingPathEntry"] = QtWidgets.QLineEdit("")
widgets["loggingPathEntry"].setToolTip(
"Logging Directory"
)
widgets["selectLogDirButton"] = QtGui.QPushButton("Select Directory")
widgets["selectLogDirButton"] = QtWidgets.QPushButton("Select Directory")
widgets["otherHeaderLabel"] = QtGui.QLabel("<b><u>Other Settings</u></b>")
widgets["inhibitCRCLabel"] = QtGui.QLabel("<b>Hide Failed CRC Errors:</b>")
widgets["inhibitCRCSelector"] = QtGui.QCheckBox()
widgets["otherHeaderLabel"] = QtWidgets.QLabel("<b><u>Other Settings</u></b>")
widgets["inhibitCRCLabel"] = QtWidgets.QLabel("<b>Hide Failed CRC Errors:</b>")
widgets["inhibitCRCSelector"] = QtWidgets.QCheckBox()
widgets["inhibitCRCSelector"].setChecked(True)
widgets["inhibitCRCSelector"].setToolTip(
"Hide CRC Failed error messages."
@ -361,41 +362,41 @@ d0_other.addWidget(w1_other)
w1_rotator = pg.LayoutWidget()
widgets["rotatorHeaderLabel"] = QtGui.QLabel("<b><u>Rotator Control</u></b>")
widgets["rotatorHeaderLabel"] = QtWidgets.QLabel("<b><u>Rotator Control</u></b>")
widgets["rotatorTypeLabel"] = QtGui.QLabel("<b>Rotator Type:</b>")
widgets["rotatorTypeSelector"] = QtGui.QComboBox()
widgets["rotatorTypeLabel"] = QtWidgets.QLabel("<b>Rotator Type:</b>")
widgets["rotatorTypeSelector"] = QtWidgets.QComboBox()
widgets["rotatorTypeSelector"].addItem("rotctld")
widgets["rotatorTypeSelector"].addItem("PSTRotator")
widgets["rotatorHostLabel"] = QtGui.QLabel("<b>Rotator Hostname:</b>")
widgets["rotatorHostEntry"] = QtGui.QLineEdit("localhost")
widgets["rotatorHostLabel"] = QtWidgets.QLabel("<b>Rotator Hostname:</b>")
widgets["rotatorHostEntry"] = QtWidgets.QLineEdit("localhost")
widgets["rotatorHostEntry"].setToolTip(
"Hostname of the rotctld or PSTRotator Server.\n"\
)
widgets["rotatorPortLabel"] = QtGui.QLabel("<b>Rotator TCP/UDP Port:</b>")
widgets["rotatorPortEntry"] = QtGui.QLineEdit("4533")
widgets["rotatorPortLabel"] = QtWidgets.QLabel("<b>Rotator TCP/UDP Port:</b>")
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"] = QtGui.QLabel("<b>Rotator Movement Threshold:</b>")
widgets["rotatorThresholdEntry"] = QtGui.QLineEdit("5.0")
widgets["rotatorThresholdLabel"] = QtWidgets.QLabel("<b>Rotator Movement Threshold:</b>")
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"] = QtGui.QPushButton("Start")
widgets["rotatorConnectButton"] = QtWidgets.QPushButton("Start")
widgets["rotatorCurrentStatusLabel"] = QtGui.QLabel("<b>Status:</b>")
widgets["rotatorCurrentStatusValue"] = QtGui.QLabel("Not Started.")
widgets["rotatorCurrentStatusLabel"] = QtWidgets.QLabel("<b>Status:</b>")
widgets["rotatorCurrentStatusValue"] = QtWidgets.QLabel("Not Started.")
widgets["rotatorCurrentPositionLabel"] = QtGui.QLabel("<b>Commanded Az/El:</b>")
widgets["rotatorCurrentPositionValue"] = QtGui.QLabel("---˚, --˚")
widgets["rotatorCurrentPositionLabel"] = QtWidgets.QLabel("<b>Commanded Az/El:</b>")
widgets["rotatorCurrentPositionValue"] = QtWidgets.QLabel("---˚, --˚")
@ -429,25 +430,25 @@ widgets["spectrumPlotData"] = widgets["spectrumPlot"].plot([0])
widgets["estimatorLines"] = [
pg.InfiniteLine(
pos=-1000,
pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.DashLine),
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.DashLine),
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.DashLine),
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.DashLine),
pen=pg.mkPen(color="w", width=2, style=QtCore.Qt.PenStyle.DashLine),
label="F4",
labelOpts={'position':0.9}
),
@ -472,13 +473,13 @@ widgets["spectrumPlotRange"] = [-100, -20]
w3_stats = pg.LayoutWidget()
widgets["snrBar"] = QtWidgets.QProgressBar()
widgets["snrBar"].setOrientation(QtCore.Qt.Vertical)
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.AlignCenter)
widgets["snrLabel"] = QtGui.QLabel("--.-")
widgets["snrLabel"].setAlignment(QtCore.Qt.AlignCenter);
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)
@ -513,14 +514,14 @@ d2_snr.addWidget(widgets["snrPlot"])
# Telemetry Data
w4_data = pg.LayoutWidget()
widgets["latestRawSentenceLabel"] = QtGui.QLabel("<b>Latest Packet (Raw):</b>")
widgets["latestRawSentenceData"] = QtGui.QLineEdit("NO DATA")
widgets["latestRawSentenceLabel"] = QtWidgets.QLabel("<b>Latest Packet (Raw):</b>")
widgets["latestRawSentenceData"] = QtWidgets.QLineEdit("NO DATA")
widgets["latestRawSentenceData"].setReadOnly(True)
widgets["latestDecodedSentenceLabel"] = QtGui.QLabel("<b>Latest Packet (Decoded):</b>")
widgets["latestDecodedSentenceData"] = QtGui.QLineEdit("NO DATA")
widgets["latestDecodedSentenceLabel"] = QtWidgets.QLabel("<b>Latest Packet (Decoded):</b>")
widgets["latestDecodedSentenceData"] = QtWidgets.QLineEdit("NO DATA")
widgets["latestDecodedSentenceData"].setReadOnly(True)
widgets["latestDecodedAgeLabel"] = QtGui.QLabel("<b>Last Packet Age:</b>")
widgets["latestDecodedAgeData"] = QtGui.QLabel("No packet yet!")
widgets["latestDecodedAgeLabel"] = QtWidgets.QLabel("<b>Last Packet Age:</b>")
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)
@ -536,30 +537,30 @@ if 'Windows' in platform.system():
else:
POSITION_LABEL_FONT_SIZE = 16
widgets["latestPacketCallsignLabel"] = QtGui.QLabel("<b>Callsign</b>")
widgets["latestPacketCallsignValue"] = QtGui.QLabel("---")
widgets["latestPacketCallsignValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold))
widgets["latestPacketTimeLabel"] = QtGui.QLabel("<b>Time</b>")
widgets["latestPacketTimeValue"] = QtGui.QLabel("---")
widgets["latestPacketTimeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold))
widgets["latestPacketLatitudeLabel"] = QtGui.QLabel("<b>Latitude</b>")
widgets["latestPacketLatitudeValue"] = QtGui.QLabel("---")
widgets["latestPacketLatitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold))
widgets["latestPacketLongitudeLabel"] = QtGui.QLabel("<b>Longitude</b>")
widgets["latestPacketLongitudeValue"] = QtGui.QLabel("---")
widgets["latestPacketLongitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold))
widgets["latestPacketAltitudeLabel"] = QtGui.QLabel("<b>Altitude</b>")
widgets["latestPacketAltitudeValue"] = QtGui.QLabel("---")
widgets["latestPacketAltitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold))
widgets["latestPacketBearingLabel"] = QtGui.QLabel("<b>Bearing</b>")
widgets["latestPacketBearingValue"] = QtGui.QLabel("---")
widgets["latestPacketBearingValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold))
widgets["latestPacketElevationLabel"] = QtGui.QLabel("<b>Elevation</b>")
widgets["latestPacketElevationValue"] = QtGui.QLabel("---")
widgets["latestPacketElevationValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold))
widgets["latestPacketRangeLabel"] = QtGui.QLabel("<b>Range (km)</b>")
widgets["latestPacketRangeValue"] = QtGui.QLabel("---")
widgets["latestPacketRangeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Bold))
widgets["latestPacketCallsignLabel"] = QtWidgets.QLabel("<b>Callsign</b>")
widgets["latestPacketCallsignValue"] = QtWidgets.QLabel("---")
widgets["latestPacketCallsignValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold))
widgets["latestPacketTimeLabel"] = QtWidgets.QLabel("<b>Time</b>")
widgets["latestPacketTimeValue"] = QtWidgets.QLabel("---")
widgets["latestPacketTimeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold))
widgets["latestPacketLatitudeLabel"] = QtWidgets.QLabel("<b>Latitude</b>")
widgets["latestPacketLatitudeValue"] = QtWidgets.QLabel("---")
widgets["latestPacketLatitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold))
widgets["latestPacketLongitudeLabel"] = QtWidgets.QLabel("<b>Longitude</b>")
widgets["latestPacketLongitudeValue"] = QtWidgets.QLabel("---")
widgets["latestPacketLongitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold))
widgets["latestPacketAltitudeLabel"] = QtWidgets.QLabel("<b>Altitude</b>")
widgets["latestPacketAltitudeValue"] = QtWidgets.QLabel("---")
widgets["latestPacketAltitudeValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold))
widgets["latestPacketBearingLabel"] = QtWidgets.QLabel("<b>Bearing</b>")
widgets["latestPacketBearingValue"] = QtWidgets.QLabel("---")
widgets["latestPacketBearingValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold))
widgets["latestPacketElevationLabel"] = QtWidgets.QLabel("<b>Elevation</b>")
widgets["latestPacketElevationValue"] = QtWidgets.QLabel("---")
widgets["latestPacketElevationValue"].setFont(QtGui.QFont("Courier New", POSITION_LABEL_FONT_SIZE, QtGui.QFont.Weight.Bold))
widgets["latestPacketRangeLabel"] = QtWidgets.QLabel("<b>Range (km)</b>")
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)
@ -708,7 +709,7 @@ telemetry_logger = TelemetryLogger(
# Handlers for various checkboxes and push-buttons
def habitat_position_reupload(upload=True):
def habitat_position_reupload(dummy_arg, upload=True):
""" Trigger a re-upload of user position information """
global widgets, sondehub_uploader
@ -725,13 +726,14 @@ def habitat_position_reupload(upload=True):
if upload:
sondehub_uploader.last_user_position_upload = 0
logging.info("Triggered user position re-upload.")
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():
habitat_position_reupload(False)
habitat_position_reupload(upload=False)
widgets["userCallEntry"].textEdited.connect(update_uploader_details)
@ -875,11 +877,20 @@ def handle_status_update(status):
def get_latest_snr():
global widgets
# Assume 2 Hz stats updates, and take the peak of the last 4 seconds.
SNR_LEN = 2*4
_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_LEN:
return np.max(widgets["snrPlotSNR"][-1*SNR_LEN:])
if len(widgets["snrPlotSNR"])>_snr_lookback:
return np.max(widgets["snrPlotSNR"][-1*_snr_lookback:])
else:
return np.max(widgets["snrPlotSNR"])
@ -963,6 +974,9 @@ def handle_new_packet(frame):
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:
@ -988,6 +1002,9 @@ def handle_new_packet(frame):
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():
@ -1054,7 +1071,7 @@ def handle_new_packet(frame):
telemetry_logger.add(_decoded)
# Try and force a refresh of the displays.
QtGui.QApplication.processEvents()
QtWidgets.QApplication.processEvents()
@ -1070,6 +1087,15 @@ def start_decoding():
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
@ -1249,7 +1275,7 @@ def processQueues():
widgets['latestDecodedAgeData'].setText(f"{_time_delta_hours:02d}:{_time_delta_minutes:02d}:{_time_delta_seconds:02d}")
# Try and force a re-draw.
QtGui.QApplication.processEvents()
QtWidgets.QApplication.processEvents()
if not decoder_init:
# Initialise decoders, and other libraries here.
@ -1341,7 +1367,7 @@ class ConsoleHandler(logging.Handler):
try:
self.log_queue.put_nowait(_text)
except:
print("Queue full!")
print("Console Log Queue full!")
@ -1353,11 +1379,12 @@ logging.getLogger().addHandler(console_handler)
logging.info("Started GUI.")
# Main
def main():
# Start the Qt Loop
if (sys.flags.interactive != 1) or not hasattr(QtCore, "PYQT_VERSION"):
QtGui.QApplication.instance().exec_()
QtWidgets.QApplication.instance().exec()
save_config(widgets)
try:

Wyświetl plik

@ -1,9 +1,9 @@
# Useful widgets
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
from PyQt5 import QtWidgets
# Useful class for adding horizontal lines.
class QHLine(QtGui.QFrame):
class QHLine(QtWidgets.QFrame):
def __init__(self):
super(QHLine, self).__init__()
self.setFrameShape(QtGui.QFrame.HLine)
self.setFrameShadow(QtGui.QFrame.Sunken)
self.setFrameShape(QtWidgets.QFrame.HLine)
self.setFrameShadow(QtWidgets.QFrame.Sunken)

Wyświetl plik

@ -1,6 +1,6 @@
[tool.poetry]
name = "horusgui"
version = "0.3.13"
version = "0.3.18"
description = ""
authors = ["Mark Jessop <vk5qi@rfhead.net>"]
@ -8,11 +8,10 @@ authors = ["Mark Jessop <vk5qi@rfhead.net>"]
python = "^3.6"
requests = "^2.24.0"
crcmod = "^1.7"
PyQt5 = "^5.13.0"
pyqtgraph = "^0.11.0"
PyQt5 = "^5.15.0"
pyqtgraph = "^0.12.3"
pyaudio = "^0.2.11"
"ruamel.yaml" = "^0.16.10"
horusdemodlib = "^0.3.12"
horusdemodlib = "^0.3.13"
[tool.poetry.dev-dependencies]

Wyświetl plik

@ -2,7 +2,6 @@ numpy
pyaudio
crcmod
PyQt5
pyqtgraph==0.12.4
ruamel.yaml
pyqtgraph
requests
horusdemodlib>=0.3.12