Switch iMet-1/4 decoder to IQ input.

pull/638/head
Mark Jessop 2022-04-02 20:08:13 +10:30
rodzic 7fb25745f2
commit 17894c5ecc
12 zmienionych plików z 2766 dodań i 472 usunięć

Wyświetl plik

@ -162,9 +162,13 @@ def start_scanner():
detect_dwell_time=config["detect_dwell_time"],
max_peaks=config["max_peaks"],
rs_path=RS_PATH,
sdr_power=config["sdr_power"],
sdr_fm=config["sdr_fm"],
device_idx=_device_idx,
sdr_type=config["sdr_type"],
# Network SDR Options
sdr_hostname=config["sdr_hostname"],
sdr_port=config["sdr_port"],
rtl_power_path=config["sdr_power"],
rtl_fm_path=config["sdr_fm"],
rtl_device_idx=_device_idx,
gain=autorx.sdr_list[_device_idx]["gain"],
ppm=autorx.sdr_list[_device_idx]["ppm"],
bias=autorx.sdr_list[_device_idx]["bias"],
@ -233,11 +237,11 @@ def start_decoder(freq, sonde_type):
autorx.task_list[freq]["task"] = SondeDecoder(
sonde_type=sonde_type,
sonde_freq=freq,
sdr_type = "RTLSDR", # TODO!
rs_path=RS_PATH,
# Network SDR Options (TODO)
sdr_hostname="localhost",
sdr_port=12345,
sdr_type=config["sdr_type"],
# Network SDR Options
sdr_hostname=config["sdr_hostname"],
sdr_port=config["sdr_port"],
# RTLSDR Options
rtl_fm_path=config["sdr_fm"],
rtl_device_idx=_device_idx,

Wyświetl plik

@ -17,7 +17,7 @@ except ImportError:
# MINOR - New sonde type support, other fairly big changes that may result in telemetry or config file incompatability issus.
# PATCH - Small changes, or minor feature additions.
__version__ = "1.5.11-alpha1"
__version__ = "1.5.11-alpha2"
# Global Variables

Wyświetl plik

@ -11,7 +11,7 @@ import logging
import os
import traceback
import json
from .utils import rtlsdr_test
from .sdr_wrappers import test_sdr
# Dummy initial config with some parameters we need to make the web interface happy.
global_config = {
@ -71,6 +71,9 @@ def read_auto_rx_config(filename, no_sdr_test=False):
"email_to": None,
"email_subject": "<type> Sonde launch detected on <freq>: <id>",
# SDR Settings
"sdr_type": "RTLSDR",
"sdr_hostname": "localhost",
"sdr_port": 5555,
"sdr_fm": "rtl_fm",
"sdr_power": "rtl_power",
"sdr_quantity": 1,
@ -685,45 +688,110 @@ def read_auto_rx_config(filename, no_sdr_test=False):
auto_rx_config["aprs_port"] = 14590
# 1.6.0 - New SDR options
try:
auto_rx_config["sdr_type"] = config.get("sdr", "sdr_type")
auto_rx_config["sdr_hostname"] = config.get("sdr", "sdr_hostname")
auto_rx_config["sdr_port"] = config.getint("sdr", "sdr_port")
except:
# Switch this to warning on release...
logging.debug(
"Config - Did not find sdr_type option, using defaults."
)
# If we are being called as part of a unit test, just return the config now.
if no_sdr_test:
return auto_rx_config
# Now we attempt to read in the individual SDR parameters.
# Now we enumerate our SDRs.
auto_rx_config["sdr_settings"] = {}
for _n in range(1, auto_rx_config["sdr_quantity"] + 1):
_section = "sdr_%d" % _n
try:
_device_idx = config.get(_section, "device_idx")
_ppm = round(config.getfloat(_section, "ppm"))
_gain = config.getfloat(_section, "gain")
_bias = config.getboolean(_section, "bias")
if auto_rx_config["sdr_type"] == "RTLSDR":
# Multiple RTLSDRs in use - we need to read in each SDRs settings.
for _n in range(1, auto_rx_config["sdr_quantity"] + 1):
_section = "sdr_%d" % _n
try:
_device_idx = config.get(_section, "device_idx")
_ppm = round(config.getfloat(_section, "ppm"))
_gain = config.getfloat(_section, "gain")
_bias = config.getboolean(_section, "bias")
if (auto_rx_config["sdr_quantity"] > 1) and (_device_idx == "0"):
logging.critical(
"Config - SDR Device ID of 0 used with a multi-SDR configuration. Go read the warning in the config file!"
if (auto_rx_config["sdr_quantity"] > 1) and (_device_idx == "0"):
logging.critical(
"Config - RTLSDR Device ID of 0 used with a multi-SDR configuration. Go read the warning in the config file!"
)
return None
# See if the SDR exists.
_sdr_valid = test_sdr(sdr_type = "RTLSDR", rtl_device_idx = _device_idx)
if _sdr_valid:
auto_rx_config["sdr_settings"][_device_idx] = {
"ppm": _ppm,
"gain": _gain,
"bias": _bias,
"in_use": False,
"task": None,
}
logging.info("Config - Tested RTLSDR #%s OK" % _device_idx)
else:
logging.warning("Config - RTLSDR #%s invalid." % _device_idx)
except Exception as e:
logging.error(
"Config - Error parsing RTLSDR %d config - %s" % (_n, str(e))
)
return None
continue
# See if the SDR exists.
_sdr_valid = rtlsdr_test(_device_idx)
if _sdr_valid:
auto_rx_config["sdr_settings"][_device_idx] = {
"ppm": _ppm,
"gain": _gain,
"bias": _bias,
"in_use": False,
"task": None,
}
logging.info("Config - Tested SDR #%s OK" % _device_idx)
else:
logging.warning("Config - SDR #%s invalid." % _device_idx)
except Exception as e:
logging.error(
"Config - Error parsing SDR %d config - %s" % (_n, str(e))
)
continue
elif auto_rx_config["sdr_type"] == "SpyServer":
# Test access to the SpyServer
_sdr_ok = test_sdr(
sdr_type=auto_rx_config["sdr_type"],
sdr_hostname=auto_rx_config["sdr_hostname"],
sdr_port=auto_rx_config["sdr_port"]
)
if not _sdr_ok:
logging.critical(f"Config - Could not contact SpyServer {auto_rx_config['sdr_hostname']}:{auto_rx_config['sdr_port']}. Exiting.")
return None
for _n in range(1, auto_rx_config["sdr_quantity"] + 1):
_sdr_name = f"SPY{_n:02d}"
auto_rx_config["sdr_settings"][_sdr_name] = {
"ppm": 0,
"gain": 0,
"bias": 0,
"in_use": False,
"task": None,
}
elif auto_rx_config["sdr_type"] == "KA9Q":
# Test access to the SpyServer
_sdr_ok = test_sdr(
sdr_type=auto_rx_config["sdr_type"],
sdr_hostname=auto_rx_config["sdr_hostname"],
sdr_port=auto_rx_config["sdr_port"]
)
if not _sdr_ok:
logging.critical(f"Config - Could not contact KA9Q Server {auto_rx_config['sdr_hostname']}:{auto_rx_config['sdr_port']}. Exiting.")
return None
for _n in range(1, auto_rx_config["sdr_quantity"] + 1):
_sdr_name = f"KA9Q{_n:02d}"
auto_rx_config["sdr_settings"][_sdr_name] = {
"ppm": 0,
"gain": 0,
"bias": 0,
"in_use": False,
"task": None,
}
logging.critical("Config - KA9Q SDR Support not implemented yet - exiting.")
return None
else:
logging.critical(f"Config - Unknown SDR Type {auto_rx_config['sdr_type']} - exiting.")
return None
# Sanity checks when using more than one SDR
if (len(auto_rx_config["sdr_settings"].keys()) > 1) and (

Wyświetl plik

@ -149,11 +149,9 @@ class SondeDecoder(object):
sdr_type (str): 'RTLSDR', 'Spyserver' or 'KA9Q'
Arguments for KA9Q SDR Server / SpyServer:
sdr_hostname (str): Hostname of KA9Q Server
sdr_port (int): Port number of KA9Q Server
Arguments for RTLSDRs:
rtl_fm_path (str): Path to rtl_fm, or drop-in equivalent. Defaults to 'rtl_fm'
@ -358,28 +356,23 @@ class SondeDecoder(object):
if self.sonde_type == "RS41":
# RS41 Decoder command.
# rtl_fm -p 0 -g -1 -M fm -F9 -s 15k -f 405500000 | sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - lowpass 2600 2>/dev/null | ./rs41ecc --crc --ecc --ptu
# Note: Have removed a 'highpass 20' filter from the sox line, will need to re-evaluate if adding that is useful in the future.
decode_cmd = "%s %s-p %d -d %s %s-M fm -F9 -s 15k -f %d 2>/dev/null | " % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
)
# If selected by the user, we can add a highpass filter into the sox command. This helps handle up to about 5ppm of receiver drift
# before performance becomes significantly degraded. By default this is off, as it is not required with TCXO RTLSDRs, and actually
# slightly degrades performance.
if self.rs41_drift_tweak:
_highpass = "highpass 20 "
else:
_highpass = ""
_sample_rate = 48000
_filter_bandwidth = 15000
decode_cmd += (
"sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - %slowpass 2600 2>/dev/null | "
% _highpass
decode_cmd = get_sdr_fm_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
filter_bandwidth=_filter_bandwidth,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias,
highpass = 20,
lowpass = 2600
)
# Add in tee command to save audio to disk if debugging is enabled.
@ -430,20 +423,22 @@ class SondeDecoder(object):
# No PTU data availble for RS92-NGP sondes.
_ptu_opts = "--ngp --ptu"
# Now construct the decoder command.
# rtl_fm -p 0 -g 26.0 -M fm -F9 -s 12k -f 400500000 | sox -t raw -r 12k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 lowpass 2500 2>/dev/null | ./rs92ecc -vx -v --crc --ecc --vel -e ephemeris.dat
decode_cmd = "%s %s-p %d -d %s %s-M fm -F9 -s %d -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
_rx_bw,
self.sonde_freq,
)
decode_cmd += (
"sox -t raw -r %d -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - lowpass 2500 highpass 20 2>/dev/null |"
% _rx_bw
_sample_rate = 48000
decode_cmd = get_sdr_fm_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
filter_bandwidth=_rx_bw,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias,
highpass = 20,
lowpass = 2500
)
# Add in tee command to save audio to disk if debugging is enabled.
@ -461,16 +456,23 @@ class SondeDecoder(object):
# so we don't need to specify an invert flag.
# 2019-02-27: Added the --dist flag, which should reduce bad positions a bit.
# Note: Have removed a 'highpass 20' filter from the sox line, will need to re-evaluate if adding that is useful in the future.
decode_cmd = "%s %s-p %d -d %s %s-M fm -F9 -s 15k -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
_sample_rate = 48000
_filter_bandwidth = 15000
decode_cmd = get_sdr_fm_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
filter_bandwidth=_filter_bandwidth,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias,
highpass = 20,
lowpass = 2000
)
decode_cmd += "sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 lowpass 2000 2>/dev/null |"
# Add in tee command to save audio to disk if debugging is enabled.
if self.save_decode_audio:
@ -482,15 +484,22 @@ class SondeDecoder(object):
elif self.sonde_type == "M10":
# M10 Sondes
decode_cmd = "%s %s-p %d -d %s %s-M fm -F9 -s 22k -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
_sample_rate = 48000
_filter_bandwidth = 22000
decode_cmd = get_sdr_fm_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
filter_bandwidth=_filter_bandwidth,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias,
highpass = 20
)
decode_cmd += "sox -t raw -r 22k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 2>/dev/null |"
# Add in tee command to save audio to disk if debugging is enabled.
if self.save_decode_audio:
@ -502,33 +511,42 @@ class SondeDecoder(object):
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.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
_sample_rate = 48000
decode_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
decode_cmd += "sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 2>/dev/null |"
# Add in tee command to save audio to disk if debugging is enabled.
if self.save_decode_audio:
decode_cmd += " tee decode_%s.wav |" % str(self.rtl_device_idx)
if self.save_decode_iq:
decode_cmd += " tee decode_%s.raw |" % str(self.rtl_device_idx)
# iMet-4 (IMET1RS) decoder
decode_cmd += f"./imet1rs_dft --json {self.raw_file_option} 2>/dev/null"
decode_cmd += f"./imet4iq --iq 0.0 --lpIQ --dc - {_sample_rate} 16 --json 2>/dev/null"
elif self.sonde_type == "IMET5":
# iMet-54 Sondes
decode_cmd = "%s %s-p %d -d %s %s-M raw -F9 -s 48k -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
_sample_rate = 48000
decode_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
# Add in tee command to save audio to disk if debugging is enabled.
@ -543,22 +561,27 @@ class SondeDecoder(object):
elif self.sonde_type == "MRZ":
# Meteo-Radiy MRZ Sondes
decode_cmd = "%s %s-p %d -d %s %s-M fm -F9 -s 15k -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
_sample_rate = 48000
decode_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
decode_cmd += "sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 2>/dev/null |"
# Add in tee command to save audio to disk if debugging is enabled.
if self.save_decode_audio:
decode_cmd += " tee decode_%s.wav |" % str(self.rtl_device_idx)
if self.save_decode_iq:
decode_cmd += " tee decode_IQ_%s.bin |" % str(self.rtl_device_idx)
# MRZ decoder
decode_cmd += "./mp3h1mod --auto --json --ptu 2>/dev/null"
#decode_cmd += "./mp3h1mod --auto --json --ptu 2>/dev/null"
decode_cmd += "./mp3h1mod --IQ 0.0 --lp - 48000 16 --json --ptu 2>/dev/null"
elif self.sonde_type == "MK2LMS":
# 1680 MHz LMS6 sondes, using 9600 baud MK2A-format telemetry.
@ -571,13 +594,21 @@ class SondeDecoder(object):
# mk2mod runs at ~90% CPU on a RPi 3, with rtl_fm using ~50% of another core.
# Update 2021-07-24: Updated version with speedups now taking 240 kHz BW and only using 50% of a core.
decode_cmd = "%s %s-p %d -d %s %s-M raw -s 240k -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
_baud_rate = 4800
_sample_rate = 240000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias,
fast_filter = True
)
# Add in tee command to save audio to disk if debugging is enabled.
@ -598,26 +629,23 @@ class SondeDecoder(object):
# LMS6 Decoder command.
# rtl_fm -p 0 -g -1 -M fm -F9 -s 15k -f 405500000 | sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - lowpass 2600 2>/dev/null | ./rs41ecc --crc --ecc --ptu
# Note: Have removed a 'highpass 20' filter from the sox line, will need to re-evaluate if adding that is useful in the future.
decode_cmd = "%s %s-p %d -d %s %s-M fm -F9 -s 15k -f %d 2>/dev/null | " % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
)
# If selected by the user, we can add a highpass filter into the sox command. This helps handle up to about 5ppm of receiver drift
# before performance becomes significantly degraded. By default this is off, as it is not required with TCXO RTLSDRs, and actually
# slightly degrades performance.
if self.rs41_drift_tweak:
_highpass = "highpass 20 "
else:
_highpass = ""
_sample_rate = 48000
_filter_bandwidth = 15000
decode_cmd += (
"sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - %slowpass 2600 2>/dev/null | "
% _highpass
decode_cmd = get_sdr_fm_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
filter_bandwidth=_filter_bandwidth,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias,
highpass = 20,
lowpass = 2600
)
# Add in tee command to save audio to disk if debugging is enabled.
@ -630,15 +658,22 @@ class SondeDecoder(object):
# Meisei IMS-100 Sondes
# Starting out with a 15 kHz bandwidth filter.
decode_cmd = "%s %s-p %d -d %s %s-M fm -F9 -s 15k -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
_sample_rate = 48000
_filter_bandwidth = 15000
decode_cmd = get_sdr_fm_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
filter_bandwidth=_filter_bandwidth,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias,
highpass = 20
)
decode_cmd += "sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 2>/dev/null |"
# Add in tee command to save audio to disk if debugging is enabled.
if self.save_decode_audio:
@ -684,11 +719,11 @@ class SondeDecoder(object):
# RS41 Decoder
_baud_rate = 4800
_sample_rate = _baud_rate*10 # 10x Oversampling
_sample_rate = 48000 # 10x Oversampling
# Limit FSK estimator window to roughly +/- 10 kHz
_lower = -1.0*(_sample_rate*0.20)
_upper = (_sample_rate*0.20)
_lower = -10000
_upper = 10000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
@ -749,32 +784,30 @@ class SondeDecoder(object):
else:
_rs92_gps_data = "-e %s" % self.rs92_ephemeris
if self.sonde_freq > 1000e6:
# Use a higher IQ rate for 1680 MHz sondes, at the expense of some CPU usage.
_sdr_rate = 96000
_ptu_ops = "--ngp --ptu"
else:
# On 400 MHz, use 48 khz - RS92s dont drift far enough to need any more than this.
_sdr_rate = 48000
_ptu_ops = "--ptu"
_output_rate = 48000
_baud_rate = 4800
_offset = 0.25 # Place the sonde frequency in the centre of the passband.
_lower = int(
0.025 * _sdr_rate
) # Limit the frequency estimation window to not include the passband edges.
_upper = int(0.475 * _sdr_rate)
_freq = int(self.sonde_freq - _sdr_rate * _offset)
if self.sonde_freq > 1000e6:
_sample_rate = 96000
_ptu_ops = "--ngp --ptu"
_lower = -10000
_upper = 10000
else:
_sample_rate = 48000
_ptu_ops = "--ptu"
_lower = -20000
_upper = 20000
demod_cmd = "%s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
_sdr_rate,
_freq,
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
# Add in tee command to save IQ to disk if debugging is enabled.
@ -785,7 +818,7 @@ class SondeDecoder(object):
_lower,
_upper,
_stats_rate,
_sdr_rate,
_sample_rate,
_baud_rate,
)
@ -796,28 +829,34 @@ class SondeDecoder(object):
# RS92s transmit continuously - average over the last 2 frames, and use a mean
demod_stats = FSKDemodStats(averaging_time=2.0, peak_hold=True)
self.rx_frequency = _freq
self.rx_frequency = self.sonde_freq
elif self.sonde_type == "DFM":
# DFM06/DFM09 Sondes.
# DFM06/DFM09/DFM17 Sondes.
_sdr_rate = 50000
_baud_rate = 2500
_offset = 0.25 # Place the sonde frequency in the centre of the passband.
_lower = int(
0.025 * _sdr_rate
) # Limit the frequency estimation window to not include the passband edges.
_upper = int(0.475 * _sdr_rate)
_freq = int(self.sonde_freq - _sdr_rate * _offset)
_sample_rate = 50000 # 10x Oversampling
demod_cmd = "%s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
_sdr_rate,
_freq,
# Limit FSK estimator window to roughly +/- 10 kHz
_lower = -10000
_upper = 10000
if (abs(403200000 - frequency) < 20000) and (sdr_type == "RTLSDR"):
# Narrow up the frequency estimator window if we are close to
# the 403.2 MHz RTLSDR Spur.
_lower = -8000
_upper = 8000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
# Add in tee command to save IQ to disk if debugging is enabled.
@ -828,7 +867,7 @@ class SondeDecoder(object):
_lower,
_upper,
_stats_rate,
_sdr_rate,
_sample_rate,
_baud_rate,
)
@ -843,29 +882,31 @@ class SondeDecoder(object):
# DFM sondes transmit continuously - average over the last 2 frames, and peak hold
demod_stats = FSKDemodStats(averaging_time=2.0, peak_hold=True)
self.rx_frequency = _freq
self.rx_frequency = self.sonde_freq
elif self.sonde_type == "M10":
# M10 Sondes
# These have a 'weird' baud rate, and as fsk_demod requires the input sample rate to be an integer multiple of the baud rate,
# our required sample rate is correspondingly weird!
_sdr_rate = 48080
_baud_rate = 9616
_offset = 0.25 # Place the sonde frequency in the centre of the passband.
_lower = int(
0.025 * _sdr_rate
) # Limit the frequency estimation window to not include the passband edges.
_upper = int(0.475 * _sdr_rate)
_freq = int(self.sonde_freq - _sdr_rate * _offset)
demod_cmd = "%s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
_sdr_rate,
_freq,
_baud_rate = 9616
_sample_rate = 48080
_p = 5 # Override the oversampling rate
# Limit FSK estimator window to roughly +/- 10 kHz
_lower = -10000
_upper = 10000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
# Add in tee command to save IQ to disk if debugging is enabled.
@ -873,8 +914,8 @@ class SondeDecoder(object):
demod_cmd += " tee decode_IQ_%s.bin |" % str(self.rtl_device_idx)
demod_cmd += (
"./fsk_demod --cs16 -b %d -u %d -s -p 5 --stats=%d 2 %d %d - -"
% (_lower, _upper, _stats_rate, _sdr_rate, _baud_rate)
"./fsk_demod --cs16 -b %d -u %d -s -p %d --stats=%d 2 %d %d - -"
% (_lower, _upper, _p, _stats_rate, _sample_rate, _baud_rate)
)
# M10 decoder
@ -882,28 +923,30 @@ class SondeDecoder(object):
# M10 sondes transmit in short, irregular pulses - average over the last 2 frames, and use a peak hold
demod_stats = FSKDemodStats(averaging_time=2.0, peak_hold=True)
self.rx_frequency = _freq
self.rx_frequency = self.sonde_freq
elif self.sonde_type == "M20":
# M20 Sondes
# 9600 baud.
_sdr_rate = 48000
_baud_rate = 9600
_offset = 0.25 # Place the sonde frequency in the centre of the passband.
_lower = int(
0.025 * _sdr_rate
) # Limit the frequency estimation window to not include the passband edges.
_upper = int(0.475 * _sdr_rate)
_freq = int(self.sonde_freq - _sdr_rate * _offset)
demod_cmd = "%s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
_sdr_rate,
_freq,
_baud_rate = 9600
_sample_rate = 48000
_p = 5
# Limit FSK estimator window to roughly +/- 10 kHz
_lower = -10000
_upper = 10000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
# Add in tee command to save IQ to disk if debugging is enabled.
@ -911,8 +954,8 @@ class SondeDecoder(object):
demod_cmd += " tee decode_IQ_%s.bin |" % str(self.rtl_device_idx)
demod_cmd += (
"./fsk_demod --cs16 -b %d -u %d -s -p 5 --stats=%d 2 %d %d - -"
% (_lower, _upper, _stats_rate, _sdr_rate, _baud_rate)
"./fsk_demod --cs16 -b %d -u %d -s -p %d --stats=%d 2 %d %d - -"
% (_lower, _upper, _p, _stats_rate, _sample_rate, _baud_rate)
)
# M20 decoder
@ -920,29 +963,30 @@ class SondeDecoder(object):
# M20 sondes transmit in short, irregular pulses - average over the last 2 frames, and use a peak hold
demod_stats = FSKDemodStats(averaging_time=2.0, peak_hold=True)
self.rx_frequency = _freq
self.rx_frequency = self.sonde_freq
elif self.sonde_type.startswith("LMS"):
# LMS6 (400 MHz variant) Decoder command.
_sdr_rate = 48000 # IQ rate. Lower rate = lower CPU usage, but less frequency tracking ability.
_output_rate = 48000
_baud_rate = 4800
_offset = 0.25 # Place the sonde frequency in the centre of the passband.
_lower = int(
0.025 * _sdr_rate
) # Limit the frequency estimation window to not include the passband edges.
_upper = int(0.475 * _sdr_rate)
_freq = int(self.sonde_freq - _sdr_rate * _offset)
demod_cmd = "%s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
_sdr_rate,
_freq,
_baud_rate = 4800
_sample_rate = 48000
# Limit FSK estimator window to roughly +/- 10 kHz
_lower = -10000
_upper = 10000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
# Add in tee command to save IQ to disk if debugging is enabled.
if self.save_decode_iq:
demod_cmd += " tee decode_IQ_%s.bin |" % str(self.rtl_device_idx)
@ -951,7 +995,7 @@ class SondeDecoder(object):
_lower,
_upper,
_stats_rate,
_sdr_rate,
_sample_rate,
_baud_rate,
)
@ -959,28 +1003,28 @@ class SondeDecoder(object):
# LMS sondes transmit continuously - average over the last 2 frames, and use a peak hold
demod_stats = FSKDemodStats(averaging_time=2.0, peak_hold=True)
self.rx_frequency = _freq
self.rx_frequency = self.sonde_freq
elif self.sonde_type == "IMET5":
# iMet-54 Decoder command.
_sdr_rate = 48000 # IQ rate. Lower rate = lower CPU usage, but less frequency tracking ability.
_output_rate = 48000
_baud_rate = 4800
_offset = 0.25 # Place the sonde frequency in the centre of the passband.
_lower = int(
0.025 * _sdr_rate
) # Limit the frequency estimation window to not include the passband edges.
_upper = int(0.475 * _sdr_rate)
_freq = int(self.sonde_freq - _sdr_rate * _offset)
demod_cmd = "%s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
_sdr_rate,
_freq,
_baud_rate = 4800
_sample_rate = 48000
# Limit FSK estimator window to roughly +/- 10 kHz
_lower = -10000
_upper = 10000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
# Add in tee command to save IQ to disk if debugging is enabled.
if self.save_decode_iq:
@ -990,7 +1034,7 @@ class SondeDecoder(object):
_lower,
_upper,
_stats_rate,
_sdr_rate,
_sample_rate,
_baud_rate,
)
@ -998,28 +1042,28 @@ class SondeDecoder(object):
# iMet54 sondes transmit in bursts. Use a peak hold.
demod_stats = FSKDemodStats(averaging_time=2.0, peak_hold=True)
self.rx_frequency = _freq
self.rx_frequency = self.sonde_freq
elif self.sonde_type == "MRZ":
# MRZ Sondes.
_sdr_rate = 48000
_baud_rate = 2400
_offset = 0.25 # Place the sonde frequency in the centre of the passband.
_lower = int(
0.025 * _sdr_rate
) # Limit the frequency estimation window to not include the passband edges.
_upper = int(0.475 * _sdr_rate)
_freq = int(self.sonde_freq - _sdr_rate * _offset)
_sample_rate = 48000
demod_cmd = "%s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
_sdr_rate,
_freq,
# Limit FSK estimator window to roughly +/- 10 kHz
_lower = -10000
_upper = 10000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias
)
# Add in tee command to save IQ to disk if debugging is enabled.
@ -1030,7 +1074,7 @@ class SondeDecoder(object):
_lower,
_upper,
_stats_rate,
_sdr_rate,
_sample_rate,
_baud_rate,
)
@ -1039,7 +1083,7 @@ class SondeDecoder(object):
# MRZ sondes transmit continuously - average over the last frame, and use a peak hold
demod_stats = FSKDemodStats(averaging_time=1.0, peak_hold=True)
self.rx_frequency = _freq
self.rx_frequency = self.sonde_freq
elif self.sonde_type == "MK2LMS":
# 1680 MHz LMS6 sondes, using 9600 baud MK2A-format telemetry.
@ -1051,13 +1095,20 @@ class SondeDecoder(object):
# Have scaled back sample rate to 220 kHz to again save CPU.
# mk2mod runs at ~90% CPU on a RPi 3, with rtl_fm using ~50% of another core.
demod_cmd = "%s %s-p %d -d %s %s-M raw -s 220k -f %d 2>/dev/null |" % (
self.rtl_fm_path,
bias_option,
int(self.ppm),
str(self.rtl_device_idx),
gain_param,
self.sonde_freq,
_baud_rate = 4800
_sample_rate = 220000
demod_cmd = get_sdr_iq_cmd(
sdr_type = self.sdr_type,
frequency = self.sonde_freq,
sample_rate = _sample_rate,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port,
rtl_device_idx = self.rtl_device_idx,
ppm = self.ppm,
gain = self.gain,
bias = self.bias,
fast_filter = True # Don't use -F9
)
# Add in tee command to save audio to disk if debugging is enabled.
@ -1068,6 +1119,7 @@ class SondeDecoder(object):
demod_cmd += f"./mk2mod --iq 0.0 --lpIQ --lpbw 160 --lpFM --dc --crc --json {self.raw_file_option} - 220000 16 2>/dev/null"
decode_cmd = None
demod_stats = None
self.rx_frequency = self.sonde_freq
# Settings for old decoder, which cares about FM inversion.
# if self.inverted:
# self.log_debug("Using inverted MK2A decoder.")
@ -1511,9 +1563,14 @@ class SondeDecoder(object):
Args:
line (str): Message to be logged.
"""
_sdr_name = get_sdr_name(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
logging.debug(
"Decoder #%s %s %.3f - %s"
% (str(self.rtl_device_idx), self.sonde_type, self.sonde_freq / 1e6, line)
f"Decoder ({_sdr_name}) {self.sonde_type} {self.sonde_freq/1e6:.3f} - {line}"
)
def log_info(self, line):
@ -1521,9 +1578,14 @@ class SondeDecoder(object):
Args:
line (str): Message to be logged.
"""
_sdr_name = get_sdr_name(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
logging.info(
"Decoder #%s %s %.3f - %s"
% (str(self.rtl_device_idx), self.sonde_type, self.sonde_freq / 1e6, line)
f"Decoder ({_sdr_name}) {self.sonde_type} {self.sonde_freq/1e6:.3f} - {line}"
)
def log_error(self, line):
@ -1531,9 +1593,14 @@ class SondeDecoder(object):
Args:
line (str): Message to be logged.
"""
_sdr_name = get_sdr_name(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
logging.error(
"Decoder #%s %s %.3f - %s"
% (str(self.rtl_device_idx), self.sonde_type, self.sonde_freq / 1e6, line)
f"Decoder ({_sdr_name}) {self.sonde_type} {self.sonde_freq/1e6:.3f} - {line}"
)
def log_critical(self, line):
@ -1541,9 +1608,14 @@ class SondeDecoder(object):
Args:
line (str): Message to be logged.
"""
_sdr_name = get_sdr_name(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
logging.critical(
"Decoder #%s %s %.3f - %s"
% (str(self.rtl_device_idx), self.sonde_type, self.sonde_freq / 1e6, line)
f"Decoder ({_sdr_name}) {self.sonde_type} {self.sonde_freq/1e6:.3f} - {line}"
)
def stop(self, nowait=False):

Wyświetl plik

@ -57,7 +57,7 @@ def run_rtl_power(
step,
filename="log_power.csv",
dwell=20,
sdr_power="rtl_power",
rtl_power_path="rtl_power",
device_idx=0,
ppm=0,
gain=-1,
@ -71,7 +71,7 @@ def run_rtl_power(
step (int): Search step, in Hz.
filename (str): Output results to this file. Defaults to ./log_power.csv
dwell (int): How long to average on the frequency range for.
sdr_power (str): Path to the rtl_power utility.
rtl_power_path (str): Path to the rtl_power utility.
device_idx (int or str): Device index or serial number of the RTLSDR. Defaults to 0 (the first SDR found).
ppm (int): SDR Frequency accuracy correction, in ppm.
gain (float): SDR Gain setting, in dB.
@ -109,7 +109,7 @@ def run_rtl_power(
% (
timeout_kill,
dwell + 10,
sdr_power,
rtl_power_path,
bias_option,
start,
stop,
@ -233,8 +233,11 @@ def detect_sonde(
frequency,
rs_path="./",
dwell_time=10,
sdr_fm="rtl_fm",
device_idx=0,
sdr_type="RTLSDR",
sdr_hostname="localhost",
sdr_port=5555,
rtl_fm_path="rtl_fm",
rtl_device_idx=0,
ppm=0,
gain=-1,
bias=False,
@ -247,8 +250,8 @@ def detect_sonde(
frequency (int): Frequency to perform the detection on, in Hz.
rs_path (str): Path to the RS binaries (i.e rs_detect). Defaults to ./
dwell_time (int): Timeout before giving up detection.
sdr_fm (str): Path to rtl_fm, or drop-in equivalent. Defaults to 'rtl_fm'
device_idx (int or str): Device index or serial number of the RTLSDR. Defaults to 0 (the first SDR found).
rtl_fm_path (str): Path to rtl_fm, or drop-in equivalent. Defaults to 'rtl_fm'
rtl_device_idx (int or str): Device index or serial number of the RTLSDR. Defaults to 0 (the first SDR found).
ppm (int): SDR Frequency accuracy correction, in ppm.
gain (int): SDR Gain setting, in dB. A gain setting of -1 enables the RTLSDR AGC.
bias (bool): If True, enable the bias tee on the SDR.
@ -293,7 +296,7 @@ def detect_sonde(
# Try and avoid the RTLSDR 403.2 MHz spur.
# Note that this is only goign to work if we are detecting on 403.210 or 403.190 MHz.
if abs(403200000 - frequency) < 20000:
if (abs(403200000 - frequency) < 20000) and (sdr_type == "RTLSDR"):
logging.debug("Scanner - Narrowing detection IF BW to avoid RTLSDR spur.")
_if_bw = 15
@ -315,20 +318,34 @@ def detect_sonde(
if _mode == "IQ":
# IQ decoding
# Sample source (rtl_fm, in IQ mode)
rx_test_command = (
"timeout %ds %s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |"
% (
dwell_time * 2,
sdr_fm,
bias_option,
int(ppm),
str(device_idx),
gain_param,
_iq_bw,
frequency,
)
rx_test_command = f"timeout {dwell_time * 2} "
rx_test_command += get_sdr_iq_cmd(
sdr_type=sdr_type,
frequency=frequency,
sample_rate=_iq_bw,
rtl_device_idx = rtl_device_idx,
rtl_fm_path = rtl_fm_path,
ppm = ppm,
gain = gain,
bias = bias,
sdr_hostname = sdr_hostname,
sdr_port = sdr_port
)
# rx_test_command = (
# "timeout %ds %s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |"
# % (
# dwell_time * 2,
# rtl_fm_path,
# bias_option,
# int(ppm),
# str(device_idx),
# gain_param,
# _iq_bw,
# frequency,
# )
# )
# Saving of Debug audio, if enabled,
if save_detection_audio:
rx_test_command += "tee detect_%s.raw | " % str(device_idx)
@ -345,25 +362,44 @@ def detect_sonde(
# FM decoding
# Sample Source (rtl_fm)
rx_test_command = (
"timeout %ds %s %s-p %d -d %s %s-M fm -F9 -s %d -f %d 2>/dev/null |"
% (
dwell_time * 2,
sdr_fm,
bias_option,
int(ppm),
str(device_idx),
gain_param,
_rx_bw,
frequency,
)
)
# Sample filtering
rx_test_command += (
"sox -t raw -r %d -e s -b 16 -c 1 - -r 48000 -t wav - highpass 20 2>/dev/null | "
% _rx_bw
rx_test_command = f"timeout {dwell_time * 2} "
rx_test_command += get_sdr_fm_cmd(
sdr_type=sdr_type,
frequency=frequency,
filter_bandwidth=_rx_bw,
sample_rate=48000,
highpass = 20,
lowpass = None,
rtl_device_idx = rtl_device_idx,
rtl_fm_path = rtl_fm_path,
ppm = ppm,
gain = gain,
bias = bias,
sdr_hostname = "",
sdr_port = 1234,
)
# rx_test_command = (
# "timeout %ds %s %s-p %d -d %s %s-M fm -F9 -s %d -f %d 2>/dev/null |"
# % (
# dwell_time * 2,
# rtl_fm_path,
# bias_option,
# int(ppm),
# str(device_idx),
# gain_param,
# _rx_bw,
# frequency,
# )
# )
# # Sample filtering
# rx_test_command += (
# "sox -t raw -r %d -e s -b 16 -c 1 - -r 48000 -t wav - highpass 20 2>/dev/null | "
# % _rx_bw
# )
# Saving of Debug audio, if enabled,
if save_detection_audio:
rx_test_command += "tee detect_%s.wav | " % str(device_idx)
@ -374,12 +410,18 @@ def detect_sonde(
os.path.join(rs_path, "dft_detect") + " -t %d 2>/dev/null" % dwell_time
)
_sdr_name = get_sdr_name(
sdr_type,
rtl_device_idx = rtl_device_idx,
sdr_hostname = sdr_hostname,
sdr_port = sdr_port
)
logging.debug(
"Scanner #%s - Using detection command: %s" % (str(device_idx), rx_test_command)
f"Scanner ({_sdr_name}) - Using detection command: {rx_test_command}"
)
logging.debug(
"Scanner #%s - Attempting sonde detection on %.3f MHz"
% (str(device_idx), frequency / 1e6)
f"Scanner ({_sdr_name})- Attempting sonde detection on {frequency/1e6 :.3f} MHz"
)
try:
@ -392,23 +434,21 @@ def detect_sonde(
# dft_detect returns a code of 1 if no sonde is detected.
# logging.debug("Scanner - dfm_detect return code: %s" % e.returncode)
if e.returncode == 124:
logging.error("Scanner #%s - dft_detect timed out." % str(device_idx))
raise IOError("Possible RTLSDR lockup.")
logging.error(f"Scanner ({_sdr_name}) - dft_detect timed out.")
raise IOError("Possible SDR lockup.")
elif e.returncode >= 2:
ret_output = e.output.decode("utf8")
else:
_runtime = time.time() - _start
logging.debug(
"Scanner #%s - dft_detect exited in %.1f seconds with return code %d."
% (str(device_idx), _runtime, e.returncode)
f"Scanner ({_sdr_name}) - dft_detect exited in {_runtime:.1f} seconds with return code {e.returncode}."
)
return (None, 0.0)
except Exception as e:
# Something broke when running the detection function.
logging.error(
"Scanner #%s - Error when running dft_detect - %s"
% (str(device_idx), str(e))
f"Scanner ({_sdr_name}) - Error when running dft_detect - {sdr(e)}"
)
return (None, 0.0)
@ -452,74 +492,74 @@ def detect_sonde(
if "RS41" in _type:
logging.debug(
"Scanner #%s - Detected a RS41! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a RS41! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
_sonde_type = "RS41"
elif "RS92" in _type:
logging.debug(
"Scanner #%s - Detected a RS92! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a RS92! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
_sonde_type = "RS92"
elif "DFM" in _type:
logging.debug(
"Scanner #%s - Detected a DFM Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a DFM Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
_sonde_type = "DFM"
elif "M10" in _type:
logging.debug(
"Scanner #%s - Detected a M10 Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a M10 Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
_sonde_type = "M10"
elif "M20" in _type:
logging.debug(
"Scanner #%s - Detected a M20 Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a M20 Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
_sonde_type = "M20"
elif "IMET4" in _type:
logging.debug(
"Scanner #%s - Detected a iMet-4 Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a iMet-4 Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
_sonde_type = "IMET"
elif "IMET1" in _type:
logging.debug(
"Scanner #%s - Detected a iMet Sonde! (Type %s - Unsupported) (Score: %.2f)"
% (str(device_idx), _type, _score)
"Scanner (%s) - Detected a iMet Sonde! (Type %s - Unsupported) (Score: %.2f)"
% (_sdr_name, _type, _score)
)
_sonde_type = "IMET1"
elif "IMETafsk" in _type:
logging.debug(
"Scanner #%s - Detected a iMet Sonde! (Type %s - Unsupported) (Score: %.2f)"
% (str(device_idx), _type, _score)
"Scanner (%s) - Detected a iMet Sonde! (Type %s - Unsupported) (Score: %.2f)"
% (_sdr_name, _type, _score)
)
_sonde_type = "IMET1"
elif "IMET5" in _type:
logging.debug(
"Scanner #%s - Detected a iMet-54 Sonde! (Score: %.2f)"
% (str(device_idx), _score)
"Scanner (%s) - Detected a iMet-54 Sonde! (Score: %.2f)"
% (_sdr_name, _score)
)
_sonde_type = "IMET5"
elif "LMS6" in _type:
logging.debug(
"Scanner #%s - Detected a LMS6 Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a LMS6 Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
_sonde_type = "LMS6"
elif "C34" in _type:
logging.debug(
"Scanner #%s - Detected a Meteolabor C34/C50 Sonde! (Not yet supported...) (Score: %.2f)"
% (str(device_idx), _score)
"Scanner (%s) - Detected a Meteolabor C34/C50 Sonde! (Not yet supported...) (Score: %.2f)"
% (_sdr_name, _score)
)
_sonde_type = "C34C50"
elif "MRZ" in _type:
logging.debug(
"Scanner #%s - Detected a Meteo-Radiy MRZ Sonde! (Score: %.2f)"
% (str(device_idx), _score)
"Scanner (%s) - Detected a Meteo-Radiy MRZ Sonde! (Score: %.2f)"
% (_sdr_name, _score)
)
if _score < 0:
_sonde_type = "-MRZ"
@ -528,8 +568,8 @@ def detect_sonde(
elif "MK2LMS" in _type:
logging.debug(
"Scanner #%s - Detected a 1680 MHz LMS6 Sonde (MK2A Telemetry)! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a 1680 MHz LMS6 Sonde (MK2A Telemetry)! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
if _score < 0:
_sonde_type = "-MK2LMS"
@ -538,8 +578,8 @@ def detect_sonde(
elif "MEISEI" in _type:
logging.debug(
"Scanner #%s - Detected a Meisei Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (str(device_idx), _score, _offset_est)
"Scanner (%s) - Detected a Meisei Sonde! (Score: %.2f, Offset: %.1f Hz)"
% (_sdr_name, _score, _offset_est)
)
# Not currently sure if we expect to see inverted Meisei sondes.
if _score < 0:
@ -557,7 +597,7 @@ def detect_sonde(
#
class SondeScanner(object):
"""Radiosonde Scanner
Continuously scan for radiosondes using a RTLSDR, and pass results onto a callback function
Continuously scan for radiosondes using a SDR, and pass results onto a callback function
"""
# Allow up to X consecutive scan errors before giving up.
@ -582,12 +622,18 @@ class SondeScanner(object):
max_peaks=10,
scan_check_interval=10,
rs_path="./",
sdr_power="rtl_power",
sdr_fm="rtl_fm",
device_idx=0,
sdr_type="RTLSDR",
sdr_hostname="localhost",
sdr_port=5555,
rtl_power_path="rtl_power",
rtl_fm_path="rtl_fm",
rtl_device_idx=0,
gain=-1,
ppm=0,
bias=False,
save_detection_audio=False,
temporary_block_list={},
temporary_block_time=60,
@ -617,12 +663,26 @@ class SondeScanner(object):
max_peaks (int): Maximum number of peaks to search over. Peaks are ordered by signal power before being limited to this number.
scan_check_interval (int): If we are using a only_scan list, re-check the RTLSDR works every X scan runs.
rs_path (str): Path to the RS binaries (i.e rs_detect). Defaults to ./
sdr_power (str): Path to rtl_power, or drop-in equivalent. Defaults to 'rtl_power'
sdr_fm (str): Path to rtl_fm, or drop-in equivalent. Defaults to 'rtl_fm'
sdr_type (str): 'RTLSDR', 'Spyserver' or 'KA9Q'
Arguments for KA9Q SDR Server / SpyServer:
sdr_hostname (str): Hostname of KA9Q Server
sdr_port (int): Port number of KA9Q Server
Arguments for RTLSDRs:
rtl_power_path (str): Path to rtl_power, or drop-in equivalent. Defaults to 'rtl_power'
rtl_fm_path (str): Path to rtl_fm, or drop-in equivalent. Defaults to 'rtl_fm'
rtl_device_idx (int or str): Device index or serial number of the RTLSDR. Defaults to 0 (the first SDR found).
ppm (int): SDR Frequency accuracy correction, in ppm.
gain (int): SDR Gain setting, in dB. A gain setting of -1 enables the RTLSDR AGC.
bias (bool): If True, enable the bias tee on the SDR.
device_idx (int): SDR Device index. Defaults to 0 (the first SDR found).
ppm (int): SDR Frequency accuracy correction, in ppm.
gain (int): SDR Gain setting, in dB. A gain setting of -1 enables the RTLSDR AGC.
bias (bool): If True, enable the bias tee on the SDR.
save_detection_audio (bool): Save the audio used in each detecton to detect_<device_idx>.wav
temporary_block_list (dict): A dictionary where each attribute represents a frequency that should be blocked for a set time.
temporary_block_time (int): How long (minutes) frequencies in the temporary block list should remain blocked for.
@ -648,12 +708,19 @@ class SondeScanner(object):
self.scan_delay = scan_delay
self.max_peaks = max_peaks
self.rs_path = rs_path
self.sdr_power = sdr_power
self.sdr_fm = sdr_fm
self.device_idx = device_idx
self.sdr_type = sdr_type
self.sdr_hostname = sdr_hostname
self.sdr_port = sdr_port
self.rtl_power_path = rtl_power_path
self.rtl_fm_path = rtl_fm_path
self.rtl_device_idx = rtl_device_idx
self.gain = gain
self.ppm = ppm
self.bias = bias
self.callback = callback
self.save_detection_audio = save_detection_audio
@ -680,12 +747,15 @@ class SondeScanner(object):
# This will become our scanner thread.
self.sonde_scan_thread = None
# Test if the supplied RTLSDR is working.
_rtlsdr_ok = rtlsdr_test(device_idx)
# Test if the supplied SDR is working.
_sdr_ok = test_sdr(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
# TODO: How should this error be handled?
if not _rtlsdr_ok:
self.log_error("RTLSDR #%s non-functional - exiting." % device_idx)
if not _sdr_ok:
self.sonde_scanner_running = False
self.exit_state = "FAILED SDR"
return
@ -737,11 +807,18 @@ class SondeScanner(object):
if len(self.only_scan) > 0:
self.scan_counter += 1
if (self.scan_counter % self.scan_check_interval) == 0:
self.log_debug("Performing periodic check of RTLSDR.")
_rtlsdr_ok = rtlsdr_test(self.device_idx)
if not _rtlsdr_ok:
self.log_debug("Performing periodic check of SDR.")
_sdr_ok = test_sdr(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
if not _sdr_ok:
self.log_error(
"Unrecoverable RTLSDR error. Closing scan thread."
"Unrecoverable SDR error. Closing scan thread."
)
break
@ -749,17 +826,17 @@ class SondeScanner(object):
_results = self.sonde_search()
except (IOError, ValueError) as e:
# No log file produced. Reset the RTLSDR and try again.
# No log file produced. Reset the SDR and try again.
# traceback.print_exc()
self.log_warning("RTLSDR produced no output... resetting and retrying.")
self.log_warning("SDR produced no output... resetting and retrying.")
self.error_retries += 1
# Attempt to reset the RTLSDR.
if self.device_idx == "0":
# If the device ID is 0, we assume we only have a single RTLSDR on this system.
reset_all_rtlsdrs()
else:
# Otherwise, we reset the specific RTLSDR
reset_rtlsdr_by_serial(self.device_idx)
# Attempt to reset the SDR, if possible.
reset_sdr(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
time.sleep(10)
continue
@ -802,31 +879,31 @@ class SondeScanner(object):
if len(self.only_scan) == 0:
# No only_scan frequencies provided - perform a scan.
run_rtl_power(
self.min_freq * 1e6,
self.max_freq * 1e6,
self.search_step,
filename="log_power_%s.csv" % self.device_idx,
dwell=self.scan_dwell_time,
sdr_power=self.sdr_power,
device_idx=self.device_idx,
(freq, power, step) = get_power_spectrum(
sdr_type=self.sdr_type,
frequency_start=self.min_freq * 1e6,
frequency_stop=self.max_freq * 1e6,
step=self.search_step,
integration_time=self.scan_dwell_time,
rtl_device_idx=self.rtl_device_idx,
rtl_power_path=self.rtl_power_path,
ppm=self.ppm,
gain=self.gain,
bias=self.bias,
sdr_hostname=self.sdr_hostname,
sdr_port=self.sdr_port
)
# Exit opportunity.
if self.sonde_scanner_running == False:
return []
# Read in result.
# This step will throw an IOError if the file does not exist.
(freq, power, step) = read_rtl_power("log_power_%s.csv" % self.device_idx)
# Sanity check results.
if step == 0 or len(freq) == 0 or len(power) == 0:
if step == None or len(freq) == 0 or len(power) == 0:
# Otherwise, if a file has been written but contains no data, it can indicate
# an issue with the RTLSDR. Sometimes these issues can be resolved by issuing a usb reset to the RTLSDR.
raise ValueError("Invalid Log File")
raise ValueError("Error getting PSD")
# Update the global scan result
(_freq_decimate, _power_decimate) = peak_decimation(freq / 1e6, power, 10)
@ -837,7 +914,9 @@ class SondeScanner(object):
scan_result["peak_lvl"] = []
# Rough approximation of the noise floor of the received power spectrum.
power_nf = np.mean(power)
# Switched to use a Median instead of a Mean 2022-04-02. Should remove outliers better.
power_nf = np.median(power)
logging.debug(f"Noise Floor Estimate: {power_nf:.1f} dB uncal")
# Pass the threshold data to the web client for plotting
scan_result["threshold"] = power_nf
@ -976,8 +1055,11 @@ class SondeScanner(object):
(detected, offset_est) = detect_sonde(
_freq,
sdr_fm=self.sdr_fm,
device_idx=self.device_idx,
sdr_type=self.sdr_type,
sdr_hostname=self.sdr_hostname,
sdr_port=self.sdr_port,
rtl_fm_path=self.rtl_fm_path,
rtl_device_idx=self.rtl_device_idx,
ppm=self.ppm,
gain=self.gain,
bias=self.bias,
@ -1062,28 +1144,52 @@ class SondeScanner(object):
Args:
line (str): Message to be logged.
"""
logging.debug("Scanner #%s - %s" % (self.device_idx, line))
_sdr_name = get_sdr_name(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
logging.debug(f"Scanner ({_sdr_name}) - {line}")
def log_info(self, line):
"""Helper function to log an informational message with a descriptive heading.
Args:
line (str): Message to be logged.
"""
logging.info("Scanner #%s - %s" % (self.device_idx, line))
_sdr_name = get_sdr_name(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
logging.info(f"Scanner ({_sdr_name}) - {line}")
def log_error(self, line):
"""Helper function to log an error message with a descriptive heading.
Args:
line (str): Message to be logged.
"""
logging.error("Scanner #%s - %s" % (self.device_idx, line))
_sdr_name = get_sdr_name(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
logging.error(f"Scanner ({_sdr_name}) - {line}")
def log_warning(self, line):
"""Helper function to log a warning message with a descriptive heading.
Args:
line (str): Message to be logged.
"""
logging.warning("Scanner #%s - %s" % (self.device_idx, line))
_sdr_name = get_sdr_name(
self.sdr_type,
rtl_device_idx = self.rtl_device_idx,
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
logging.warning(f"Scanner ({_sdr_name}) - {line}")
if __name__ == "__main__":

Wyświetl plik

@ -6,20 +6,25 @@
# Released under GNU GPL v3 or later
#
import logging
import os.path
import platform
import subprocess
import numpy as np
from io import StringIO
from .utils import rtlsdr_test
from .utils import rtlsdr_test, reset_rtlsdr_by_serial, reset_all_rtlsdrs
def test_sdr(
sdr_type: str,
rtl_device_idx = "0",
sdr_hostname = "",
sdr_port = 1234
sdr_port = 5555
):
"""
Test the prescence / functionality of a SDR.
sdr_type (str): 'RTLSDR', 'Spyserver' or 'KA9Q'
sdr_type (str): 'RTLSDR', 'SpyServer' or 'KA9Q'
Arguments for RTLSDRs:
rtl_device_id (str) - Device ID for a RTLSDR
@ -31,6 +36,8 @@ def test_sdr(
"""
if sdr_type == "RTLSDR":
# Use the existing rtlsdr_test code, which tries to read some samples
# from the specified RTLSDR
_ok = rtlsdr_test(rtl_device_idx)
if not _ok:
logging.error(f"RTLSDR #{rtl_device_idx} non-functional.")
@ -47,23 +54,102 @@ def test_sdr(
return _ok
elif sdr_type == "SpyServer":
# Test connectivity to a SpyServer by trying to grab some samples.
_cmd = (
f"timeout 10 " # Add a timeout, because connections to non-existing IPs seem to block.
f"./ss_iq "
f"-f 401500000 "
f"-s 48000 "
f"-r {sdr_hostname} -q {sdr_port} -n 48000 - > /dev/null"
)
logging.debug(f"SpyServer - Testing using command: {_cmd}")
try:
_output = subprocess.check_output(
_cmd, shell=True, stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError as e:
# Something went wrong...
logging.critical(
f"SpyServer ({sdr_hostname}:{sdr_port}) - ss_iq call failed with return code {e.returncode}."
)
return False
return True
else:
logging.error(f"Test SDR: Unknown SDR Type {sdr_type}")
return False
def reset_sdr(
sdr_type: str,
rtl_device_idx = "0",
sdr_hostname = "",
sdr_port = 5555
):
"""
Attempt to reset a SDR. Only used for RTLSDRs.
sdr_type (str): 'RTLSDR', 'Spyserver' or 'KA9Q'
Arguments for RTLSDRs:
rtl_device_id (str) - Device ID for a RTLSDR
Arguments for KA9Q SDR Server / SpyServer:
sdr_hostname (str): Hostname of KA9Q Server
sdr_port (int): Port number of KA9Q Server
"""
if sdr_type == "RTLSDR":
# Attempt to reset the RTLSDR.
if rtl_device_idx == "0":
# If the device ID is 0, we assume we only have a single RTLSDR on this system.
reset_all_rtlsdrs()
else:
# Otherwise, we reset the specific RTLSDR
reset_rtlsdr_by_serial(rtl_device_idx)
else:
logging.debug(f"No reset ability for SDR type {sdr_type}")
def get_sdr_name(
sdr_type: str,
rtl_device_idx = "0",
sdr_hostname = "",
sdr_port = 5555
):
if sdr_type == "RTLSDR":
return f"RTLSDR {rtl_device_idx}"
elif sdr_type == "KA9Q":
return f"KA9Q {sdr_hostname}:{sdr_port}"
elif sdr_type == "SpyServer":
return f"SpyServer {sdr_hostname}:{sdr_port}"
else:
return f"UNKNOWN {sdr_type}"
def get_sdr_iq_cmd(
sdr_type: str,
frequency: int,
sample_rate: int,
rtl_device_idx = "0",
rtl_fm_path = "rtl_fm",
fast_filter: bool = False,
ppm = 0,
gain = None,
bias = False,
sdr_hostname = "",
sdr_port = 1234,
sdr_port = 5555
):
"""
Get a command-line argument to get IQ (signed 16-bit) from a SDR
@ -79,6 +165,7 @@ def get_sdr_iq_cmd(
ppm (int): SDR Frequency accuracy correction, in ppm.
gain (int): SDR Gain setting, in dB. A gain setting of -1 enables the RTLSDR AGC.
bias (bool): If True, enable the bias tee on the SDR.
fast_filter (bool): If true, drop the -F9 higher quality filter for rtl_fm
Arguments for KA9Q SDR Server / SpyServer:
sdr_hostname (str): Hostname of KA9Q Server
@ -93,7 +180,8 @@ def get_sdr_iq_cmd(
_gain = f"-g {gain:.1f} "
_cmd = (
f"{rtl_fm_path} -M raw -F9 "
f"{rtl_fm_path} -M raw "
f"{'' if fast_filter else '-F9 '}"
f"{'-T ' if bias else ''}"
f"-p {int(ppm)} "
f"-d {str(rtl_device_idx)} "
@ -105,8 +193,19 @@ def get_sdr_iq_cmd(
return _cmd
if sdr_type == "SpyServer":
_cmd = (
f"./ss_iq "
f"-f {frequency} "
f"-s {int(sample_rate)} "
f"-r {sdr_hostname} -q {sdr_port} - |"
)
return _cmd
else:
return None
logging.critical(f"IQ Source - Unsupported SDR type {sdr_type}")
return "false |"
@ -166,11 +265,11 @@ def get_sdr_fm_cmd(
f"{_gain}"
f"-s {int(filter_bandwidth)} "
f"-f {int(frequency)} "
f" 2>/dev/null | "
f"2>/dev/null | "
)
# Add in resampling / convertion to wav using sox.
_cmd += f"sox -t raw -f {int(filter_bandwidth)} -e s -b 16 -c 1 - -r {int(sample_rate)} -b 16 -t wav - "
_cmd += f"sox -t raw -r {int(filter_bandwidth)} -e s -b 16 -c 1 - -r {int(sample_rate)} -b 16 -t wav - "
if highpass:
_cmd += f"highpass {int(highpass)} "
@ -178,16 +277,204 @@ def get_sdr_fm_cmd(
if lowpass:
_cmd += f"lowpass {int(lowpass)} "
_cmd += "- 2> /dev/null | "
_cmd += "2> /dev/null | "
return _cmd
else:
return None
logging.critical(f"FM Demod Source - Unsupported SDR type {sdr_type}")
return "false |"
def get_power_spectrum(
sdr_type: str,
frequency_start: int = 400050000,
frequency_stop: int = 403000000,
step: int = 800,
integration_time: int = 20,
rtl_device_idx = "0",
rtl_power_path = "rtl_power",
ppm = 0,
gain = None,
bias = False,
sdr_hostname = "",
sdr_port = 5555
):
"""
Get power spectral density data from a SDR.
Arguments:
sdr_type (str): 'RTLSDR', 'Spyserver' or 'KA9Q'
frequency_start (int): Start frequency for the PSD, Hz
frequency_stop (int): Stop frequency for the PSD, Hz
step (int): Requested frequency step for the PSD, Hz. May not always be honoured.
integration_time (int): Integration time in seconds.
Arguments for RTLSDRs:
rtl_device_idx (str) - Device ID for a RTLSDR
rtl_power_path (str) - Path to rtl_power. Defaults to just "rtl_power"
ppm (int): SDR Frequency accuracy correction, in ppm.
gain (int): SDR Gain setting, in dB. A gain setting of -1 enables the RTLSDR AGC.
bias (bool): If True, enable the bias tee on the SDR.
Arguments for KA9Q SDR Server / SpyServer:
sdr_hostname (str): Hostname of KA9Q Server
sdr_port (int): Port number of KA9Q Server
Returns:
(freq, power, step) Tuple
freq (np.array): Array of frequencies, in Hz
power (np.array): Array of uncalibrated power estimates, in Hz
step (float): Frequency step of the output data, in Hz.
Returns (None, None, None) if an error occurs.
"""
if sdr_type == "RTLSDR":
# Use rtl_power to obtain power spectral density data
# Create filename to output to.
_log_filename = f"log_power_{rtl_device_idx}.csv"
# If the output log file exists, remove it.
if os.path.exists(_log_filename):
os.remove(_log_filename)
# Add -k 30 option, to SIGKILL rtl_power 30 seconds after the regular timeout expires.
# Note that this only works with the GNU Coreutils version of Timeout, not the IBM version,
# which is provided with OSX (Darwin).
_platform = platform.system()
if "Darwin" in _platform:
_timeout_kill = ""
else:
_timeout_kill = "-k 30 "
_timeout_cmd = f"timeout {_timeout_kill}{integration_time+10}"
_gain = ""
if gain:
if gain >= 0:
_gain = f"-g {gain:.1f} "
_rtl_power_cmd = (
f"{_timeout_cmd} {rtl_power_path} "
f"{'-T ' if bias else ''}"
f"-p {int(ppm)} "
#f"-d {str(rtl_device_idx)} "
f"{_gain}"
f"-f {frequency_start}:{frequency_stop}:{step} "
f"-i {integration_time} -1 -c 20% "
f"{_log_filename}"
)
_sdr_name = get_sdr_name(
sdr_type=sdr_type,
rtl_device_idx=rtl_device_idx,
sdr_hostname=sdr_hostname,
sdr_port=sdr_port
)
logging.info(f"Scanner ({_sdr_name}) - Running frequency scan.")
logging.debug(
f"Scanner ({_sdr_name}) - Running command: {_rtl_power_cmd}"
)
try:
_output = subprocess.check_output(
_rtl_power_cmd, shell=True, stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError as e:
# Something went wrong...
logging.critical(
f"Scanner ({_sdr_name}) - rtl_power call failed with return code {e.returncode}."
)
# Look at the error output in a bit more details.
_output = e.output.decode("ascii")
if "No supported devices found" in _output:
logging.critical(
f"Scanner ({_sdr_name}) - rtl_power could not find device with ID {rtl_device_idx}, is your configuration correct?"
)
elif "illegal option" in _output:
if bias:
logging.critical(
f"Scanner ({_sdr_name}) - rtl_power reported an illegal option was used. Are you using a rtl_power version with bias tee support?"
)
else:
logging.critical(
f"Scanner ({_sdr_name}) - rtl_power reported an illegal option was used. (This shouldn't happen... are you running an ancient version?)"
)
else:
# Something else odd happened, dump the entire error output to the log for further analysis.
logging.critical(
f"Scanner ({_sdr_name}) - rtl_power reported error: {_output}"
)
return (None, None, None)
# OK, now try to read in the saved data.
# Output buffers.
freq = np.array([])
power = np.array([])
freq_step = 0
# Open file.
f = open(_log_filename, "r")
# rtl_power log files are csv's, with the first 6 fields in each line describing the time and frequency scan parameters
# for the remaining fields, which contain the power samples.
for line in f:
# Split line into fields.
fields = line.split(",")
if len(fields) < 6:
logging.error(
f"Scanner ({_sdr_name}) - Invalid number of samples in input file - corrupt?"
)
raise Exception(
f"Scanner ({_sdr_name}) - Invalid number of samples in input file - corrupt?"
)
start_date = fields[0]
start_time = fields[1]
start_freq = float(fields[2])
stop_freq = float(fields[3])
freq_step = float(fields[4])
n_samples = int(fields[5])
# freq_range = np.arange(start_freq,stop_freq,freq_step)
samples = np.loadtxt(StringIO(",".join(fields[6:])), delimiter=",")
freq_range = np.linspace(start_freq, stop_freq, len(samples))
# Add frequency range and samples to output buffers.
freq = np.append(freq, freq_range)
power = np.append(power, samples)
f.close()
# Sanitize power values, to remove the nan's that rtl_power puts in there occasionally.
power = np.nan_to_num(power)
return (freq, power, freq_step)
else:
# Unsupported SDR Type
logging.critical(f"Get PSD - Unsupported SDR Type: {sdr_type}")
return (None, None, None)
if __name__ == "__main__":
logging.basicConfig(
format="%(asctime)s %(levelname)s:%(message)s", level=logging.DEBUG
)
_sdr_type = "RTLSDR"
#print(f"Test RTLSDR 0: {test_sdr(_sdr_type)}")
@ -202,4 +489,11 @@ if __name__ == "__main__":
print(f"RTLSDR IQ (Fixed Gain): {get_sdr_iq_cmd(_sdr_type, _freq, _sample_rate, gain=30.0, bias=True)}")
print(f"RTLSDR FM (AGC): {get_sdr_fm_cmd(_sdr_type, _freq, _fm_bw, _sample_rate)}")
print(f"RTLSDR FM (Fixed Gain): {get_sdr_fm_cmd(_sdr_type, _freq, _fm_bw, _sample_rate, gain=30.0, bias=True, highpass=20)}")
print(f"RTLSDR FM (Fixed Gain): {get_sdr_fm_cmd(_sdr_type, _freq, _fm_bw, _sample_rate, gain=30.0, bias=True, highpass=20)}")
(freq, power, step) = get_power_spectrum(
sdr_type="RTLSDR"
)
print(freq)
print(power)
print(step)

Wyświetl plik

@ -37,7 +37,6 @@ REQUIRED_RS_UTILS = [
"dft_detect",
"dfm09mod",
"m10mod",
"imet1rs_dft",
"rs41mod",
"rs92mod",
"fsk_demod",
@ -47,6 +46,7 @@ REQUIRED_RS_UTILS = [
"imet54mod",
"mp3h1mod",
"m20mod",
"imet4iq"
]

Wyświetl plik

@ -54,6 +54,7 @@ echo "Building iMet-4 demod."
cd ../imet/
# Note -O3 flag removed from this demodulator until Bus Error bug can be resolved.
gcc imet1rs_dft.c -lm -Ofast -o imet1rs_dft $VERS_FLAG
gcc imet4iq.c -lm -Ofast -o imet4iq $VERS_FLAG
echo "Building fsk-demod utils from codec2"
cd ../utils/
@ -63,23 +64,26 @@ gcc fsk_demod.c fsk.c modem_stats.c kiss_fftr.c kiss_fft.c -lm -O3 -o fsk_demod
#gcc tsrc.c -o tsrc -lm -lsamplerate
# If running under OSX and using MacPorts, you may need to uncomment the following line to be able to find libsamplerate.
#gcc tsrc.c -o tsrc -lm -lsamplerate -I/opt/local/include -L/opt/local/lib
# ... and for homebrew users.
#gcc tsrc.c -o tsrc -lm -lsamplerate -I/opt/homebrew/include -L/opt/homebrew/lib
# Copy all necessary files into this directory.
echo "Copying files into auto_rx directory."
cd ../auto_rx/
cp ../scan/dft_detect .
cp ../utils/fsk_demod .
cp ../imet/imet1rs_dft .
cp ../mk2a/mk2mod .
cp ../demod/mod/rs41mod .
cp ../demod/mod/dfm09mod .
cp ../demod/mod/m10mod .
cp ../demod/mod/m20mod .
cp ../demod/mod/rs92mod .
cp ../demod/mod/lms6Xmod .
cp ../demod/mod/meisei100mod .
cp ../demod/mod/imet54mod .
cp ../demod/mod/mp3h1mod .
mv ../scan/dft_detect .
mv ../utils/fsk_demod .
mv ../imet/imet1rs_dft .
mv ../imet/imet4iq .
mv ../mk2a/mk2mod .
mv ../demod/mod/rs41mod .
mv ../demod/mod/dfm09mod .
mv ../demod/mod/m10mod .
mv ../demod/mod/m20mod .
mv ../demod/mod/rs92mod .
mv ../demod/mod/lms6Xmod .
mv ../demod/mod/meisei100mod .
mv ../demod/mod/imet54mod .
mv ../demod/mod/mp3h1mod .
echo "Done!"

Wyświetl plik

@ -9,20 +9,54 @@
# - [search_params] -> min_freq, max_freq - Set these suitable for your location!
#
###################
# RTLSDR SETTINGS #
###################
################
# SDR SETTINGS #
################
[sdr]
# Number of RTLSDRs to use.
#
# SDR Type
#
# RTLSDR - Use one or more RTLSDRs
#
# EXPERIMENTAL / NOT IMPLEMENTED options:
# KA9Q - Use a KA9Q SDR Server
# SpyServer - Use a SpyServer
#
sdr_type = RTLSDR
#
# Number of SDRs or SDR Connection Threads to use
#
# If SDR type is either KA9Q or SpyServer, this defines the maximum number of parallel
# decoding/scan tasks.
# If SDR type is set to RTLSDR above, then this number is the number of individual RTLSDRs
# that will be used, eith each RTLSDR allocated a scan or decode task.
# If more than one RTLSDR is in use, multiple [sdr_X] sections must be populated below
#
sdr_quantity = 1
# Individual SDR Settings.
#
# Network SDR Connection Details
#
# If using either a KA9Q or SpyServer network server, the hostname and port
# of the server needs to be defined below. Usually this will be running on the
# same machine as auto_rx, so the defaults are usually fine.
#
sdr_hostname = localhost
sdr_port = 5555
#
# Individual RTLSDR Settings
#
# Provide details of your RTLSDRs here, e.g. device numbers, gain settings, and PPM offsets.
#
[sdr_1]
# Device Index / Serial
# If using a single RTLSDR, set this value to 0
# If using multiple SDRs, you MUST allocate each SDR a unique serial number using rtl_eeprom
# The serial number must NOT be 00000000 or 00000001, as this causes big confusion to the rtl utilities.
# i.e. to set the serial number of a (single) connected RTLSDR: rtl_eeprom -s 00000002
# Then set the device_idx below to 00000002, and repeat for the other [sdr_n] sections below
#
@ -68,6 +102,9 @@ bias = False
# In these areas I suggest using the only_scan feature below instead of using the peak-detect search.
# You may also need to apply a small offset to the frequency for decoding reliability (i.e. 1676.025 MHz) as
# the sondes are often off-frequency. For now, check in something like GQRX to get an idea of what offset is required.
#
# Note - when using either a KA9Q or SpyServer as the SDR backend, the frequency scan limits are set by the
# Server.
min_freq = 400.05
max_freq = 403.0

Wyświetl plik

@ -29,15 +29,28 @@ CSV_DIR = "./results/"
# }
# soft-decision decoding, with a +0.25 Fs signal centre.
# sonde_types = {
# 'RS41': {'csv':'rs41_fsk_demod_soft.txt', 'packets': 118, 'color': 'C0'},
# 'RS92': {'csv':'rs92_fsk_demod_soft.txt', 'packets': 120, 'color': 'C1'},
# 'RS92-NGP': {'csv':'rs92ngp_fsk_demod_soft.txt', 'packets': 120, 'color': 'C2'},
# 'DFM09': {'csv':'dfm_fsk_demod_soft.txt', 'packets': 96, 'color': 'C3'},
# 'M10': {'csv':'m10_fsk_demod_soft.txt', 'packets': 120, 'color': 'C4'},
# 'LMS6-400': {'csv':'lms6-400_fsk_demod_soft.txt', 'packets': 120, 'color': 'C5'},
# 'MRZ': {'csv':'mrz_fsk_demod_soft.txt', 'packets': 105, 'color': 'C6'},
# 'iMet-54': {'csv':'imet54_fsk_demod_soft.txt', 'packets': 240, 'color': 'C7'},
# }
# soft-decision decoding, with a 0Hz signal centre.
sonde_types = {
'RS41': {'csv':'rs41_fsk_demod_soft.txt', 'packets': 118, 'color': 'C0'},
'RS92': {'csv':'rs92_fsk_demod_soft.txt', 'packets': 120, 'color': 'C1'},
'RS92-NGP': {'csv':'rs92ngp_fsk_demod_soft.txt', 'packets': 120, 'color': 'C2'},
'DFM09': {'csv':'dfm_fsk_demod_soft.txt', 'packets': 96, 'color': 'C3'},
'M10': {'csv':'m10_fsk_demod_soft.txt', 'packets': 120, 'color': 'C4'},
'LMS6-400': {'csv':'lms6-400_fsk_demod_soft.txt', 'packets': 120, 'color': 'C5'},
'MRZ': {'csv':'mrz_fsk_demod_soft.txt', 'packets': 105, 'color': 'C6'},
'iMet-54': {'csv':'imet54_fsk_demod_soft.txt', 'packets': 240, 'color': 'C7'},
'RS41': {'csv':'rs41_fsk_demod_soft_centre.txt', 'packets': 118, 'color': 'C0'},
'RS92': {'csv':'rs92_fsk_demod_soft_centre.txt', 'packets': 120, 'color': 'C1'},
'RS92-NGP': {'csv':'rs92ngp_fsk_demod_soft_centre.txt', 'packets': 120, 'color': 'C2'},
'DFM09': {'csv':'dfm_fsk_demod_soft_centre.txt', 'packets': 96, 'color': 'C3'},
'M10': {'csv':'m10_fsk_demod_soft_centre.txt', 'packets': 120, 'color': 'C4'},
'LMS6-400': {'csv':'lms6-400_fsk_demod_soft_centre.txt', 'packets': 120, 'color': 'C5'},
'MRZ': {'csv':'mrz_fsk_demod_soft_centre.txt', 'packets': 105, 'color': 'C6'},
'iMet-54': {'csv':'imet54_fsk_demod_soft_centre.txt', 'packets': 240, 'color': 'C7'},
}
@ -85,5 +98,5 @@ plt.legend()
plt.grid()
plt.ylabel("Packet Error Rate")
plt.xlabel("Eb/No (dB)")
plt.title("auto_rx Decode Chain Performance - fsk_demod")
plt.title("auto_rx Decode Chain Performance - fsk_demod soft-decision 0 Hz")
plt.show()

Wyświetl plik

@ -207,6 +207,16 @@ processing_type = {
"post_process" : " | grep frame | wc -l",
'files' : "./generated/rs41*"
},
'rs41_fsk_demod_soft_centre': {
# Keep signal centred.
'demod' : "| csdr convert_f_s16 | ./tsrc - - 0.500 | ../fsk_demod --cs16 -b -10000 -u 10000 -s --stats=5 2 48000 4800 - - 2>stats.txt |",
# Decode using rs41ecc
'decode': "../rs41mod --ecc --ptu --crc --softin -i --json 2>/dev/null",
# Count the number of telemetry lines.
"post_process" : " | grep frame | wc -l",
'files' : "./generated/rs41*"
},
# RS92 Decoding
'rs92_fsk_demod_soft': {
# Shift up to ~24 khz, and then pass into fsk_demod.
@ -218,6 +228,16 @@ processing_type = {
"post_process" : " | grep M2513116 | wc -l",
'files' : "./generated/rs92*"
},
'rs92_fsk_demod_soft_centre': {
# Shift up to ~24 khz, and then pass into fsk_demod.
'demod' : "| csdr convert_f_s16 | ./tsrc - - 0.500 | ../fsk_demod --cs16 -b -10000 -u 10000 -s --stats=5 2 48000 4800 - - 2>stats.txt |",
# Decode using rs41ecc
'decode': "../rs92mod -vx -v --crc --ecc --vel --softin -i 2>/dev/null",
# Count the number of telemetry lines.
"post_process" : " | grep M2513116 | wc -l",
'files' : "./generated/rs92*"
},
# RS92-NGP Decoding
'rs92ngp_fsk_demod_soft': {
# Shift up to ~24 khz, and then pass into fsk_demod.
@ -229,6 +249,16 @@ processing_type = {
"post_process" : " | grep P3213708 | wc -l",
'files' : "./generated/rsngp*"
},
'rs92ngp_fsk_demod_soft_centre': {
# Shift up to ~24 khz, and then pass into fsk_demod.
'demod' : "| csdr convert_f_s16 | ../fsk_demod --cs16 -b -20000 -u 20000 -s --stats=5 2 96000 4800 - - 2>stats.txt |",
# Decode using rs41ecc
'decode': "../rs92mod -vx -v --crc --ecc --vel --ngp --softin -i 2>/dev/null",
# Count the number of telemetry lines.
"post_process" : " | grep P3213708 | wc -l",
'files' : "./generated/rsngp*"
},
'm10_fsk_demod_soft': {
# Shift up to ~24 khz, and then pass into fsk_demod.
'demod' : "| csdr shift_addition_cc 0.125 2>/dev/null | csdr convert_f_s16 | ../tsrc - - 0.50083333333 -c | ../fsk_demod --cs16 -b 1 -p 5 -u 23000 -s --stats=5 2 48080 9616 - - 2>stats.txt |",
@ -237,6 +267,14 @@ processing_type = {
"post_process" : "| grep aprsid | wc -l",
'files' : "./generated/m10*"
},
'm10_fsk_demod_soft_centre': {
# Shift up to ~24 khz, and then pass into fsk_demod.
'demod' : "| csdr convert_f_s16 | ../tsrc - - 0.50083333333 -c | ../fsk_demod --cs16 -b -10000 -p 5 -u 10000 -s --stats=5 2 48080 9616 - - 2>stats.txt |",
'decode': "../m10mod --json --softin -i -vvv 2>/dev/null",
# Count the number of telemetry lines.
"post_process" : "| grep aprsid | wc -l",
'files' : "./generated/m10*"
},
'dfm_fsk_demod_soft': {
# cat ./generated/dfm09_96k_float_15.0dB.bin | csdr shift_addition_cc 0.25000 2>/dev/null | csdr convert_f_s16 |
#./tsrc - - 1.041666 | ../fsk_demod --cs16 -b 1 -u 45000 2 100000 2500 - - 2>/dev/null |
@ -249,7 +287,18 @@ processing_type = {
#"post_process" : "| grep -o '\[OK\]' | wc -l", # No ECC
'files' : "./generated/dfm*.bin"
},
'dfm_fsk_demod_soft_centre': {
# cat ./generated/dfm09_96k_float_15.0dB.bin | csdr shift_addition_cc 0.25000 2>/dev/null | csdr convert_f_s16 |
#./tsrc - - 1.041666 | ../fsk_demod --cs16 -b 1 -u 45000 2 100000 2500 - - 2>/dev/null |
#python ./bit_to_samples.py 50000 2500 | sox -t raw -r 50k -e unsigned-integer -b 8 -c 1 - -r 50000 -b 8 -t wav - 2>/dev/null|
#../dfm09ecc -vv --json --dist --auto
'demod': '| csdr convert_f_s16 | ../tsrc - - 0.5208| ../fsk_demod --cs16 -b -10000 -u 10000 -s --stats=5 2 50000 2500 - - 2>stats.txt |',#' python ./bit_to_samples.py 50000 2500 | sox -t raw -r 50k -e unsigned-integer -b 8 -c 1 - -r 50000 -b 8 -t wav - 2>/dev/null| ',
'decode': '../dfm09mod -vv --json --dist --auto --softin -i 2>/dev/null',
"post_process" : " | grep frame | wc -l", # ECC
#"post_process" : "| grep -o '\[OK\]' | wc -l", # No ECC
'files' : "./generated/dfm*.bin"
},
# LMS6-400 Decoding
'lms6-400_fsk_demod_soft': {
# Shift up to ~24 khz, and then pass into fsk_demod.
@ -261,7 +310,16 @@ processing_type = {
"post_process" : "| grep frame | wc -l",
'files' : "./generated/lms6-400*",
},
'lms6-400_fsk_demod_soft_centre': {
# Shift up to ~24 khz, and then pass into fsk_demod.
'demod' : "| csdr convert_f_s16 | ../tsrc - - 0.500 | ../fsk_demod --cs16 -b -10000 -u 10000 -s --stats=5 2 48000 4800 - - 2>stats.txt |",
# Decode using rs41ecc
'decode': "../lms6Xmod --json --softin -i 2>/dev/null",
# Count the number of telemetry lines.
"post_process" : "| grep frame | wc -l",
'files' : "./generated/lms6-400*",
},
# iMet-54 Decoding
'imet54_fsk_demod_soft': {
# Shift up to ~24 khz, and then pass into fsk_demod.
@ -273,7 +331,16 @@ processing_type = {
"post_process" : "| grep frame | wc -l",
'files' : "./generated/imet54*",
},
'imet54_fsk_demod_soft_centre': {
# Shift up to ~24 khz, and then pass into fsk_demod.
'demod' : "| csdr convert_f_s16 | ../tsrc - - 0.500 | ../fsk_demod --cs16 -b -10000 -u 10000 -s --stats=5 2 48000 4800 - - 2>stats.txt |",
# Decode using rs41ecc
'decode': "../imet54mod --ecc --json --softin -i 2>/dev/null",
# Count the number of telemetry lines.
"post_process" : "| grep frame | wc -l",
'files' : "./generated/imet54*",
},
# MRZ Sonde decoding - Soft Input
'mrz_fsk_demod_soft': {
'demod': '| csdr shift_addition_cc 0.125000 2>/dev/null | csdr convert_f_s16 | ../tsrc - - 0.50| ../fsk_demod --cs16 -s -b 1250 -u 23750 --stats=5 2 48000 2400 - - 2>stats.txt |',
@ -281,6 +348,19 @@ processing_type = {
"post_process" : " | grep -F [OK] | wc -l", # ECC
'files' : "./generated/mrz*.bin"
},
'mrz_fsk_demod_soft_centre': {
'demod': '| csdr convert_f_s16 | ../tsrc - - 0.500 | ../fsk_demod --cs16 -s -b -10000 -u 10000 --stats=5 2 48000 2400 - - 2>stats.txt |',
'decode': '../mp3h1mod -vv --softin --json --auto 2>/dev/null',
"post_process" : " | grep -F [OK] | wc -l", # ECC
'files' : "./generated/mrz*.bin"
},
'imet4_iq': {
'demod': '| csdr convert_f_s16 | ../tsrc - - 0.50|',
'decode': '../imet4iq --iq 0.0 --lpIQ --dc - 48000 16 --json 2> /dev/null',
"post_process" : "| grep -F [OK] | wc -l", # ECC
'files' : "./generated/imet4*.bin"
},
}
@ -726,7 +806,7 @@ def run_analysis(mode, file_mask=None, shift=0.0, verbose=False, log_output = No
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mode", type=str, default="rs41_fsk_demod", help="Operation mode.")
parser.add_argument("-m", "--mode", type=str, default="rs41_fsk_demod_soft", help="Operation mode.")
parser.add_argument("-f", "--files", type=str, default=None, help="Glob-path to files to run over.")
parser.add_argument("-v", "--verbose", action='store_true', default=False, help="Show additional debug info.")
parser.add_argument("-d", "--dry-run", action='store_true', default=False, help="Show additional debug info.")
@ -743,7 +823,8 @@ if __name__ == "__main__":
sys.exit(1)
batch_modes = ['dfm_fsk_demod_soft', 'rs41_fsk_demod_soft', 'm10_fsk_demod_soft', 'rs92_fsk_demod_soft', 'rs92ngp_fsk_demod_soft', 'lms6-400_fsk_demod_soft', 'imet4_rtlfm', 'mrz_fsk_demod_soft', 'imet54_fsk_demod_soft']
#batch_modes = ['dfm_fsk_demod_soft', 'rs41_fsk_demod_soft', 'm10_fsk_demod_soft', 'rs92_fsk_demod_soft', 'rs92ngp_fsk_demod_soft', 'lms6-400_fsk_demod_soft', 'imet4_rtlfm', 'mrz_fsk_demod_soft', 'imet54_fsk_demod_soft']
batch_modes = ['dfm_fsk_demod_soft_centre', 'rs41_fsk_demod_soft_centre', 'm10_fsk_demod_soft_centre', 'rs92_fsk_demod_soft_centre', 'rs92ngp_fsk_demod_soft_centre', 'lms6-400_fsk_demod_soft_centre', 'imet4_iq', 'mrz_fsk_demod_soft_centre', 'imet54_fsk_demod_soft_centre']
if args.batch:
for _mode in batch_modes:

1615
imet/imet4iq.c 100644

Plik diff jest za duży Load Diff