kopia lustrzana https://github.com/projecthorus/horus-gui
commit
6fec273eaf
|
@ -56,6 +56,7 @@ $ cd horusdemodlib && mkdir build && cd build
|
||||||
$ cmake ..
|
$ cmake ..
|
||||||
$ make
|
$ make
|
||||||
$ sudo make install
|
$ sudo make install
|
||||||
|
$ sudo ldconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
### Grab this Repo
|
### Grab this Repo
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.3.19"
|
__version__ = "0.4.0"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Audio Interfacing
|
# Audio Interfacing
|
||||||
import logging
|
import logging
|
||||||
import pyaudio
|
import pyaudio
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
# Global PyAudio object
|
# Global PyAudio object
|
||||||
|
@ -125,8 +126,14 @@ class AudioStream(object):
|
||||||
self.modem = modem
|
self.modem = modem
|
||||||
self.stats_callback = stats_callback
|
self.stats_callback = stats_callback
|
||||||
|
|
||||||
# Start audio stream
|
self.audio_thread_running = True
|
||||||
|
|
||||||
self.audio = pyaudio.PyAudio()
|
self.audio = pyaudio.PyAudio()
|
||||||
|
|
||||||
|
def start_stream(self, info_callback=None):
|
||||||
|
if info_callback:
|
||||||
|
self.stats_callback = info_callback
|
||||||
|
|
||||||
self.stream = self.audio.open(
|
self.stream = self.audio.open(
|
||||||
format=pyaudio.paInt16,
|
format=pyaudio.paInt16,
|
||||||
channels=1,
|
channels=1,
|
||||||
|
@ -138,6 +145,11 @@ class AudioStream(object):
|
||||||
stream_callback=self.handle_samples,
|
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=""):
|
def handle_samples(self, data, frame_count, time_info="", status_flags=""):
|
||||||
""" Handle incoming samples from pyaudio """
|
""" Handle incoming samples from pyaudio """
|
||||||
|
|
||||||
|
@ -151,10 +163,11 @@ class AudioStream(object):
|
||||||
# Send any stats data back to the stats callback
|
# Send any stats data back to the stats callback
|
||||||
if _stats:
|
if _stats:
|
||||||
if self.stats_callback:
|
if self.stats_callback:
|
||||||
self.stats_callback(_stats)
|
self.stats_callback.emit(_stats)
|
||||||
|
|
||||||
return (None, pyaudio.paContinue)
|
return (None, pyaudio.paContinue)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Halt stream """
|
""" Halt stream """
|
||||||
|
self.audio_thread_running = False
|
||||||
self.stream.close()
|
self.stream.close()
|
|
@ -34,9 +34,11 @@ default_config = {
|
||||||
"rotator_type": "rotctld",
|
"rotator_type": "rotctld",
|
||||||
"rotator_host": "localhost",
|
"rotator_host": "localhost",
|
||||||
"rotator_port": 4533,
|
"rotator_port": 4533,
|
||||||
|
"rotator_rangeinhibit": True,
|
||||||
"logging_enabled": False,
|
"logging_enabled": False,
|
||||||
"log_format": "CSV",
|
"log_format": "CSV",
|
||||||
"log_directory": "",
|
"log_directory": "",
|
||||||
|
"fft_smoothing": False,
|
||||||
"payload_list": json.dumps(horusdemodlib.payloads.HORUS_PAYLOAD_LIST),
|
"payload_list": json.dumps(horusdemodlib.payloads.HORUS_PAYLOAD_LIST),
|
||||||
"custom_field_list": json.dumps({})
|
"custom_field_list": json.dumps({})
|
||||||
}
|
}
|
||||||
|
@ -76,7 +78,7 @@ def read_config(widgets):
|
||||||
global qt_settings, default_config
|
global qt_settings, default_config
|
||||||
|
|
||||||
# This is getting a bit ridiculous, need to re-think this approach.
|
# 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
|
# Try and read in the version parameter from QSettings
|
||||||
if qt_settings.value("version") not in OK_VERSIONS:
|
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["rotatorTypeSelector"].setCurrentText(default_config["rotator_type"])
|
||||||
widgets["rotatorHostEntry"].setText(str(default_config["rotator_host"]))
|
widgets["rotatorHostEntry"].setText(str(default_config["rotator_host"]))
|
||||||
widgets["rotatorPortEntry"].setText(str(default_config["rotator_port"]))
|
widgets["rotatorPortEntry"].setText(str(default_config["rotator_port"]))
|
||||||
|
widgets["rotatorRangeInhibit"].setChecked(ValueToBool(default_config["rotator_rangeinhibit"]))
|
||||||
|
|
||||||
# Logging Settings
|
# Logging Settings
|
||||||
widgets["loggingPathEntry"].setText(str(default_config["log_directory"]))
|
widgets["loggingPathEntry"].setText(str(default_config["log_directory"]))
|
||||||
widgets["loggingFormatSelector"].setCurrentText(default_config["log_format"])
|
widgets["loggingFormatSelector"].setCurrentText(default_config["log_format"])
|
||||||
widgets["enableLoggingSelector"].setChecked(ValueToBool(default_config["logging_enabled"]))
|
widgets["enableLoggingSelector"].setChecked(ValueToBool(default_config["logging_enabled"]))
|
||||||
|
|
||||||
|
widgets["fftSmoothingSelector"].setChecked(ValueToBool(default_config["fft_smoothing"]))
|
||||||
|
|
||||||
if default_config['baud_rate'] != -1:
|
if default_config['baud_rate'] != -1:
|
||||||
widgets["horusModemRateSelector"].setCurrentText(str(default_config['baud_rate']))
|
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_type"] = widgets["rotatorTypeSelector"].currentText()
|
||||||
default_config["rotator_host"] = widgets["rotatorHostEntry"].text()
|
default_config["rotator_host"] = widgets["rotatorHostEntry"].text()
|
||||||
default_config["rotator_port"] = int(widgets["rotatorPortEntry"].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["logging_enabled"] = widgets["enableLoggingSelector"].isChecked()
|
||||||
default_config["log_directory"] = widgets["loggingPathEntry"].text()
|
default_config["log_directory"] = widgets["loggingPathEntry"].text()
|
||||||
default_config["log_format"] = widgets["loggingFormatSelector"].currentText()
|
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["payload_list"] = json.dumps(horusdemodlib.payloads.HORUS_PAYLOAD_LIST)
|
||||||
default_config["custom_field_list"] = json.dumps(horusdemodlib.payloads.HORUS_CUSTOM_FIELDS)
|
default_config["custom_field_list"] = json.dumps(horusdemodlib.payloads.HORUS_CUSTOM_FIELDS)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import logging
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Thread
|
#from threading import Thread
|
||||||
|
|
||||||
|
|
||||||
class FFTProcess(object):
|
class FFTProcess(object):
|
||||||
|
@ -37,8 +37,8 @@ class FFTProcess(object):
|
||||||
|
|
||||||
self.processing_thread_running = True
|
self.processing_thread_running = True
|
||||||
|
|
||||||
self.t = Thread(target=self.processing_thread)
|
#self.t = Thread(target=self.processing_thread)
|
||||||
self.t.start()
|
#self.t.start()
|
||||||
|
|
||||||
def init_window(self):
|
def init_window(self):
|
||||||
""" Initialise Window functions and FFT scales. """
|
""" Initialise Window functions and FFT scales. """
|
||||||
|
@ -74,7 +74,7 @@ class FFTProcess(object):
|
||||||
|
|
||||||
if self.callback != None:
|
if self.callback != None:
|
||||||
if self.update_counter % self.update_decimation == 0:
|
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
|
self.update_counter += 1
|
||||||
|
|
||||||
|
@ -86,7 +86,9 @@ class FFTProcess(object):
|
||||||
while len(self.sample_buffer) > self.nfft * self.sample_width:
|
while len(self.sample_buffer) > self.nfft * self.sample_width:
|
||||||
self.perform_fft()
|
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:
|
while self.processing_thread_running:
|
||||||
if self.input_queue.qsize() > 0:
|
if self.input_queue.qsize() > 0:
|
||||||
|
@ -95,6 +97,8 @@ class FFTProcess(object):
|
||||||
else:
|
else:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
logging.debug("Stopped FFT processing thread")
|
||||||
|
|
||||||
def add_samples(self, samples):
|
def add_samples(self, samples):
|
||||||
""" Add a block of samples to the input queue """
|
""" Add a block of samples to the input queue """
|
||||||
try:
|
try:
|
||||||
|
|
2126
horusgui/gui.py
2126
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 time
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
from threading import Thread
|
# from threading import Thread
|
||||||
|
|
||||||
class ROTCTLD(object):
|
class ROTCTLD(object):
|
||||||
""" rotctld (hamlib) communication class """
|
""" rotctld (hamlib) communication class """
|
||||||
|
@ -112,11 +112,11 @@ class PSTRotator(object):
|
||||||
self.poll_rate = poll_rate
|
self.poll_rate = poll_rate
|
||||||
self.azel_thread_running = True
|
self.azel_thread_running = True
|
||||||
|
|
||||||
self.t_rx = Thread(target=self.azel_rx_loop)
|
# self.t_rx = Thread(target=self.azel_rx_loop)
|
||||||
self.t_rx.start()
|
# self.t_rx.start()
|
||||||
|
|
||||||
self.t_poll = Thread(target=self.azel_poll_loop)
|
# self.t_poll = Thread(target=self.azel_poll_loop)
|
||||||
self.t_poll.start()
|
# self.t_poll.start()
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
@ -157,13 +157,13 @@ class PSTRotator(object):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def azel_poll_loop(self):
|
def azel_poll_loop(self, info_callback=None):
|
||||||
while self.azel_thread_running:
|
while self.azel_thread_running:
|
||||||
self.poll_azel()
|
self.poll_azel()
|
||||||
logging.debug("Poll sent to PSTRotator.")
|
logging.debug("Poll sent to PSTRotator.")
|
||||||
time.sleep(self.poll_rate)
|
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"""
|
""" Listen for Azimuth and Elevation reports from PSTRotator"""
|
||||||
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||||
s.settimeout(1)
|
s.settimeout(1)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# UDP Audio Source (Obtaining audio from GQRX)
|
# UDP Audio Source (Obtaining audio from GQRX)
|
||||||
import socket
|
import socket
|
||||||
import traceback
|
import traceback
|
||||||
from threading import Thread
|
#from threading import Thread
|
||||||
|
|
||||||
|
|
||||||
class UDPStream(object):
|
class UDPStream(object):
|
||||||
""" Listen for UDP Audio data from GQRX (s16, 48kHz), and pass data around to different callbacks """
|
""" 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
|
# Start audio stream
|
||||||
self.listen_thread_running = True
|
self.listen_thread_running = True
|
||||||
self.listen_thread = Thread(target=self.udp_listen_thread)
|
#self.listen_thread = Thread(target=self.udp_listen_thread)
|
||||||
self.listen_thread.start()
|
#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 """
|
""" 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 = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||||
self.s.settimeout(1)
|
self.s.settimeout(1)
|
||||||
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
@ -50,7 +54,6 @@ class UDPStream(object):
|
||||||
|
|
||||||
self.s.close()
|
self.s.close()
|
||||||
|
|
||||||
|
|
||||||
def handle_samples(self, data, frame_count, time_info="", status_flags=""):
|
def handle_samples(self, data, frame_count, time_info="", status_flags=""):
|
||||||
""" Handle incoming samples from pyaudio """
|
""" Handle incoming samples from pyaudio """
|
||||||
|
|
||||||
|
@ -64,7 +67,7 @@ class UDPStream(object):
|
||||||
# Send any stats data back to the stats callback
|
# Send any stats data back to the stats callback
|
||||||
if _stats:
|
if _stats:
|
||||||
if self.stats_callback:
|
if self.stats_callback:
|
||||||
self.stats_callback(_stats)
|
self.stats_callback.emit(_stats)
|
||||||
|
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# Useful widgets
|
# Useful widgets
|
||||||
from PyQt5 import QtWidgets
|
from PyQt6 import QtWidgets
|
||||||
|
|
||||||
# Useful class for adding horizontal lines.
|
# Useful class for adding horizontal lines.
|
||||||
class QHLine(QtWidgets.QFrame):
|
class QHLine(QtWidgets.QFrame):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(QHLine, self).__init__()
|
super(QHLine, self).__init__()
|
||||||
self.setFrameShape(QtWidgets.QFrame.HLine)
|
self.setFrameShape(QtWidgets.QFrame.Shape.HLine)
|
||||||
self.setFrameShadow(QtWidgets.QFrame.Sunken)
|
self.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
|
||||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -1,7 +1,8 @@
|
||||||
numpy
|
numpy
|
||||||
pyaudio
|
pyaudio
|
||||||
crcmod
|
crcmod
|
||||||
PyQt5
|
PyQt6
|
||||||
pyqtgraph
|
pyqtgraph
|
||||||
requests
|
requests
|
||||||
horusdemodlib>=0.3.12
|
horusdemodlib>=0.3.12
|
||||||
|
audioop-lts; python_version>='3.13'
|
Ładowanie…
Reference in New Issue