2020-07-18 05:35:05 +00:00
#
# HorusLib - Command-Line Uploader
#
# Python 3 check
import sys
if sys . version_info < ( 3 , 6 ) :
print ( " ERROR - This script requires Python 3.6 or newer! " )
sys . exit ( 1 )
import argparse
import codecs
import traceback
from configparser import RawConfigParser
from . habitat import *
2022-02-05 05:32:53 +00:00
from . sondehubamateur import *
2020-07-18 05:35:05 +00:00
from . decoder import decode_packet , parse_ukhas_string
from . payloads import *
from . horusudp import send_payload_summary
from . payloads import init_custom_field_list , init_payload_id_list
from . demodstats import FSKDemodStats
import horusdemodlib . payloads
2022-02-05 05:32:53 +00:00
import horusdemodlib
2020-07-18 05:35:05 +00:00
def read_config ( filename ) :
''' Read in the user configuation file. '''
user_config = {
' user_call ' : ' HORUS_RX ' ,
' ozi_udp_port ' : 55683 ,
' summary_port ' : 55672 ,
' station_lat ' : 0.0 ,
' station_lon ' : 0.0 ,
' radio_comment ' : " " ,
' antenna_comment ' : " "
}
try :
config = RawConfigParser ( )
config . read ( filename )
user_config [ ' user_call ' ] = config . get ( ' user ' , ' callsign ' )
user_config [ ' station_lat ' ] = config . getfloat ( ' user ' , ' station_lat ' )
user_config [ ' station_lon ' ] = config . getfloat ( ' user ' , ' station_lon ' )
user_config [ ' radio_comment ' ] = config . get ( ' user ' , ' radio_comment ' )
user_config [ ' antenna_comment ' ] = config . get ( ' user ' , ' antenna_comment ' )
user_config [ ' ozi_udp_port ' ] = config . getint ( ' horus_udp ' , ' ozimux_port ' )
user_config [ ' summary_port ' ] = config . getint ( ' horus_udp ' , ' summary_port ' )
return user_config
except :
traceback . print_exc ( )
logging . error ( " Could not parse config file, exiting. Have you copied user.cfg.example to user.cfg? " )
return None
def main ( ) :
# Read command-line arguments
parser = argparse . ArgumentParser ( description = " Project Horus Binary/RTTY Telemetry Handler " , formatter_class = argparse . ArgumentDefaultsHelpFormatter )
parser . add_argument ( ' -c ' , ' --config ' , type = str , default = ' user.cfg ' , help = " Configuration file to use. Default: user.cfg " )
parser . add_argument ( " --noupload " , action = " store_true " , default = False , help = " Disable Habitat upload. " )
parser . add_argument ( " --rtty " , action = " store_true " , default = False , help = " Expect only RTTY inputs, do not update payload lists. " )
2020-12-30 01:05:35 +00:00
parser . add_argument ( " --log " , type = str , default = " telemetry.log " , help = " Write decoded telemetry to this log file. " )
2020-07-18 05:35:05 +00:00
parser . add_argument ( " --debuglog " , type = str , default = " horusb_debug.log " , help = " Write debug log to this file. " )
parser . add_argument ( " --payload-list " , type = str , default = " payload_id_list.txt " , help = " List of known payload IDs. " )
parser . add_argument ( " --custom-fields " , type = str , default = " custom_field_list.json " , help = " List of payload Custom Fields " )
2021-08-25 11:43:44 +00:00
parser . add_argument ( " --nodownload " , action = " store_true " , default = False , help = " Do not download new lists. " )
2020-07-18 05:35:05 +00:00
# parser.add_argument("--ozimux", type=int, default=-1, help="Override user.cfg OziMux output UDP port. (NOT IMPLEMENTED)")
# parser.add_argument("--summary", type=int, default=-1, help="Override user.cfg UDP Summary output port. (NOT IMPLEMENTED)")
2022-02-05 05:32:53 +00:00
parser . add_argument ( " --freq_hz " , type = float , default = None , help = " Receiver IQ centre frequency in Hz, used in determine the absolute frequency of a telemetry burst. " )
parser . add_argument ( " --freq_target_hz " , type = float , default = None , help = " Receiver ' target ' frequency in Hz, used to add metadata to station position info. " )
2022-09-17 09:51:21 +00:00
parser . add_argument ( " --baud_rate " , type = int , default = None , help = " Modulation baud rate (Hz), used to add additional metadata info. " )
2020-07-18 05:35:05 +00:00
parser . add_argument ( " -v " , " --verbose " , action = " store_true " , default = False , help = " Verbose output (set logging level to DEBUG) " )
args = parser . parse_args ( )
if args . verbose :
logging_level = logging . DEBUG
else :
logging_level = logging . INFO
# Set up logging
logging . basicConfig ( format = " %(asctime)s %(levelname)s : %(message)s " , level = logging_level )
# Read in the configuration file.
user_config = read_config ( args . config )
# If we could not read the configuration file, exit.
if user_config == None :
logging . critical ( f " Could not load { args . config } , exiting... " )
sys . exit ( 1 )
2020-12-23 09:30:29 +00:00
if args . log != " none " :
_logfile = open ( args . log , ' a ' )
logging . info ( f " Opened log file { args . log } . " )
else :
_logfile = None
2023-07-28 12:00:06 +00:00
# Some variables to handle re-downloading of payload ID lists.
min_download_time = 30 * 60 # Only try and download new payload ID / custom field lists every 30 min.
next_download_time = time . time ( )
2020-07-18 05:35:05 +00:00
if args . rtty == False :
2021-08-25 11:43:44 +00:00
if args . nodownload :
logging . info ( " Using local lists. " )
horusdemodlib . payloads . HORUS_PAYLOAD_LIST = read_payload_list ( filename = args . payload_list )
horusdemodlib . payloads . HORUS_CUSTOM_FIELDS = read_custom_field_list ( filename = args . custom_fields )
else :
2023-07-28 12:00:06 +00:00
# Download
2021-08-25 11:43:44 +00:00
horusdemodlib . payloads . HORUS_PAYLOAD_LIST = init_payload_id_list ( filename = args . payload_list )
horusdemodlib . payloads . HORUS_CUSTOM_FIELDS = init_custom_field_list ( filename = args . custom_fields )
2020-07-18 05:35:05 +00:00
logging . info ( f " Payload list contains { len ( list ( horusdemodlib . payloads . HORUS_PAYLOAD_LIST . keys ( ) ) ) } entries. " )
logging . info ( f " Custom Field list contains { len ( list ( horusdemodlib . payloads . HORUS_CUSTOM_FIELDS . keys ( ) ) ) } entries. " )
# Start the Habitat uploader thread.
2022-02-05 05:32:53 +00:00
if args . freq_target_hz :
_listener_freq_str = f " ( { args . freq_target_hz / 1e6 : .3f } MHz) "
else :
_listener_freq_str = " "
if user_config [ ' station_lat ' ] == 0.0 and user_config [ ' station_lon ' ] == 0.0 :
_sondehub_user_pos = None
else :
_sondehub_user_pos = [ user_config [ ' station_lat ' ] , user_config [ ' station_lon ' ] , 0.0 ]
sondehub_uploader = SondehubAmateurUploader (
upload_rate = 2 ,
user_callsign = user_config [ ' user_call ' ] ,
user_position = _sondehub_user_pos ,
2023-07-28 12:00:06 +00:00
user_radio = user_config [ ' radio_comment ' ] + _listener_freq_str ,
2022-02-05 05:32:53 +00:00
user_antenna = user_config [ ' antenna_comment ' ] ,
software_name = " horusdemodlib " ,
software_version = horusdemodlib . __version__ ,
inhibit = args . noupload
)
2020-07-18 05:35:05 +00:00
logging . info ( " Using User Callsign: %s " % user_config [ ' user_call ' ] )
2022-02-05 05:32:53 +00:00
demod_stats = FSKDemodStats ( peak_hold = True )
2020-07-18 05:35:05 +00:00
logging . info ( " Started Horus Demod Uploader. Hit CTRL-C to exit. " )
# Main loop
try :
while True :
# Read lines in from stdin, and strip off any trailing newlines
data = sys . stdin . readline ( )
if ( data == ' ' ) :
# Empty line means stdin has been closed.
2022-06-26 01:02:53 +00:00
logging . critical ( " Caught EOF (rtl_fm / horus_demod processes have exited, maybe because there ' s no RTLSDR?), exiting. " )
2020-07-18 05:35:05 +00:00
break
# Otherwise, strip any newlines, and continue.
data = data . rstrip ( )
# If the line of data starts with '$$', we assume it is a UKHAS-standard ASCII telemetry sentence.
# Otherwise, we assume it is a string of hexadecimal bytes, and attempt to parse it as a binary telemetry packet.
if data . startswith ( ' $$ ' ) :
# RTTY packet handling.
# Attempt to extract fields from it:
logging . info ( f " Received raw RTTY packet: { data } " )
try :
_decoded = parse_ukhas_string ( data )
# If we get here, the string is valid!
# Add in SNR data.
_snr = demod_stats . snr
_decoded [ ' snr ' ] = _snr
2022-02-05 05:32:53 +00:00
# Add in frequency estimate, if we have been supplied a receiver frequency.
if args . freq_hz :
_decoded [ ' f_centre ' ] = int ( demod_stats . fest_mean ) + int ( args . freq_hz )
2022-12-18 06:44:22 +00:00
#habitat_uploader.last_freq_hz = _decoded['f_centre']
2022-02-05 05:32:53 +00:00
2022-09-17 09:51:21 +00:00
# Add in baud rate, if provided.
if args . baud_rate :
_decoded [ ' baud_rate ' ] = int ( args . baud_rate )
2020-07-18 05:35:05 +00:00
# Send via UDP
send_payload_summary ( _decoded , port = user_config [ ' summary_port ' ] )
# Upload the string to Habitat
2022-12-18 06:44:22 +00:00
#_decoded_str = "$$" + data.split('$')[-1] + '\n'
#habitat_uploader.add(_decoded_str)
2020-07-18 05:35:05 +00:00
2022-02-05 05:32:53 +00:00
# Upload the string to Sondehub Amateur
sondehub_uploader . add ( _decoded )
2020-12-23 09:30:29 +00:00
if _logfile :
_logfile . write ( _decoded_str )
_logfile . flush ( )
2020-07-18 05:35:05 +00:00
logging . info ( f " Decoded String (SNR { demod_stats . snr : .1f } dB): { _decoded_str [ : - 1 ] } " )
except Exception as e :
logging . error ( f " Decode Failed: { str ( e ) } " )
elif data . startswith ( ' { ' ) :
# Possibly a line of modem statistics, attempt to decode it.
demod_stats . update ( data )
else :
# Handle binary packets
logging . info ( f " Received raw binary packet: { data } " )
try :
_binary_string = codecs . decode ( data , ' hex ' )
except TypeError as e :
logging . error ( " Error parsing line as hexadecimal ( %s ): %s " % ( str ( e ) , data ) )
continue
try :
_decoded = decode_packet ( _binary_string )
# If we get here, we have a valid packet!
2023-07-28 12:00:06 +00:00
if ( _decoded [ ' callsign ' ] == " UNKNOWN_PAYLOAD_ID " ) and not args . nodownload :
# We haven't seen this payload ID. Our payload ID list might be out of date.
if time . time ( ) > next_download_time :
logging . info ( " Observed unknown Payload ID, attempting to re-download lists. " )
# Download lists.
horusdemodlib . payloads . HORUS_PAYLOAD_LIST = init_payload_id_list ( filename = args . payload_list )
horusdemodlib . payloads . HORUS_CUSTOM_FIELDS = init_custom_field_list ( filename = args . custom_fields )
# Update next_download_time so we don't re-attempt to download with every new packet.
next_download_time = time . time ( ) + min_download_time
# Re-attempt to decode the packet.
_decoded = decode_packet ( _binary_string )
if _decoded [ ' callsign ' ] != " UNKNOWN_PAYLOAD_ID " :
logging . info ( f " Payload found in new payload ID list - { _decoded [ ' callsign ' ] } " )
2020-07-18 05:35:05 +00:00
# Add in SNR data.
_snr = demod_stats . snr
_decoded [ ' snr ' ] = _snr
2022-02-05 05:32:53 +00:00
# Add in frequency estimate, if we have been supplied a receiver frequency.
if args . freq_hz :
_decoded [ ' f_centre ' ] = int ( demod_stats . fest_mean ) + int ( args . freq_hz )
2022-12-18 06:44:22 +00:00
#habitat_uploader.last_freq_hz = _decoded['f_centre']
2022-02-05 05:32:53 +00:00
2022-09-17 09:51:21 +00:00
# Add in baud rate, if provided.
if args . baud_rate :
_decoded [ ' baud_rate ' ] = int ( args . baud_rate )
2020-07-18 05:35:05 +00:00
# Send via UDP
send_payload_summary ( _decoded , port = user_config [ ' summary_port ' ] )
2022-09-17 09:51:21 +00:00
# Do not upload Horus Binary packets to the Habitat endpoint.
# habitat_uploader.add(_decoded['ukhas_str']+'\n')
2020-07-18 05:35:05 +00:00
2022-02-05 05:32:53 +00:00
# Upload the string to Sondehub Amateur
sondehub_uploader . add ( _decoded )
2020-12-23 09:30:29 +00:00
if _logfile :
_logfile . write ( _decoded [ ' ukhas_str ' ] + ' \n ' )
_logfile . flush ( )
2020-07-18 05:35:05 +00:00
logging . info ( f " Decoded Binary Packet (SNR { demod_stats . snr : .1f } dB): { _decoded [ ' ukhas_str ' ] } " )
2023-07-08 00:51:49 +00:00
# Remove a few fields from the packet before printing.
_temp_packet = _decoded . copy ( )
_temp_packet . pop ( ' packet_format ' )
_temp_packet . pop ( ' ukhas_str ' )
logging . debug ( f " Binary Packet Contents: { _temp_packet } " )
2020-07-18 05:35:05 +00:00
except Exception as e :
logging . error ( f " Decode Failed: { str ( e ) } " )
except KeyboardInterrupt :
logging . info ( " Caught CTRL-C, exiting. " )
2022-12-18 06:44:22 +00:00
#habitat_uploader.close()
2022-06-26 01:02:53 +00:00
sondehub_uploader . close ( )
2020-07-18 05:35:05 +00:00
if __name__ == " __main__ " :
main ( )