diff --git a/auto_rx/auto_rx.py b/auto_rx/auto_rx.py index 9a00150..e51f272 100644 --- a/auto_rx/auto_rx.py +++ b/auto_rx/auto_rx.py @@ -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'] diff --git a/auto_rx/autorx/__init__.py b/auto_rx/autorx/__init__.py index 0c95de2..4401fd2 100644 --- a/auto_rx/autorx/__init__.py +++ b/auto_rx/autorx/__init__.py @@ -5,7 +5,7 @@ # Copyright (C) 2018 Mark Jessop # Released under GNU GPL v3 or later # -__version__ = "20190316" +__version__ = "20190317-beta" # Global Variables diff --git a/auto_rx/autorx/aprs.py b/auto_rx/autorx/aprs.py index f5263e9..4daf433 100644 --- a/auto_rx/autorx/aprs.py +++ b/auto_rx/autorx/aprs.py @@ -62,6 +62,11 @@ def telemetry_to_aprs_position(sonde_data, object_name="", 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. diff --git a/auto_rx/autorx/decode.py b/auto_rx/autorx/decode.py index fa04030..b1dd8bb 100644 --- a/auto_rx/autorx/decode.py +++ b/auto_rx/autorx/decode.py @@ -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. diff --git a/auto_rx/autorx/scan.py b/auto_rx/autorx/scan.py index 072181f..ce121a3 100644 --- a/auto_rx/autorx/scan.py +++ b/auto_rx/autorx/scan.py @@ -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' diff --git a/auto_rx/autorx/sonde_specific.py b/auto_rx/autorx/sonde_specific.py index 78c80e1..16b7b1a 100644 --- a/auto_rx/autorx/sonde_specific.py +++ b/auto_rx/autorx/sonde_specific.py @@ -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"}, diff --git a/auto_rx/autorx/utils.py b/auto_rx/autorx/utils.py index 7b1e493..8deafc2 100644 --- a/auto_rx/autorx/utils.py +++ b/auto_rx/autorx/utils.py @@ -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 diff --git a/auto_rx/test/generate_lowsnr.py b/auto_rx/test/generate_lowsnr.py index 1c22360..2e146d1 100644 --- a/auto_rx/test/generate_lowsnr.py +++ b/auto_rx/test/generate_lowsnr.py @@ -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. ] diff --git a/auto_rx/test/test_demod.py b/auto_rx/test/test_demod.py index 719492f..0a7e2e1 100644 --- a/auto_rx/test/test_demod.py +++ b/auto_rx/test/test_demod.py @@ -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 diff --git a/imet/imet1rs_dft.c b/imet/imet1rs_dft.c index 533a218..9000277 100644 --- a/imet/imet1rs_dft.c +++ b/imet/imet1rs_dft.c @@ -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); } }