diff --git a/README.md b/README.md index dd32275..3b08777 100755 --- a/README.md +++ b/README.md @@ -19,11 +19,14 @@ Written by: ![Screenshot](doc/horusgui_screenshot.png) +### Known Issues +* Occasional crash when processing is stopped just as a packet is being processed by horus_api. +* Queue events not processed on OSX when the application is running in the background. + ### TODO LIST - Important Stuff * Better build system via Travis (@xssfox) ### TODO LIST - Extras -* UDP input from GQRX * Waterfall Display (? Need something GPU accelerated if possible...) * rotctld rotator control? diff --git a/horus-gui_win.spec b/horus-gui_win.spec index dc880b8..9f55889 100755 --- a/horus-gui_win.spec +++ b/horus-gui_win.spec @@ -4,8 +4,8 @@ block_cipher = None a = Analysis(['horus-gui.py'], - pathex=['C:\\HAB\\horus-gui'], - binaries=[('libhorus.dll','.')], + pathex=['.'], + binaries=[('libhorus.dll','.'),('libgcc_s_seh-1.dll','.'),('libwinpthread-1.dll','.'),('libstdc++-6.dll','.')], datas=[], hiddenimports=['pkg_resources.py2_warn'], hookspath=[], diff --git a/horusgui/__init__.py b/horusgui/__init__.py index 0a8da88..f1380ee 100755 --- a/horusgui/__init__.py +++ b/horusgui/__init__.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.1.7" diff --git a/horusgui/audio.py b/horusgui/audio.py index 7b23b6b..d50c975 100644 --- a/horusgui/audio.py +++ b/horusgui/audio.py @@ -20,6 +20,8 @@ def init_audio(widgets): # Clear list widgets["audioDeviceSelector"].clear() + # Add in the 'dummy' GQRX UDP interface + widgets["audioDeviceSelector"].addItem('GQRX UDP') # Iterate through PyAudio devices for x in range(0, pyAudio.get_device_count()): @@ -54,6 +56,11 @@ def populate_sample_rates(widgets): # Get information on current audio device _dev_name = widgets["audioDeviceSelector"].currentText() + # Add in fixed sample rate for GQRX input. + if _dev_name == 'GQRX UDP': + widgets["audioSampleRateSelector"].addItem(str(48000)) + widgets["audioSampleRateSelector"].setCurrentIndex(0) + if _dev_name in audioDevices: # TODO: Determine valid samples rates. For now, just use the default. # TODO: Add support for resampling. diff --git a/horusgui/gui.py b/horusgui/gui.py index 964a322..318b504 100644 --- a/horusgui/gui.py +++ b/horusgui/gui.py @@ -25,6 +25,7 @@ from threading import Thread from .widgets import * from .audio import * +from .udpaudio import * from .fft import * from .modem import * from .config import * @@ -73,7 +74,7 @@ pg.mkQApp() win = QtGui.QMainWindow() area = DockArea() win.setCentralWidget(area) -win.setWindowTitle("Horus Telemetry GUI") +win.setWindowTitle(f"Horus Telemetry GUI - v{__version__}") win.setWindowIcon(getHorusIcon()) # Create multiple dock areas, for displaying our data. @@ -599,8 +600,12 @@ def start_decoding(): if not running: # Grab settings off widgets _dev_name = widgets["audioDeviceSelector"].currentText() - _sample_rate = int(widgets["audioSampleRateSelector"].currentText()) - _dev_index = audio_devices[_dev_name]["index"] + if _dev_name != 'GQRX UDP': + _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() @@ -647,18 +652,29 @@ def start_decoding(): mode=_modem_id, rate=_modem_rate, tone_spacing=_modem_tone_spacing, - callback=handle_new_packet + callback=handle_new_packet, + sample_rate=_sample_rate ) - # Setup Audio - 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 - ) + # Setup Audio (or UDP input) + if _dev_name == 'GQRX UDP': + 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 diff --git a/horusgui/udpaudio.py b/horusgui/udpaudio.py new file mode 100644 index 0000000..485fb5a --- /dev/null +++ b/horusgui/udpaudio.py @@ -0,0 +1,85 @@ +# UDP Audio Source (Obtaining audio from GQRX) +import socket +import traceback +from threading import Thread + +class UDPStream(object): + """ Listen for UDP Audio data from GQRX (s16, 48kHz), and pass data around to different callbacks """ + + def __init__(self, udp_port=7355, fs=48000, block_size=8192, fft_input=None, modem=None, stats_callback = None): + + self.udp_port = udp_port + self.fs = fs + self.block_size = block_size + + self.fft_input = fft_input + + self.modem = modem + self.stats_callback = stats_callback + + # Start audio stream + self.listen_thread_running = True + self.listen_thread = Thread(target=self.udp_listen_thread) + self.listen_thread.start() + + + def udp_listen_thread(self): + """ Open a UDP socket and listen for incoming data """ + + self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) + self.s.settimeout(1) + self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + # OSX Specific + try: + self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except: + pass + + self.s.bind(('',self.udp_port)) + while self.listen_thread_running: + try: + m = self.s.recvfrom(65535) + except socket.timeout: + m = None + except: + traceback.print_exc() + + if m != None: + self.handle_samples(m[0], len(m[0])//2) + + self.s.close() + + + def handle_samples(self, data, frame_count, time_info="", status_flags=""): + """ Handle incoming samples from pyaudio """ + + # Pass samples directly into fft. + if self.fft_input: + self.fft_input(data) + + if self.modem: + # Add samples to modem + _stats = self.modem.add_samples(data) + # Send any stats data back to the stats callback + if _stats: + if self.stats_callback: + self.stats_callback(_stats) + + return (None, None) + + def stop(self): + """ Halt stream """ + self.listen_thread_running = False + + +if __name__ == "__main__": + import time + + udp = UDPStream() + + try: + while True: + time.sleep(5) + except KeyboardInterrupt: + udp.close() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 22c5bd4..04e4478 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "horusgui" -version = "0.1.6" +version = "0.1.7" description = "" authors = ["Mark Jessop "]