Initial iMet-4 support!

pull/144/head
Mark Jessop 2019-03-17 21:58:09 +10:30
rodzic 6808aa99b9
commit c27705c9b0
10 zmienionych plików z 82 dodań i 14 usunięć

Wyświetl plik

@ -170,7 +170,7 @@ def start_decoder(freq, sonde_type):
Args:
freq (float): Radiosonde frequency in Hz.
sonde_type (str): The radiosonde type ('RS41', 'RS92', 'DFM')
sonde_type (str): The radiosonde type ('RS41', 'RS92', 'DFM', 'M10, 'iMet')
"""
global config, RS_PATH, exporter_functions, rs92_ephemeris
@ -368,7 +368,8 @@ def telemetry_filter(telemetry):
# Regex to check DFM06/09/15/17 callsigns.
dfm_callsign_valid = re.match(r'DFM[01][5679]-\d{6}', _serial)
if vaisala_callsign_valid or dfm_callsign_valid or 'M10' in telemetry['type']:
# If Vaisala or DFMs, check the callsigns are valid. If M10 or iMet, just pass it through.
if vaisala_callsign_valid or dfm_callsign_valid or ('M10' in telemetry['type']) or ('iMet' in telemetry['type']):
return True
else:
_id_msg = "Payload ID %s is invalid." % telemetry['id']

Wyświetl plik

@ -5,7 +5,7 @@
# Copyright (C) 2018 Mark Jessop <vk5qi@rfhead.net>
# Released under GNU GPL v3 or later
#
__version__ = "20190316"
__version__ = "20190317-beta"
# Global Variables

Wyświetl plik

@ -62,6 +62,11 @@ def telemetry_to_aprs_position(sonde_data, object_name="<id>", aprs_comment="BOM
elif 'M10' in sonde_data['type']:
# Use the generated id same as dxlAPRS
_object_name = sonde_data['dxlid']
elif 'iMet' in sonde_data['type']:
# Use the last 5 characters of the unique ID we have generated.
_object_name = "IMET" + sonde_data['id'][-5:]
# New Sonde types will be added in here.
else:
# Unknown sonde type, don't know how to handle this yet.

Wyświetl plik

@ -17,9 +17,10 @@ from threading import Thread
from types import FunctionType, MethodType
from .utils import AsynchronousFileReader, rtlsdr_test
from .gps import get_ephemeris, get_almanac
from .sonde_specific import *
# Global valid sonde types list.
VALID_SONDE_TYPES = ['RS92', 'RS41', 'DFM', 'M10']
VALID_SONDE_TYPES = ['RS92', 'RS41', 'DFM', 'M10', 'iMet']
class SondeDecoder(object):
@ -56,12 +57,13 @@ class SondeDecoder(object):
DECODER_OPTIONAL_FIELDS = {
'temp' : -273.0,
'humidity' : -1,
'batt' : -1,
'vel_h' : 0.0,
'vel_v' : 0.0,
'heading' : 0.0
}
VALID_SONDE_TYPES = ['RS92', 'RS41', 'DFM', 'M10']
VALID_SONDE_TYPES = ['RS92', 'RS41', 'DFM', 'M10', 'iMet']
def __init__(self,
sonde_type="None",
@ -77,7 +79,9 @@ class SondeDecoder(object):
timeout = 180,
telem_filter = None,
rs92_ephemeris = None):
rs92_ephemeris = None,
imet_location = ""):
""" Initialise and start a Sonde Decoder.
Args:
@ -98,6 +102,8 @@ class SondeDecoder(object):
not just lack-of-telemetry. This function is passed the telemetry dict, and must return a boolean based on the telemetry validity.
rs92_ephemeris (str): OPTIONAL - A fixed ephemeris file to use if decoding a RS92. If not supplied, one will be downloaded.
imet_location (str): OPTIONAL - A location field which is use in the generation of iMet unique ID.
"""
# Thread running flag
self.decoder_running = True
@ -116,6 +122,7 @@ class SondeDecoder(object):
self.telem_filter = telem_filter
self.timeout = timeout
self.rs92_ephemeris = rs92_ephemeris
self.imet_location = imet_location
# This will become our decoder thread.
self.decoder = None
@ -271,6 +278,14 @@ class SondeDecoder(object):
# M10 decoder
decode_cmd += "./m10 -b -b2 2>/dev/null"
elif self.sonde_type == "iMet":
# iMet-4 Sondes
decode_cmd = "%s %s-p %d -d %s %s-M fm -F9 -s 15k -f %d 2>/dev/null |" % (self.sdr_fm, bias_option, int(self.ppm), str(self.device_idx), gain_param, self.sonde_freq)
decode_cmd += "sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 2>/dev/null |"
# iMet-4 (IMET1RS) decoder
decode_cmd += "./imet1rs_dft --json 2>/dev/null"
else:
# Should never get here.
return None
@ -353,7 +368,7 @@ class SondeDecoder(object):
# Don't even try and decode lines which don't start with a '{'
# These may be other output from the decoder, which we shouldn't try to parse.
# Catch 'bad' first characters.
try:
_first_char = data.decode('ascii')[0]
@ -391,7 +406,7 @@ class SondeDecoder(object):
try:
_telemetry['datetime_dt'] = parse(_telemetry['datetime'])
except Exception as e:
self.log_error("Invalid date/time in telemetry dict - %s (Sonde may not have GPS lock" % str(e))
self.log_error("Invalid date/time in telemetry dict - %s (Sonde may not have GPS lock)" % str(e))
return False
# Add in the sonde frequency and type fields.
@ -407,6 +422,21 @@ class SondeDecoder(object):
if 'aux' in _telemetry:
_telemetry['type'] += "-Ozone"
# iMet Specific actions
if self.sonde_type == 'iMet':
# Check we have GPS lock.
if _telemetry['sats'] < 4:
# No GPS lock means an invalid time, which means we can't accurately calculate a unique ID.
self.log_error("iMet sonde has no GPS lock - discarding frame.")
return False
# Fix up the time.
_telemetry['datetime_dt'] = imet_fix_datetime(_telemetry['datetime'])
# Generate a unique ID based on the power-on time and frequency, as iMet sondes don't send one.
_telemetry['id'] = imet_unique_id(_telemetry, custom=self.imet_location)
# If we have been provided a telemetry filter function, pass the telemetry data
# through the filter, and return the response
# By default, we will assume the telemetry is OK.

Wyświetl plik

@ -300,9 +300,12 @@ def detect_sonde(frequency, rs_path="./", dwell_time=10, sdr_fm='rtl_fm', device
elif 'M10' in _type:
logging.debug("Scanner #%s - Detected a M10 Sonde! (Score: %.2f)" % (str(device_idx), _score))
return "M10"
elif 'IMET1RS' in _type:
logging.debug("Scanner #%s - Detected a iMet-4 Sonde! (Score: %.2f)" % (str(device_idx), _score))
return "iMet"
elif 'IMET' in _type:
logging.debug("Scanner #%s - Detected a iMet Sonde! (Unsupported, type %s) (Score: %.2f)" % (str(device_idx), _type, _score))
return "iMet"
logging.debug("Scanner #%s - Detected a iMet Sonde! (Type %s - Unsupported) (Score: %.2f)" % (str(device_idx), _type, _score))
return _type
elif 'LMS6' in _type:
logging.debug("Scanner #%s - Detected a LMS6 Sonde! (Unsupported) (Score: %.2f)" % (str(device_idx), _score))
return 'LMS6'

Wyświetl plik

@ -80,6 +80,7 @@ if __name__ == "__main__":
# Testing scripts for the above.
test_data = [
{'datetime':'23:59:58', 'frame': 50, 'freq': '402.000 MHz', 'local_dt': "2019-03-01T23:59:58Z"},
{'datetime':'23:59:58', 'frame': 50, 'freq': '402.000 MHz', 'local_dt': "2019-03-01T23:59:57Z"},
{'datetime':'23:59:58', 'frame': 50, 'freq': '402.000 MHz', 'local_dt': "2019-03-02T00:00:03Z"},
{'datetime':'00:00:00', 'frame': 52, 'freq': '402.000 MHz', 'local_dt': "2019-03-01T23:59:57Z"},

Wyświetl plik

@ -26,7 +26,7 @@ except ImportError:
# List of binaries we check for on startup
REQUIRED_RS_UTILS = ['dft_detect', 'rs41ecc', 'rs92ecc', 'dfm09ecc', 'm10']
REQUIRED_RS_UTILS = ['dft_detect', 'rs41ecc', 'rs92ecc', 'dfm09ecc', 'm10', 'imet1rs_dft']
def check_rs_utils():
""" Check the required RS decoder binaries exist

Wyświetl plik

@ -41,6 +41,7 @@ SAMPLES = [
['rs92_96k_float.bin', 2400, -100, 96000], # No threshold set, as signal is continuous.
['dfm09_96k_float.bin', 2500, -100, 96000], # Weird baud rate. No threshold set, as signal is continuous.
['m10_96k_float.bin', 9616, -10.0, 96000], # Really weird baud rate.
['imet4_96k_float.bin', 1200, -10.0, 96000], # 1200 baud, but AFSK, so we expect 7-8 dB worse performance than the other sondes.
#['rsngp_96k_float.bin', 2400, -100.0, 96000] # RS92-NGP - wider bandwidth.
]

Wyświetl plik

@ -265,7 +265,7 @@ processing_type['rs92ngp_rtlfm'] = {
}
# DFM
_fm_rate = 20000
_fm_rate = 15000 # Match what's in autorx.decode
# Calculate the necessary conversions
_rtlfm_oversampling = 8.0 # Viproz's hacked rtl_fm oversamples by 8x.
_shift = -2.0*_fm_rate/_sample_fs # rtl_fm tunes 'up' by rate*2, so we need to shift the signal down by this amount.
@ -321,6 +321,33 @@ processing_type['m10_rtlfm'] = {
'files' : "./generated/m10*.bin"
}
# iMet
_fm_rate = 15000
# Calculate the necessary conversions
_rtlfm_oversampling = 8.0 # Viproz's hacked rtl_fm oversamples by 8x.
_shift = -2.0*_fm_rate/_sample_fs # rtl_fm tunes 'up' by rate*2, so we need to shift the signal down by this amount.
_resample = (_fm_rate*_rtlfm_oversampling)/_sample_fs
if _resample != 1.0:
# We will need to resample.
_resample_command = "csdr convert_f_s16 | ./tsrc - - %.4f | csdr convert_s16_f |" % _resample
_shift = (-2.0*_fm_rate)/(_sample_fs*_resample)
else:
_resample_command = ""
_demod_command = "| %s csdr shift_addition_cc %.5f 2>/dev/null | csdr convert_f_u8 |" % (_resample_command, _shift)
_demod_command += " ./rtl_fm_stdin -M fm -f 401000000 -F9 -s %d 2>/dev/null|" % (int(_fm_rate))
_demod_command += " sox -t raw -r %d -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 2>/dev/null |" % int(_fm_rate)
processing_type['imet4_rtlfm'] = {
'demod': _demod_command,
'decode': "../imet1rs_dft --json 2>/dev/null",
# Count the number of telemetry lines.
"post_process" : "| grep frame | wc -l",
'files' : "./generated/imet4*.bin"
}
# # RS_Detect
# _fm_rate = 22000
# #_fm_rate = 15000

Wyświetl plik

@ -479,8 +479,8 @@ void print_ePTU(int pos) {
void print_JSON(){
if(json_data.gps_valid && json_data.ptu_valid){
printf("{ \"frame\": %d, \"id\": \"iMet\", \"datetime\": \"%02d:%02d:%02dZ\", \"lat\": %.5f, \"lon\": %.5f, \"alt\": %d, \"sats\": %d, \"temp\":%.2f, \"humidity\":%.2f, \"pressure\":%.2f, \"batt\":%.1f}\n", json_data.frame, json_data.hour, json_data.min, json_data.sec, json_data.lat, json_data.lon, json_data.alt, json_data.sats, json_data.temp, json_data.humidity, json_data.pressure, json_data.batt);
fprintf(stdout, "{ \"frame\": %d, \"id\": \"iMet\", \"datetime\": \"%02d:%02d:%02dZ\", \"lat\": %.5f, \"lon\": %.5f, \"alt\": %d, \"sats\": %d, \"temp\":%.2f, \"humidity\":%.2f, \"pressure\":%.2f, \"batt\":%.1f}\n", json_data.frame, json_data.hour, json_data.min, json_data.sec, json_data.lat, json_data.lon, json_data.alt, json_data.sats, json_data.temp, json_data.humidity, json_data.pressure, json_data.batt);
fflush(stdout);
}
}