Add GQRX UDP input

pull/13/head
Mark Jessop 2020-07-12 17:09:23 +09:30
rodzic 8d0e0ea544
commit b935f82e6d
7 zmienionych plików z 129 dodań i 18 usunięć

Wyświetl plik

@ -19,11 +19,14 @@ Written by:
![Screenshot](doc/horusgui_screenshot.png) ![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 ### TODO LIST - Important Stuff
* Better build system via Travis (@xssfox) * Better build system via Travis (@xssfox)
### TODO LIST - Extras ### TODO LIST - Extras
* UDP input from GQRX
* Waterfall Display (? Need something GPU accelerated if possible...) * Waterfall Display (? Need something GPU accelerated if possible...)
* rotctld rotator control? * rotctld rotator control?

Wyświetl plik

@ -4,8 +4,8 @@ block_cipher = None
a = Analysis(['horus-gui.py'], a = Analysis(['horus-gui.py'],
pathex=['C:\\HAB\\horus-gui'], pathex=['.'],
binaries=[('libhorus.dll','.')], binaries=[('libhorus.dll','.'),('libgcc_s_seh-1.dll','.'),('libwinpthread-1.dll','.'),('libstdc++-6.dll','.')],
datas=[], datas=[],
hiddenimports=['pkg_resources.py2_warn'], hiddenimports=['pkg_resources.py2_warn'],
hookspath=[], hookspath=[],

Wyświetl plik

@ -1 +1 @@
__version__ = "0.1.6" __version__ = "0.1.7"

Wyświetl plik

@ -20,6 +20,8 @@ def init_audio(widgets):
# Clear list # Clear list
widgets["audioDeviceSelector"].clear() widgets["audioDeviceSelector"].clear()
# Add in the 'dummy' GQRX UDP interface
widgets["audioDeviceSelector"].addItem('GQRX UDP')
# Iterate through PyAudio devices # Iterate through PyAudio devices
for x in range(0, pyAudio.get_device_count()): for x in range(0, pyAudio.get_device_count()):
@ -54,6 +56,11 @@ def populate_sample_rates(widgets):
# Get information on current audio device # Get information on current audio device
_dev_name = widgets["audioDeviceSelector"].currentText() _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: if _dev_name in audioDevices:
# TODO: Determine valid samples rates. For now, just use the default. # TODO: Determine valid samples rates. For now, just use the default.
# TODO: Add support for resampling. # TODO: Add support for resampling.

Wyświetl plik

@ -25,6 +25,7 @@ from threading import Thread
from .widgets import * from .widgets import *
from .audio import * from .audio import *
from .udpaudio import *
from .fft import * from .fft import *
from .modem import * from .modem import *
from .config import * from .config import *
@ -73,7 +74,7 @@ pg.mkQApp()
win = QtGui.QMainWindow() win = QtGui.QMainWindow()
area = DockArea() area = DockArea()
win.setCentralWidget(area) win.setCentralWidget(area)
win.setWindowTitle("Horus Telemetry GUI") win.setWindowTitle(f"Horus Telemetry GUI - v{__version__}")
win.setWindowIcon(getHorusIcon()) win.setWindowIcon(getHorusIcon())
# Create multiple dock areas, for displaying our data. # Create multiple dock areas, for displaying our data.
@ -599,8 +600,12 @@ def start_decoding():
if not running: if not running:
# Grab settings off widgets # Grab settings off widgets
_dev_name = widgets["audioDeviceSelector"].currentText() _dev_name = widgets["audioDeviceSelector"].currentText()
_sample_rate = int(widgets["audioSampleRateSelector"].currentText()) if _dev_name != 'GQRX UDP':
_dev_index = audio_devices[_dev_name]["index"] _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 # Grab Horus Settings
_modem_name = widgets["horusModemSelector"].currentText() _modem_name = widgets["horusModemSelector"].currentText()
@ -647,18 +652,29 @@ def start_decoding():
mode=_modem_id, mode=_modem_id,
rate=_modem_rate, rate=_modem_rate,
tone_spacing=_modem_tone_spacing, tone_spacing=_modem_tone_spacing,
callback=handle_new_packet callback=handle_new_packet,
sample_rate=_sample_rate
) )
# Setup Audio # Setup Audio (or UDP input)
audio_stream = AudioStream( if _dev_name == 'GQRX UDP':
_dev_index, audio_stream = UDPStream(
fs=_sample_rate, udp_port=7355,
block_size=fft_process.stride, fs=_sample_rate,
fft_input=fft_process.add_samples, block_size=fft_process.stride,
modem=horus_modem, fft_input=fft_process.add_samples,
stats_callback=add_stats_update 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") widgets["startDecodeButton"].setText("Stop")
running = True running = True

Wyświetl plik

@ -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()

Wyświetl plik

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "horusgui" name = "horusgui"
version = "0.1.6" version = "0.1.7"
description = "" description = ""
authors = ["Mark Jessop <vk5qi@rfhead.net>"] authors = ["Mark Jessop <vk5qi@rfhead.net>"]