kopia lustrzana https://github.com/projecthorus/radiosonde_auto_rx
Initial iMet-4 support!
rodzic
6808aa99b9
commit
c27705c9b0
|
@ -170,7 +170,7 @@ def start_decoder(freq, sonde_type):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
freq (float): Radiosonde frequency in Hz.
|
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
|
global config, RS_PATH, exporter_functions, rs92_ephemeris
|
||||||
|
@ -368,7 +368,8 @@ def telemetry_filter(telemetry):
|
||||||
# Regex to check DFM06/09/15/17 callsigns.
|
# Regex to check DFM06/09/15/17 callsigns.
|
||||||
dfm_callsign_valid = re.match(r'DFM[01][5679]-\d{6}', _serial)
|
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
|
return True
|
||||||
else:
|
else:
|
||||||
_id_msg = "Payload ID %s is invalid." % telemetry['id']
|
_id_msg = "Payload ID %s is invalid." % telemetry['id']
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
# Copyright (C) 2018 Mark Jessop <vk5qi@rfhead.net>
|
# Copyright (C) 2018 Mark Jessop <vk5qi@rfhead.net>
|
||||||
# Released under GNU GPL v3 or later
|
# Released under GNU GPL v3 or later
|
||||||
#
|
#
|
||||||
__version__ = "20190316"
|
__version__ = "20190317-beta"
|
||||||
|
|
||||||
# Global Variables
|
# Global Variables
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,11 @@ def telemetry_to_aprs_position(sonde_data, object_name="<id>", aprs_comment="BOM
|
||||||
elif 'M10' in sonde_data['type']:
|
elif 'M10' in sonde_data['type']:
|
||||||
# Use the generated id same as dxlAPRS
|
# Use the generated id same as dxlAPRS
|
||||||
_object_name = sonde_data['dxlid']
|
_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.
|
# New Sonde types will be added in here.
|
||||||
else:
|
else:
|
||||||
# Unknown sonde type, don't know how to handle this yet.
|
# Unknown sonde type, don't know how to handle this yet.
|
||||||
|
|
|
@ -17,9 +17,10 @@ from threading import Thread
|
||||||
from types import FunctionType, MethodType
|
from types import FunctionType, MethodType
|
||||||
from .utils import AsynchronousFileReader, rtlsdr_test
|
from .utils import AsynchronousFileReader, rtlsdr_test
|
||||||
from .gps import get_ephemeris, get_almanac
|
from .gps import get_ephemeris, get_almanac
|
||||||
|
from .sonde_specific import *
|
||||||
|
|
||||||
# Global valid sonde types list.
|
# Global valid sonde types list.
|
||||||
VALID_SONDE_TYPES = ['RS92', 'RS41', 'DFM', 'M10']
|
VALID_SONDE_TYPES = ['RS92', 'RS41', 'DFM', 'M10', 'iMet']
|
||||||
|
|
||||||
|
|
||||||
class SondeDecoder(object):
|
class SondeDecoder(object):
|
||||||
|
@ -56,12 +57,13 @@ class SondeDecoder(object):
|
||||||
DECODER_OPTIONAL_FIELDS = {
|
DECODER_OPTIONAL_FIELDS = {
|
||||||
'temp' : -273.0,
|
'temp' : -273.0,
|
||||||
'humidity' : -1,
|
'humidity' : -1,
|
||||||
|
'batt' : -1,
|
||||||
'vel_h' : 0.0,
|
'vel_h' : 0.0,
|
||||||
'vel_v' : 0.0,
|
'vel_v' : 0.0,
|
||||||
'heading' : 0.0
|
'heading' : 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
VALID_SONDE_TYPES = ['RS92', 'RS41', 'DFM', 'M10']
|
VALID_SONDE_TYPES = ['RS92', 'RS41', 'DFM', 'M10', 'iMet']
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
sonde_type="None",
|
sonde_type="None",
|
||||||
|
@ -77,7 +79,9 @@ class SondeDecoder(object):
|
||||||
timeout = 180,
|
timeout = 180,
|
||||||
telem_filter = None,
|
telem_filter = None,
|
||||||
|
|
||||||
rs92_ephemeris = None):
|
rs92_ephemeris = None,
|
||||||
|
|
||||||
|
imet_location = ""):
|
||||||
""" Initialise and start a Sonde Decoder.
|
""" Initialise and start a Sonde Decoder.
|
||||||
|
|
||||||
Args:
|
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.
|
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.
|
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
|
# Thread running flag
|
||||||
self.decoder_running = True
|
self.decoder_running = True
|
||||||
|
@ -116,6 +122,7 @@ class SondeDecoder(object):
|
||||||
self.telem_filter = telem_filter
|
self.telem_filter = telem_filter
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.rs92_ephemeris = rs92_ephemeris
|
self.rs92_ephemeris = rs92_ephemeris
|
||||||
|
self.imet_location = imet_location
|
||||||
|
|
||||||
# This will become our decoder thread.
|
# This will become our decoder thread.
|
||||||
self.decoder = None
|
self.decoder = None
|
||||||
|
@ -271,6 +278,14 @@ class SondeDecoder(object):
|
||||||
# M10 decoder
|
# M10 decoder
|
||||||
decode_cmd += "./m10 -b -b2 2>/dev/null"
|
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:
|
else:
|
||||||
# Should never get here.
|
# Should never get here.
|
||||||
return None
|
return None
|
||||||
|
@ -353,7 +368,7 @@ class SondeDecoder(object):
|
||||||
|
|
||||||
# Don't even try and decode lines which don't start with a '{'
|
# 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.
|
# These may be other output from the decoder, which we shouldn't try to parse.
|
||||||
|
|
||||||
# Catch 'bad' first characters.
|
# Catch 'bad' first characters.
|
||||||
try:
|
try:
|
||||||
_first_char = data.decode('ascii')[0]
|
_first_char = data.decode('ascii')[0]
|
||||||
|
@ -391,7 +406,7 @@ class SondeDecoder(object):
|
||||||
try:
|
try:
|
||||||
_telemetry['datetime_dt'] = parse(_telemetry['datetime'])
|
_telemetry['datetime_dt'] = parse(_telemetry['datetime'])
|
||||||
except Exception as e:
|
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
|
return False
|
||||||
|
|
||||||
# Add in the sonde frequency and type fields.
|
# Add in the sonde frequency and type fields.
|
||||||
|
@ -407,6 +422,21 @@ class SondeDecoder(object):
|
||||||
if 'aux' in _telemetry:
|
if 'aux' in _telemetry:
|
||||||
_telemetry['type'] += "-Ozone"
|
_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
|
# If we have been provided a telemetry filter function, pass the telemetry data
|
||||||
# through the filter, and return the response
|
# through the filter, and return the response
|
||||||
# By default, we will assume the telemetry is OK.
|
# By default, we will assume the telemetry is OK.
|
||||||
|
|
|
@ -300,9 +300,12 @@ def detect_sonde(frequency, rs_path="./", dwell_time=10, sdr_fm='rtl_fm', device
|
||||||
elif 'M10' in _type:
|
elif 'M10' in _type:
|
||||||
logging.debug("Scanner #%s - Detected a M10 Sonde! (Score: %.2f)" % (str(device_idx), _score))
|
logging.debug("Scanner #%s - Detected a M10 Sonde! (Score: %.2f)" % (str(device_idx), _score))
|
||||||
return "M10"
|
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:
|
elif 'IMET' in _type:
|
||||||
logging.debug("Scanner #%s - Detected a iMet Sonde! (Unsupported, type %s) (Score: %.2f)" % (str(device_idx), _type, _score))
|
logging.debug("Scanner #%s - Detected a iMet Sonde! (Type %s - Unsupported) (Score: %.2f)" % (str(device_idx), _type, _score))
|
||||||
return "iMet"
|
return _type
|
||||||
elif 'LMS6' in _type:
|
elif 'LMS6' in _type:
|
||||||
logging.debug("Scanner #%s - Detected a LMS6 Sonde! (Unsupported) (Score: %.2f)" % (str(device_idx), _score))
|
logging.debug("Scanner #%s - Detected a LMS6 Sonde! (Unsupported) (Score: %.2f)" % (str(device_idx), _score))
|
||||||
return 'LMS6'
|
return 'LMS6'
|
||||||
|
|
|
@ -80,6 +80,7 @@ if __name__ == "__main__":
|
||||||
# Testing scripts for the above.
|
# Testing scripts for the above.
|
||||||
|
|
||||||
test_data = [
|
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-01T23:59:57Z"},
|
||||||
{'datetime':'23:59:58', 'frame': 50, 'freq': '402.000 MHz', 'local_dt': "2019-03-02T00:00:03Z"},
|
{'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"},
|
{'datetime':'00:00:00', 'frame': 52, 'freq': '402.000 MHz', 'local_dt': "2019-03-01T23:59:57Z"},
|
||||||
|
|
|
@ -26,7 +26,7 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
# List of binaries we check for on startup
|
# 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():
|
def check_rs_utils():
|
||||||
""" Check the required RS decoder binaries exist
|
""" Check the required RS decoder binaries exist
|
||||||
|
|
|
@ -41,6 +41,7 @@ SAMPLES = [
|
||||||
['rs92_96k_float.bin', 2400, -100, 96000], # No threshold set, as signal is continuous.
|
['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.
|
['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.
|
['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.
|
#['rsngp_96k_float.bin', 2400, -100.0, 96000] # RS92-NGP - wider bandwidth.
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -265,7 +265,7 @@ processing_type['rs92ngp_rtlfm'] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# DFM
|
# DFM
|
||||||
_fm_rate = 20000
|
_fm_rate = 15000 # Match what's in autorx.decode
|
||||||
# Calculate the necessary conversions
|
# Calculate the necessary conversions
|
||||||
_rtlfm_oversampling = 8.0 # Viproz's hacked rtl_fm oversamples by 8x.
|
_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.
|
_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"
|
'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
|
# # RS_Detect
|
||||||
# _fm_rate = 22000
|
# _fm_rate = 22000
|
||||||
# #_fm_rate = 15000
|
# #_fm_rate = 15000
|
||||||
|
|
|
@ -479,8 +479,8 @@ void print_ePTU(int pos) {
|
||||||
|
|
||||||
void print_JSON(){
|
void print_JSON(){
|
||||||
if(json_data.gps_valid && json_data.ptu_valid){
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue