Initial commit of horuslib

pull/13/head
Mark Jessop 2020-06-29 17:36:16 +09:30
rodzic 671fb39454
commit 2b5fa0a13e
5 zmienionych plików z 432 dodań i 144 usunięć

Wyświetl plik

@ -41,9 +41,5 @@ if __name__ == "__main__":
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
entry_points={
"console_scripts": [
"horus-gui=horusgui.gui",
]
}
entry_points={"console_scripts": ["horus-gui=horusgui.gui",]},
)

Wyświetl plik

@ -164,7 +164,7 @@ w1_habitat.addWidget(widgets["userAntennaEntry"], 3, 1, 1, 2)
w1_habitat.addWidget(widgets["userRadioLabel"], 4, 0, 1, 1)
w1_habitat.addWidget(widgets["userRadioEntry"], 4, 1, 1, 2)
w1_habitat.addWidget(widgets["habitatUploadPosition"], 5, 0, 1, 3)
w1_habitat.layout.setRowStretch(6,1)
w1_habitat.layout.setRowStretch(6, 1)
d0_habitat.addWidget(w1_habitat)
@ -180,7 +180,7 @@ w1_other.addWidget(widgets["horusUploadLabel"], 0, 0, 1, 1)
w1_other.addWidget(widgets["horusUploadSelector"], 0, 1, 1, 1)
w1_other.addWidget(widgets["horusUDPLabel"], 1, 0, 1, 1)
w1_other.addWidget(widgets["horusUDPEntry"], 1, 1, 1, 1)
w1_other.layout.setRowStretch(5,1)
w1_other.layout.setRowStretch(5, 1)
d0_other.addWidget(w1_other)
@ -295,13 +295,14 @@ read_config(widgets)
# Start Habitat Uploader
habitat_uploader = HabitatUploader(
user_callsign = widgets["userCallEntry"].text(),
listener_lat = widgets["userLatEntry"].text(),
listener_lon = widgets["userLonEntry"].text(),
listener_radio = widgets["userRadioEntry"].text(),
listener_antenna = widgets["userAntennaEntry"].text()
user_callsign=widgets["userCallEntry"].text(),
listener_lat=widgets["userLatEntry"].text(),
listener_lon=widgets["userLonEntry"].text(),
listener_radio=widgets["userRadioEntry"].text(),
listener_antenna=widgets["userAntennaEntry"].text(),
)
def habitat_position_reupload():
""" Trigger a re-upload of user position information """
global widgets, habitat_uploader
@ -313,16 +314,17 @@ def habitat_position_reupload():
habitat_uploader.listener_antenna = widgets["userAntennaEntry"].text()
habitat_uploader.trigger_position_upload()
widgets["habitatUploadPosition"].clicked.connect(habitat_position_reupload)
def habitat_inhibit():
""" Update the Habitat inhibit flag """
global widgets, habitat_uploader
habitat_uploader.inhibit = not widgets[
"habitatUploadSelector"
].isChecked()
habitat_uploader.inhibit = not widgets["habitatUploadSelector"].isChecked()
logging.debug(f"Updated Habitat Inhibit state: {habitat_uploader.inhibit}")
widgets["habitatUploadSelector"].clicked.connect(habitat_inhibit)
@ -369,8 +371,8 @@ def start_decoding():
# TODO: Grab horus data here.
# Init FFT Processor
NFFT = 2**14
STRIDE = 2**13
NFFT = 2 ** 14
STRIDE = 2 ** 13
fft_process = FFTProcess(
nfft=NFFT, stride=STRIDE, fs=_sample_rate, callback=add_fft_update
)

Wyświetl plik

@ -18,33 +18,34 @@ 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. '''
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
@ -69,34 +70,32 @@ class HabitatUploader(object):
self.uploadthread = Thread(target=self.habitat_upload_thread)
self.uploadthread.start()
def habitat_upload(self, sentence):
''' Upload a UKHAS-standard telemetry sentence to Habitat '''
""" 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'))
_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
},
"_raw": _sentence_b64.decode(
"ascii"
) # Convert back to a string to be serialisable
},
"receivers": {
_user_call: {
"time_created": _date,
"time_uploaded": _date,
},
},
_user_call: {"time_created": _date, "time_uploaded": _date,},
},
}
# 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)
time.sleep(random.random() * self.upload_retry_interval * 2.0)
_retries = 0
@ -107,7 +106,9 @@ class HabitatUploader(object):
while _retries < self.upload_retries:
# Run the request.
try:
_req = requests.put(_url, data=json.dumps(_data), timeout=self.upload_timeout)
_req = requests.put(
_url, data=json.dumps(_data), timeout=self.upload_timeout
)
except Exception as e:
logging.error("Habitat - Upload Failed: %s" % str(e))
break
@ -120,20 +121,25 @@ class HabitatUploader(object):
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)
time.sleep(random.random() * self.upload_retry_interval)
_retries += 1
else:
logging.error("Habitat - Error uploading to Habitat. Status Code: %d." % _req.status_code)
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)
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 '''
""" Handle uploading of packets to Habitat """
logging.info("Started Habitat Uploader Thread.")
@ -145,7 +151,9 @@ class HabitatUploader(object):
while not self.habitat_upload_queue.empty():
sentence = self.habitat_upload_queue.get()
logging.warning("Habitat uploader queue was full - possible connectivity issue.")
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()
@ -161,22 +169,21 @@ class HabitatUploader(object):
# Check for 'valid' position
if (self.listener_lat != 0.0) or (self.listener_lon != 0.0):
_success = self.uploadListenerPosition(
self.user_callsign,
self.listener_lat,
self.listener_lon,
self.listener_radio,
self.listener_antenna)
self.user_callsign,
self.listener_lat,
self.listener_lon,
self.listener_radio,
self.listener_antenna,
)
# Set this flag regardless if the uplaod worked.
# The user can trigger a re-upload.
# 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 '''
""" Add a sentence to the upload queue """
if self.inhibit:
# We have upload inhibited. Return.
@ -184,64 +191,66 @@ class HabitatUploader(object):
# 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]
sentence = sentence.split("$")[-1]
# Now add the *correct* number of $$s back on.
sentence = '$$' +sentence
sentence = "$$" + sentence
if not (sentence[-1] == '\n'):
sentence += '\n'
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. '''
""" 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()
doc["_id"] = self.uuids.pop()
except IndexError:
logging.error("Habitat - Unable to post listener data - no UUIDs available.")
logging.error(
"Habitat - Unable to post listener data - no UUIDs available."
)
return False
doc['time_uploaded'] = self.ISOStringNow()
doc["time_uploaded"] = self.ISOStringNow()
try:
_r = requests.post(f"{self.HABITAT_URL}{self.HABITAT_DB}/", json=doc, timeout=timeout)
_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):
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'])
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))
logging.error(
"Habitat - Unable to fetch UUIDs, retrying in 2 seconds - %s"
% str(e)
)
time.sleep(2)
_retries = _retries - 1
continue
@ -249,17 +258,12 @@ class HabitatUploader(object):
logging.error("Habitat - Gave up trying to get UUIDs.")
return
def initListenerCallsign(self, callsign, radio='', antenna=''):
def initListenerCallsign(self, callsign, radio="", antenna=""):
doc = {
'type': 'listener_information',
'time_created' : self.ISOStringNow(),
'data': {
'callsign': callsign,
'antenna': antenna,
'radio': radio,
}
}
"type": "listener_information",
"time_created": self.ISOStringNow(),
"data": {"callsign": callsign, "antenna": antenna, "radio": radio,},
}
resp = self.postListenerData(doc)
@ -270,8 +274,7 @@ class HabitatUploader(object):
logging.error("Habitat - Unable to initialize callsign.")
return False
def uploadListenerPosition(self, callsign, lat, lon, radio='', antenna=''):
def uploadListenerPosition(self, callsign, lat, lon, radio="", antenna=""):
""" Initializer Listener Callsign, and upload Listener Position """
# Validate the lat/lon entries.
@ -290,16 +293,16 @@ class HabitatUploader(object):
return False
doc = {
'type': 'listener_telemetry',
'time_created': self.ISOStringNow(),
'data': {
'callsign': callsign,
'chase': False,
'latitude': _lat,
'longitude': _lon,
'altitude': 0,
'speed': 0,
}
"type": "listener_telemetry",
"time_created": self.ISOStringNow(),
"data": {
"callsign": callsign,
"chase": False,
"latitude": _lat,
"longitude": _lon,
"altitude": 0,
"speed": 0,
},
}
# post position to habitat
@ -310,7 +313,7 @@ class HabitatUploader(object):
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
@ -319,14 +322,16 @@ class HabitatUploader(object):
if __name__ == "__main__":
# Setup Logging
logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging.INFO)
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"
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")
@ -334,4 +339,4 @@ if __name__ == "__main__":
time.sleep(10)
habitat.trigger_position_upload()
time.sleep(5)
habitat.close()
habitat.close()

Wyświetl plik

@ -0,0 +1,281 @@
#!/usr/bin/env python3
import ctypes
from ctypes import *
import logging
import sys
from enum import Enum
import os
import logging
# TODO
# - Doc Strings
# - frame error checking
# - Modem Stats
# - demodulate should return an object with the stats
MODEM_STATS_NR_MAX = 8
MODEM_STATS_NC_MAX = 50
MODEM_STATS_ET_MAX = 8
MODEM_STATS_EYE_IND_MAX = 160
MODEM_STATS_NSPEC = 512
MODEM_STATS_MAX_F_EST = 4
class COMP(Structure):
_fields_ = [("real", c_float), ("imag", c_float)]
class MODEM_STATS(Structure): # modem_stats.h
_fields_ = [
("Nc", c_int),
("snr_est", c_float),
(
"rx_symbols",
(COMP * MODEM_STATS_NR_MAX) * (MODEM_STATS_NC_MAX + 1),
), # rx_symbols[MODEM_STATS_NR_MAX][MODEM_STATS_NC_MAX+1];
("nr", c_int),
("sync", c_int),
("foff", c_float),
("rx_timing", c_float),
("clock_offset", c_float),
("sync_metric", c_float),
(
"rx_eye",
(c_float * MODEM_STATS_ET_MAX) * MODEM_STATS_EYE_IND_MAX,
), # float rx_eye[MODEM_STATS_ET_MAX][MODEM_STATS_EYE_IND_MAX];
("neyetr", c_int),
("neyesamp", c_int),
("f_est", c_float * MODEM_STATS_MAX_F_EST),
("fft_buf", c_float * 2 * MODEM_STATS_NSPEC),
("fft_cfg", POINTER(c_ubyte)),
]
class Mode(Enum):
BINARY = 0
BINARY_V1 = 0
RTTY_7N2 = 99
BINARY_V2_256BIT = 1
BINARY_V2_128BIT = 2
class Frame:
def __init__(
self,
data: bytes,
sync: bool,
crc_pass: bool,
snr: float,
extended_stats: MODEM_STATS,
):
self.data = data
self.sync = sync
self.snr = snr
self.crc_pass = crc_pass
self.extended_stats = extended_stats
class HorusLib:
def __init__(
self,
libpath=f"",
mode=Mode.BINARY,
rate=-1,
tone_spacing=-1,
stereo_iq=False,
verbose=False,
):
"""
Parameters
----------
libpath : str
Path to libhorus
mode : Mode
horuslib.Mode.BINARY, horuslib.Mode.BINARY_V2_256BIT, horuslib.Mode.BINARY_V2_128BIT, horuslib.Mode.RTTY_7N2
rate : int
Changes the modem rate for supported modems. -1 for default
tone_spacing : int
Spacing between tones (hz) -1 for default
stereo_iq : bool
use stereo (IQ) input (quadrature)
verbose : bool
Enabled horus_set_verbose
"""
if sys.platform == "darwin":
libpath = os.path.join(libpath, "libhorus.dylib")
elif sys.platform == "win32":
libpath = os.path.join(libpath, "libhorus.dll")
else:
libpath = os.path.join(libpath, "libhorus.so")
self.c_lib = ctypes.cdll.LoadLibrary(
libpath
) # future improvement would be to try a few places / names
# horus_open_advanced
self.c_lib.horus_open_advanced.restype = POINTER(c_ubyte)
# horus_nin
self.c_lib.horus_nin.restype = c_uint32
# horus_get_Fs
self.c_lib.horus_get_Fs.restype = c_int
# horus_set_freq_est_limits - (struct horus *hstates, float fsk_lower, float fsk_upper)
self.c_lib.horus_set_freq_est_limits.argtype = [
POINTER(c_ubyte),
c_float,
c_float,
]
# horus_get_max_demod_in
self.c_lib.horus_get_max_demod_in.restype = c_int
# horus_get_max_ascii_out_len
self.c_lib.horus_get_max_ascii_out_len.restype = c_int
# horus_crc_ok
self.c_lib.horus_crc_ok.restype = c_int
# horus_get_modem_extended_stats - (struct horus *hstates, struct MODEM_STATS *stats)
self.c_lib.horus_get_modem_extended_stats.argtype = [
POINTER(MODEM_STATS),
POINTER(c_ubyte),
]
# horus_get_mFSK
self.c_lib.horus_get_mFSK.restype = c_int
# horus_rx
self.c_lib.horus_rx.restype = c_int
# struct horus *hstates, char ascii_out[], short demod_in[], int quadrature
if type(mode) != type(Mode(0)):
raise ValueError("Must be of type horuslib.Mode")
else:
self.mode = mode
self.stereo_iq = stereo_iq
# intial nin
self.nin = 0
# try to open the modem and set the verbosity
self.hstates = self.c_lib.horus_open_advanced(
self.mode.value, rate, tone_spacing
)
self.c_lib.horus_set_verbose(self.hstates, int(verbose))
# check that the modem was actually opened and we don't just have a null pointer
if bool(self.hstates):
logging.debug("Opened Horus API")
else:
logging.error("Couldn't open Horus API for some reason")
raise EnvironmentError("Couldn't open Horus API")
# build some class types to fit the data for demodulation using ctypes
max_demod_in = int(self.c_lib.horus_get_max_demod_in(self.hstates))
max_ascii_out = int(self.c_lib.horus_get_max_ascii_out_len(self.hstates))
self.DemodIn = c_short * (max_demod_in * (1 + int(self.stereo_iq)))
self.DataOut = c_char * max_ascii_out
self.c_lib.horus_rx.argtype = [
POINTER(c_ubyte),
c_char * max_ascii_out,
c_short * max_demod_in,
c_int,
]
self.mfsk = int(self.c_lib.horus_get_mFSK(self.hstates))
# in case someone wanted to use `with` style. I'm not sure if closing the modem does a lot.
def __enter__(self):
return self
def __exit__(self, *a):
self.close()
def close(self):
self.c_lib.horus_close(self.hstates)
logging.debug("Shutdown horus modem")
def update_nin(self):
new_nin = int(self.c_lib.horus_nin(self.hstates))
if self.nin != new_nin:
logging.debug(f"Updated nin {new_nin}")
self.nin = new_nin
def demodulate(self, demod_in: bytes):
# from_buffer_copy requires exact size so we pad it out.
buffer = bytearray(
len(self.DemodIn()) * sizeof(c_short)
) # create empty byte array
buffer[: len(demod_in)] = demod_in # copy across what we have
modulation = self.DemodIn # get an empty modulation array
modulation = modulation.from_buffer_copy(
buffer
) # copy buffer across and get a pointer to it.
data_out = self.DataOut() # initilize a pointer to where bytes will be outputed
self.c_lib.horus_rx(self.hstates, data_out, modulation, int(self.stereo_iq))
stats = MODEM_STATS()
self.c_lib.horus_get_modem_extended_stats(self.hstates, byref(stats))
crc = bool(self.c_lib.horus_crc_ok(self.hstates))
data_out = bytes(data_out)
self.update_nin()
# strip the null terminator out
data_out = data_out[:-1]
if data_out == bytes(len(data_out)):
data_out = (
b"" # check if bytes is just null and return an empty bytes instead
)
elif self.mode != Mode.RTTY:
try:
data_out = bytes.fromhex(data_out.decode("ascii"))
except ValueError:
logging.debug(data_out)
logging.error("💥Couldn't decode the hex from the modem")
return bytes()
else:
data_out = bytes(data_out.decode("ascii"))
frame = Frame(
data=data_out,
snr=float(stats.snr_est),
sync=bool(stats.sync),
crc_pass=crc,
extended_stats=stats,
)
return frame
if __name__ == "__main__":
import sys
filename = sys.argv[1]
# Setup Logging
logging.basicConfig(
format="%(asctime)s %(levelname)s: %(message)s", level=logging.INFO
)
with HorusLib(libpath=".", mode=Mode.BINARY, verbose=True) as horus:
with open(filename, "rb") as f:
while True:
data = f.read(horus.nin * 2)
if horus.nin != 0 and data == b"": # detect end of file
break
output = horus.demodulate(data)
if output.crc_pass and output.data:
print(f"{output.data.hex()} SNR: {output.snr}")
for x in range(horus.mfsk):
print(f"F{str(x)}: {float(output.extended_stats.f_est[x])}")

Wyświetl plik

@ -17,7 +17,7 @@ def crc16_ccitt(data):
(CRC16 CCITT: start 0xFFFF, poly 0x1021)
"""
crc16 = crcmod.predefined.mkCrcFun('crc-ccitt-false')
crc16 = crcmod.predefined.mkCrcFun("crc-ccitt-false")
return hex(crc16(data))[2:].upper().zfill(4)
@ -38,23 +38,23 @@ def decode_ukhas_sentence(sentence):
_sentence = sentence.strip()
# First, try and find the start of the sentence, which always starts with '$$''
_sentence = _sentence.split('$$')[-1]
_sentence = _sentence.split("$$")[-1]
# Hack to handle odd numbers of $$'s at the start of a sentence
if _sentence[0] == '$':
if _sentence[0] == "$":
_sentence = _sentence[1:]
# Now try and split out the telemetry from the CRC16.
_telem = _sentence.split('*')[0]
_crc = _sentence.split('*')[1]
_telem = _sentence.split("*")[0]
_crc = _sentence.split("*")[1]
# Now check if the CRC matches.
_calc_crc = crc16_ccitt(_telem.encode('ascii'))
_calc_crc = crc16_ccitt(_telem.encode("ascii"))
if _calc_crc != _crc:
logging.error("Could not parse ASCII Sentence - CRC Fail.")
return None
# We now have a valid sentence! Extract fields..
_fields = _telem.split(',')
_fields = _telem.split(",")
_callsign = _fields[0]
_time = _fields[2]
@ -84,16 +84,16 @@ def decode_ukhas_sentence(sentence):
# Produce a dict output which is compatible with send_payload_summary below
_telem = {
'callsign': _callsign,
'time': _time,
'latitude': _latitude,
'longitude': _longitude,
'altitude': _altitude,
'speed': -1,
'heading': -1,
'temp': -1,
'sats': -1,
'batt_voltage': -1
"callsign": _callsign,
"time": _time,
"latitude": _latitude,
"longitude": _longitude,
"altitude": _altitude,
"speed": -1,
"heading": -1,
"temp": -1,
"sats": -1,
"batt_voltage": -1,
}
return _telem
@ -114,30 +114,30 @@ def send_payload_summary(telemetry, port=55672, comment="Horus Binary"):
try:
# Do a few checks before sending.
if telemetry['latitude'] == 0.0 and telemetry['longitude'] == 0.0:
if telemetry["latitude"] == 0.0 and telemetry["longitude"] == 0.0:
logging.error("Horus UDP - Zero Latitude/Longitude, not sending.")
return
packet = {
'type' : 'PAYLOAD_SUMMARY',
'callsign' : telemetry['callsign'],
'latitude' : telemetry['latitude'],
'longitude' : telemetry['longitude'],
'altitude' : telemetry['altitude'],
'speed' : telemetry['speed'],
'heading': -1,
'time' : telemetry['time'],
'comment' : comment,
'temp': telemetry['temp'],
'sats': telemetry['sats'],
'batt_voltage': telemetry['batt_voltage']
"type": "PAYLOAD_SUMMARY",
"callsign": telemetry["callsign"],
"latitude": telemetry["latitude"],
"longitude": telemetry["longitude"],
"altitude": telemetry["altitude"],
"speed": telemetry["speed"],
"heading": -1,
"time": telemetry["time"],
"comment": comment,
"temp": telemetry["temp"],
"sats": telemetry["sats"],
"batt_voltage": telemetry["batt_voltage"],
}
# Set up our UDP socket
_s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
_s.settimeout(1)
# Set up socket for broadcast, and allow re-use of the address
_s.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
_s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Under OSX we also need to set SO_REUSEPORT to 1
try:
@ -146,12 +146,14 @@ def send_payload_summary(telemetry, port=55672, comment="Horus Binary"):
pass
try:
_s.sendto(json.dumps(packet).encode('ascii'), ('<broadcast>',port))
_s.sendto(json.dumps(packet).encode("ascii"), ("<broadcast>", port))
# Catch any socket errors, that may occur when attempting to send to a broadcast address
# when there is no network connected. In this case, re-try and send to localhost instead.
except socket.error as e:
logging.debug("Horus UDP - Send to broadcast address failed, sending to localhost instead.")
_s.sendto(json.dumps(packet).encode('ascii'), ('127.0.0.1', port))
logging.debug(
"Horus UDP - Send to broadcast address failed, sending to localhost instead."
)
_s.sendto(json.dumps(packet).encode("ascii"), ("127.0.0.1", port))
_s.close()
@ -163,10 +165,12 @@ if __name__ == "__main__":
# Test script for the above functions
# Setup Logging
logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging.INFO)
logging.basicConfig(
format="%(asctime)s %(levelname)s: %(message)s", level=logging.INFO
)
sentence = "$$TESTING,1,01:02:03,-34.0,138.0,1000"
crc = crc16_ccitt(sentence[2:].encode('ascii'))
crc = crc16_ccitt(sentence[2:].encode("ascii"))
sentence = sentence + "*" + crc
print("Sentence: " + sentence)