kopia lustrzana https://github.com/projecthorus/horus-gui
commit
6fec273eaf
|
@ -56,6 +56,7 @@ $ cd horusdemodlib && mkdir build && cd build
|
|||
$ cmake ..
|
||||
$ make
|
||||
$ sudo make install
|
||||
$ sudo ldconfig
|
||||
```
|
||||
|
||||
### Grab this Repo
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "0.3.19"
|
||||
__version__ = "0.4.0"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Audio Interfacing
|
||||
import logging
|
||||
import pyaudio
|
||||
import time
|
||||
|
||||
|
||||
# Global PyAudio object
|
||||
|
@ -125,8 +126,14 @@ class AudioStream(object):
|
|||
self.modem = modem
|
||||
self.stats_callback = stats_callback
|
||||
|
||||
# Start audio stream
|
||||
self.audio_thread_running = True
|
||||
|
||||
self.audio = pyaudio.PyAudio()
|
||||
|
||||
def start_stream(self, info_callback=None):
|
||||
if info_callback:
|
||||
self.stats_callback = info_callback
|
||||
|
||||
self.stream = self.audio.open(
|
||||
format=pyaudio.paInt16,
|
||||
channels=1,
|
||||
|
@ -138,6 +145,11 @@ class AudioStream(object):
|
|||
stream_callback=self.handle_samples,
|
||||
)
|
||||
|
||||
while self.audio_thread_running:
|
||||
time.sleep(0.5)
|
||||
|
||||
logging.debug("Stopped audio stream thread")
|
||||
|
||||
def handle_samples(self, data, frame_count, time_info="", status_flags=""):
|
||||
""" Handle incoming samples from pyaudio """
|
||||
|
||||
|
@ -151,10 +163,11 @@ class AudioStream(object):
|
|||
# Send any stats data back to the stats callback
|
||||
if _stats:
|
||||
if self.stats_callback:
|
||||
self.stats_callback(_stats)
|
||||
self.stats_callback.emit(_stats)
|
||||
|
||||
return (None, pyaudio.paContinue)
|
||||
|
||||
def stop(self):
|
||||
""" Halt stream """
|
||||
self.stream.close()
|
||||
self.audio_thread_running = False
|
||||
self.stream.close()
|
|
@ -34,9 +34,11 @@ default_config = {
|
|||
"rotator_type": "rotctld",
|
||||
"rotator_host": "localhost",
|
||||
"rotator_port": 4533,
|
||||
"rotator_rangeinhibit": True,
|
||||
"logging_enabled": False,
|
||||
"log_format": "CSV",
|
||||
"log_directory": "",
|
||||
"fft_smoothing": False,
|
||||
"payload_list": json.dumps(horusdemodlib.payloads.HORUS_PAYLOAD_LIST),
|
||||
"custom_field_list": json.dumps({})
|
||||
}
|
||||
|
@ -76,7 +78,7 @@ def read_config(widgets):
|
|||
global qt_settings, default_config
|
||||
|
||||
# This is getting a bit ridiculous, need to re-think this approach.
|
||||
OK_VERSIONS = [__version__, '0.3.18', '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']
|
||||
OK_VERSIONS = [__version__,'0.3.19', '0.3.18', '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:
|
||||
|
@ -124,12 +126,15 @@ def read_config(widgets):
|
|||
widgets["rotatorTypeSelector"].setCurrentText(default_config["rotator_type"])
|
||||
widgets["rotatorHostEntry"].setText(str(default_config["rotator_host"]))
|
||||
widgets["rotatorPortEntry"].setText(str(default_config["rotator_port"]))
|
||||
widgets["rotatorRangeInhibit"].setChecked(ValueToBool(default_config["rotator_rangeinhibit"]))
|
||||
|
||||
# Logging Settings
|
||||
widgets["loggingPathEntry"].setText(str(default_config["log_directory"]))
|
||||
widgets["loggingFormatSelector"].setCurrentText(default_config["log_format"])
|
||||
widgets["enableLoggingSelector"].setChecked(ValueToBool(default_config["logging_enabled"]))
|
||||
|
||||
widgets["fftSmoothingSelector"].setChecked(ValueToBool(default_config["fft_smoothing"]))
|
||||
|
||||
if default_config['baud_rate'] != -1:
|
||||
widgets["horusModemRateSelector"].setCurrentText(str(default_config['baud_rate']))
|
||||
|
||||
|
@ -173,9 +178,11 @@ def save_config(widgets):
|
|||
default_config["rotator_type"] = widgets["rotatorTypeSelector"].currentText()
|
||||
default_config["rotator_host"] = widgets["rotatorHostEntry"].text()
|
||||
default_config["rotator_port"] = int(widgets["rotatorPortEntry"].text())
|
||||
default_config["rotator_rangeinhibit"] = widgets["rotatorRangeInhibit"].isChecked()
|
||||
default_config["logging_enabled"] = widgets["enableLoggingSelector"].isChecked()
|
||||
default_config["log_directory"] = widgets["loggingPathEntry"].text()
|
||||
default_config["log_format"] = widgets["loggingFormatSelector"].currentText()
|
||||
default_config["fft_smoothing"] = widgets["fftSmoothingSelector"].isChecked()
|
||||
|
||||
default_config["payload_list"] = json.dumps(horusdemodlib.payloads.HORUS_PAYLOAD_LIST)
|
||||
default_config["custom_field_list"] = json.dumps(horusdemodlib.payloads.HORUS_CUSTOM_FIELDS)
|
||||
|
|
|
@ -3,7 +3,7 @@ import logging
|
|||
import time
|
||||
import numpy as np
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
#from threading import Thread
|
||||
|
||||
|
||||
class FFTProcess(object):
|
||||
|
@ -37,8 +37,8 @@ class FFTProcess(object):
|
|||
|
||||
self.processing_thread_running = True
|
||||
|
||||
self.t = Thread(target=self.processing_thread)
|
||||
self.t.start()
|
||||
#self.t = Thread(target=self.processing_thread)
|
||||
#self.t.start()
|
||||
|
||||
def init_window(self):
|
||||
""" Initialise Window functions and FFT scales. """
|
||||
|
@ -74,7 +74,7 @@ class FFTProcess(object):
|
|||
|
||||
if self.callback != None:
|
||||
if self.update_counter % self.update_decimation == 0:
|
||||
self.callback({"fft": _fft[self.mask], "scale": self.fft_scale[self.mask], 'dbfs': _dbfs})
|
||||
self.callback.emit({"fft": _fft[self.mask], "scale": self.fft_scale[self.mask], 'dbfs': _dbfs})
|
||||
|
||||
self.update_counter += 1
|
||||
|
||||
|
@ -86,7 +86,9 @@ class FFTProcess(object):
|
|||
while len(self.sample_buffer) > self.nfft * self.sample_width:
|
||||
self.perform_fft()
|
||||
|
||||
def processing_thread(self):
|
||||
def processing_thread(self, info_callback=None):
|
||||
if info_callback:
|
||||
self.callback = info_callback
|
||||
|
||||
while self.processing_thread_running:
|
||||
if self.input_queue.qsize() > 0:
|
||||
|
@ -95,6 +97,8 @@ class FFTProcess(object):
|
|||
else:
|
||||
time.sleep(0.01)
|
||||
|
||||
logging.debug("Stopped FFT processing thread")
|
||||
|
||||
def add_samples(self, samples):
|
||||
""" Add a block of samples to the input queue """
|
||||
try:
|
||||
|
|
2784
horusgui/gui.py
2784
horusgui/gui.py
Plik diff jest za duży
Load Diff
|
@ -1,353 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Horus Telemetry GUI - Habitat Uploader
|
||||
#
|
||||
# Mark Jessop <vk5qi@rfhead.net>
|
||||
#
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import requests
|
||||
import time
|
||||
from base64 import b64encode
|
||||
from hashlib import sha256
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class HabitatUploader(object):
|
||||
"""
|
||||
Queued Habitat Telemetry Uploader class
|
||||
|
||||
Packets to be uploaded to Habitat are added to a queue for uploading.
|
||||
If an upload attempt times out, the packet is discarded.
|
||||
If the queue fills up (probably indicating no network connection, and a fast packet downlink rate),
|
||||
it is immediately emptied, to avoid upload of out-of-date packets.
|
||||
"""
|
||||
|
||||
HABITAT_URL = "http://habitat.habhub.org/"
|
||||
HABITAT_DB = "habitat"
|
||||
HABITAT_UUIDS = HABITAT_URL + "_uuids?count=%d"
|
||||
HABITAT_DB_URL = HABITAT_URL + HABITAT_DB + "/"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_callsign="FSK_DEMOD",
|
||||
listener_lat=0.0,
|
||||
listener_lon=0.0,
|
||||
listener_radio="",
|
||||
listener_antenna="",
|
||||
queue_size=64,
|
||||
upload_timeout=10,
|
||||
upload_retries=5,
|
||||
upload_retry_interval=0.25,
|
||||
inhibit=False,
|
||||
):
|
||||
""" Create a Habitat Uploader object. """
|
||||
|
||||
self.upload_timeout = upload_timeout
|
||||
self.upload_retries = upload_retries
|
||||
self.upload_retry_interval = upload_retry_interval
|
||||
self.queue_size = queue_size
|
||||
self.habitat_upload_queue = Queue(queue_size)
|
||||
self.inhibit = inhibit
|
||||
|
||||
# Listener information
|
||||
self.user_callsign = user_callsign
|
||||
self.listener_lat = listener_lat
|
||||
self.listener_lon = listener_lon
|
||||
self.listener_radio = listener_radio
|
||||
self.listener_antenna = listener_antenna
|
||||
self.position_uploaded = False
|
||||
|
||||
self.last_freq_hz = None
|
||||
|
||||
self.callsign_init = False
|
||||
self.uuids = []
|
||||
|
||||
# Start the uploader thread.
|
||||
self.habitat_uploader_running = True
|
||||
self.uploadthread = Thread(target=self.habitat_upload_thread)
|
||||
self.uploadthread.start()
|
||||
|
||||
def habitat_upload(self, sentence):
|
||||
""" Upload a UKHAS-standard telemetry sentence to Habitat """
|
||||
|
||||
# Generate payload to be uploaded
|
||||
# b64encode accepts and returns bytes objects.
|
||||
_sentence_b64 = b64encode(sentence.encode("ascii"))
|
||||
_date = datetime.datetime.utcnow().isoformat("T") + "Z"
|
||||
_user_call = self.user_callsign
|
||||
|
||||
_data = {
|
||||
"type": "payload_telemetry",
|
||||
"data": {
|
||||
"_raw": _sentence_b64.decode(
|
||||
"ascii"
|
||||
) # Convert back to a string to be serialisable
|
||||
},
|
||||
"receivers": {
|
||||
_user_call: {"time_created": _date, "time_uploaded": _date,},
|
||||
},
|
||||
}
|
||||
|
||||
if self.last_freq_hz:
|
||||
# Add in frequency information if we have it.
|
||||
_data["receivers"][_user_call]["rig_info"] = {"frequency": self.last_freq_hz}
|
||||
|
||||
# The URl to upload to.
|
||||
_url = f"{self.HABITAT_URL}{self.HABITAT_DB}/_design/payload_telemetry/_update/add_listener/{sha256(_sentence_b64).hexdigest()}"
|
||||
|
||||
# Delay for a random amount of time between 0 and upload_retry_interval*2 seconds.
|
||||
time.sleep(random.random() * self.upload_retry_interval * 2.0)
|
||||
|
||||
_retries = 0
|
||||
|
||||
# When uploading, we have three possible outcomes:
|
||||
# - Can't connect. No point re-trying in this situation.
|
||||
# - The packet is uploaded successfult (201 / 403)
|
||||
# - There is a upload conflict on the Habitat DB end (409). We can retry and it might work.
|
||||
while _retries < self.upload_retries:
|
||||
# Run the request.
|
||||
try:
|
||||
_req = requests.put(
|
||||
_url, data=json.dumps(_data), timeout=(self.upload_timeout, 6.1)
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error("Habitat - Upload Failed: %s" % str(e))
|
||||
break
|
||||
|
||||
if _req.status_code == 201 or _req.status_code == 403:
|
||||
# 201 = Success, 403 = Success, sentence has already seen by others.
|
||||
logging.info(f"Habitat - Uploaded sentence: {sentence.strip()}")
|
||||
_upload_success = True
|
||||
break
|
||||
elif _req.status_code == 409:
|
||||
# 409 = Upload conflict (server busy). Sleep for a moment, then retry.
|
||||
logging.debug("Habitat - Upload conflict.. retrying.")
|
||||
time.sleep(random.random() * self.upload_retry_interval)
|
||||
_retries += 1
|
||||
else:
|
||||
logging.error(
|
||||
"Habitat - Error uploading to Habitat. Status Code: %d."
|
||||
% _req.status_code
|
||||
)
|
||||
break
|
||||
|
||||
if _retries == self.upload_retries:
|
||||
logging.error(
|
||||
"Habitat - Upload conflict not resolved with %d retries."
|
||||
% self.upload_retries
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def habitat_upload_thread(self):
|
||||
""" Handle uploading of packets to Habitat """
|
||||
|
||||
logging.info("Started Habitat Uploader Thread.")
|
||||
|
||||
while self.habitat_uploader_running:
|
||||
|
||||
if self.habitat_upload_queue.qsize() > 0:
|
||||
# If the queue is completely full, jump to the most recent telemetry sentence.
|
||||
if self.habitat_upload_queue.qsize() == self.queue_size:
|
||||
while not self.habitat_upload_queue.empty():
|
||||
sentence = self.habitat_upload_queue.get()
|
||||
|
||||
logging.warning(
|
||||
"Habitat uploader queue was full - possible connectivity issue."
|
||||
)
|
||||
else:
|
||||
# Otherwise, get the first item in the queue.
|
||||
sentence = self.habitat_upload_queue.get()
|
||||
|
||||
# Attempt to upload it.
|
||||
self.habitat_upload(sentence)
|
||||
|
||||
else:
|
||||
# Wait for a short time before checking the queue again.
|
||||
time.sleep(0.5)
|
||||
|
||||
#
|
||||
# Habitat listener position update disabled 2022-09, due to Habitat going away...
|
||||
#
|
||||
# if not self.position_uploaded:
|
||||
# # Validate the lat/lon entries.
|
||||
# try:
|
||||
# _lat = float(self.listener_lat)
|
||||
# _lon = float(self.listener_lon)
|
||||
|
||||
# if (_lat != 0.0) or (_lon != 0.0):
|
||||
# _success = self.uploadListenerPosition(
|
||||
# self.user_callsign,
|
||||
# _lat,
|
||||
# _lon,
|
||||
# self.listener_radio,
|
||||
# self.listener_antenna,
|
||||
# )
|
||||
# else:
|
||||
# logging.warning("Listener position set to 0.0/0.0 - not uploading.")
|
||||
|
||||
# except Exception as e:
|
||||
# logging.error("Error uploading listener position: %s" % str(e))
|
||||
|
||||
# # Set this flag regardless if the upload worked.
|
||||
# # The user can trigger a re-upload.
|
||||
# self.position_uploaded = True
|
||||
|
||||
|
||||
logging.info("Stopped Habitat Uploader Thread.")
|
||||
|
||||
def add(self, sentence):
|
||||
""" Add a sentence to the upload queue """
|
||||
|
||||
if self.inhibit:
|
||||
# We have upload inhibited. Return.
|
||||
return
|
||||
|
||||
# Handling of arbitrary numbers of $$'s at the start of a sentence:
|
||||
# Extract the data part of the sentence (i.e. everything after the $$'s')
|
||||
sentence = sentence.split("$")[-1]
|
||||
# Now add the *correct* number of $$s back on.
|
||||
sentence = "$$" + sentence
|
||||
|
||||
if not (sentence[-1] == "\n"):
|
||||
sentence += "\n"
|
||||
|
||||
try:
|
||||
self.habitat_upload_queue.put_nowait(sentence)
|
||||
except Exception as e:
|
||||
logging.error("Error adding sentence to queue: %s" % str(e))
|
||||
|
||||
def close(self):
|
||||
""" Shutdown uploader thread. """
|
||||
self.habitat_uploader_running = False
|
||||
|
||||
def ISOStringNow(self):
|
||||
return "%sZ" % datetime.datetime.utcnow().isoformat()
|
||||
|
||||
def postListenerData(self, doc, timeout=10):
|
||||
|
||||
# do we have at least one uuid, if not go get more
|
||||
if len(self.uuids) < 1:
|
||||
self.fetchUuids()
|
||||
|
||||
# Attempt to add UUID and time data to document.
|
||||
try:
|
||||
doc["_id"] = self.uuids.pop()
|
||||
except IndexError:
|
||||
logging.error(
|
||||
"Habitat - Unable to post listener data - no UUIDs available."
|
||||
)
|
||||
return False
|
||||
|
||||
doc["time_uploaded"] = self.ISOStringNow()
|
||||
|
||||
try:
|
||||
_r = requests.post(
|
||||
f"{self.HABITAT_URL}{self.HABITAT_DB}/", json=doc, timeout=timeout
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error("Habitat - Could not post listener data - %s" % str(e))
|
||||
return False
|
||||
|
||||
def fetchUuids(self, timeout=10):
|
||||
|
||||
_retries = 5
|
||||
|
||||
while _retries > 0:
|
||||
try:
|
||||
_r = requests.get(self.HABITAT_UUIDS % 10, timeout=timeout)
|
||||
self.uuids.extend(_r.json()["uuids"])
|
||||
logging.debug("Habitat - Got UUIDs")
|
||||
return
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
"Habitat - Unable to fetch UUIDs, retrying in 2 seconds - %s"
|
||||
% str(e)
|
||||
)
|
||||
time.sleep(2)
|
||||
_retries = _retries - 1
|
||||
continue
|
||||
|
||||
logging.error("Habitat - Gave up trying to get UUIDs.")
|
||||
return
|
||||
|
||||
def initListenerCallsign(self, callsign, radio="", antenna=""):
|
||||
doc = {
|
||||
"type": "listener_information",
|
||||
"time_created": self.ISOStringNow(),
|
||||
"data": {"callsign": callsign, "antenna": antenna, "radio": radio,},
|
||||
}
|
||||
|
||||
resp = self.postListenerData(doc)
|
||||
|
||||
if resp is True:
|
||||
logging.debug("Habitat - Listener Callsign Initialized.")
|
||||
return True
|
||||
else:
|
||||
logging.error("Habitat - Unable to initialize callsign.")
|
||||
return False
|
||||
|
||||
def uploadListenerPosition(self, callsign, lat, lon, radio="", antenna=""):
|
||||
""" Initializer Listener Callsign, and upload Listener Position """
|
||||
|
||||
# Attempt to initialize the listeners callsign
|
||||
resp = self.initListenerCallsign(callsign, radio=radio, antenna=antenna)
|
||||
# If this fails, it means we can't contact the Habitat server,
|
||||
# so there is no point continuing.
|
||||
if resp is False:
|
||||
return False
|
||||
|
||||
doc = {
|
||||
"type": "listener_telemetry",
|
||||
"time_created": self.ISOStringNow(),
|
||||
"data": {
|
||||
"callsign": callsign,
|
||||
"chase": False,
|
||||
"latitude": lat,
|
||||
"longitude": lon,
|
||||
"altitude": 0,
|
||||
"speed": 0,
|
||||
},
|
||||
}
|
||||
|
||||
# post position to habitat
|
||||
resp = self.postListenerData(doc)
|
||||
if resp is True:
|
||||
logging.info("Habitat - Listener information uploaded.")
|
||||
return True
|
||||
else:
|
||||
logging.error("Habitat - Unable to upload listener information.")
|
||||
return False
|
||||
|
||||
def trigger_position_upload(self):
|
||||
""" Trigger a re-upload of the listener position """
|
||||
self.position_uploaded = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Setup Logging
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s %(levelname)s: %(message)s", level=logging.INFO
|
||||
)
|
||||
|
||||
habitat = HabitatUploader(
|
||||
user_callsign="HORUSGUI_TEST",
|
||||
listener_lat=-34.0,
|
||||
listener_lon=138.0,
|
||||
listener_radio="Testing Habitat Uploader",
|
||||
listener_antenna="Wet Noodle",
|
||||
)
|
||||
|
||||
habitat.add("$$DUMMY,0,0.0,0.0*F000")
|
||||
|
||||
time.sleep(10)
|
||||
habitat.trigger_position_upload()
|
||||
time.sleep(5)
|
||||
habitat.close()
|
Plik binarny nie jest wyświetlany.
|
@ -9,7 +9,7 @@ import socket
|
|||
import time
|
||||
import logging
|
||||
import traceback
|
||||
from threading import Thread
|
||||
# from threading import Thread
|
||||
|
||||
class ROTCTLD(object):
|
||||
""" rotctld (hamlib) communication class """
|
||||
|
@ -112,11 +112,11 @@ class PSTRotator(object):
|
|||
self.poll_rate = poll_rate
|
||||
self.azel_thread_running = True
|
||||
|
||||
self.t_rx = Thread(target=self.azel_rx_loop)
|
||||
self.t_rx.start()
|
||||
# self.t_rx = Thread(target=self.azel_rx_loop)
|
||||
# self.t_rx.start()
|
||||
|
||||
self.t_poll = Thread(target=self.azel_poll_loop)
|
||||
self.t_poll.start()
|
||||
# self.t_poll = Thread(target=self.azel_poll_loop)
|
||||
# self.t_poll.start()
|
||||
|
||||
|
||||
def close(self):
|
||||
|
@ -157,13 +157,13 @@ class PSTRotator(object):
|
|||
except:
|
||||
pass
|
||||
|
||||
def azel_poll_loop(self):
|
||||
def azel_poll_loop(self, info_callback=None):
|
||||
while self.azel_thread_running:
|
||||
self.poll_azel()
|
||||
logging.debug("Poll sent to PSTRotator.")
|
||||
time.sleep(self.poll_rate)
|
||||
|
||||
def azel_rx_loop(self):
|
||||
def azel_rx_loop(self, info_callback=None):
|
||||
""" Listen for Azimuth and Elevation reports from PSTRotator"""
|
||||
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||
s.settimeout(1)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# UDP Audio Source (Obtaining audio from GQRX)
|
||||
import socket
|
||||
import traceback
|
||||
from threading import Thread
|
||||
#from threading import Thread
|
||||
|
||||
|
||||
class UDPStream(object):
|
||||
""" Listen for UDP Audio data from GQRX (s16, 48kHz), and pass data around to different callbacks """
|
||||
|
@ -19,13 +20,16 @@ class UDPStream(object):
|
|||
|
||||
# Start audio stream
|
||||
self.listen_thread_running = True
|
||||
self.listen_thread = Thread(target=self.udp_listen_thread)
|
||||
self.listen_thread.start()
|
||||
#self.listen_thread = Thread(target=self.udp_listen_thread)
|
||||
#self.listen_thread.start()
|
||||
|
||||
|
||||
def udp_listen_thread(self):
|
||||
def udp_listen_thread(self, info_callback=None):
|
||||
""" Open a UDP socket and listen for incoming data """
|
||||
|
||||
if info_callback:
|
||||
self.stats_callback = info_callback
|
||||
|
||||
self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||
self.s.settimeout(1)
|
||||
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
@ -50,7 +54,6 @@ class UDPStream(object):
|
|||
|
||||
self.s.close()
|
||||
|
||||
|
||||
def handle_samples(self, data, frame_count, time_info="", status_flags=""):
|
||||
""" Handle incoming samples from pyaudio """
|
||||
|
||||
|
@ -64,7 +67,7 @@ class UDPStream(object):
|
|||
# Send any stats data back to the stats callback
|
||||
if _stats:
|
||||
if self.stats_callback:
|
||||
self.stats_callback(_stats)
|
||||
self.stats_callback.emit(_stats)
|
||||
|
||||
return (None, None)
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Useful widgets
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt6 import QtWidgets
|
||||
|
||||
# Useful class for adding horizontal lines.
|
||||
class QHLine(QtWidgets.QFrame):
|
||||
def __init__(self):
|
||||
super(QHLine, self).__init__()
|
||||
self.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.setFrameShape(QtWidgets.QFrame.Shape.HLine)
|
||||
self.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -1,7 +1,8 @@
|
|||
numpy
|
||||
pyaudio
|
||||
crcmod
|
||||
PyQt5
|
||||
PyQt6
|
||||
pyqtgraph
|
||||
requests
|
||||
horusdemodlib>=0.3.12
|
||||
audioop-lts; python_version>='3.13'
|
Ładowanie…
Reference in New Issue