kopia lustrzana https://github.com/projecthorus/horus-gui
Add GQRX UDP input
rodzic
8d0e0ea544
commit
b935f82e6d
|
@ -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?
|
||||||
|
|
||||||
|
|
|
@ -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=[],
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.1.6"
|
__version__ = "0.1.7"
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
|
@ -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>"]
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue