From 243a89b42b5448ec11fb077091e8c875865038d4 Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Wed, 13 Sep 2023 13:29:10 +0930 Subject: [PATCH] Further updates to make WxR-301D decoding work. --- auto_rx/auto_rx.py | 1 + auto_rx/autorx/__init__.py | 2 +- auto_rx/autorx/config.py | 2 +- auto_rx/autorx/decode.py | 44 +++++++++++++ auto_rx/autorx/scan.py | 5 ++ auto_rx/autorx/utils.py | 6 ++ weathex/weathex301d.c | 123 +++++++++++++++++++++++++++++-------- 7 files changed, 156 insertions(+), 27 deletions(-) diff --git a/auto_rx/auto_rx.py b/auto_rx/auto_rx.py index f83cf22a..1f61c66c 100644 --- a/auto_rx/auto_rx.py +++ b/auto_rx/auto_rx.py @@ -653,6 +653,7 @@ def telemetry_filter(telemetry): or ("LMS" in telemetry["type"]) or ("IMET" in telemetry["type"]) or ("MTS01" in telemetry["type"]) + or ("WXR" in telemetry["type"]) ): return "OK" else: diff --git a/auto_rx/autorx/__init__.py b/auto_rx/autorx/__init__.py index 08ce90f8..a2ebc26c 100644 --- a/auto_rx/autorx/__init__.py +++ b/auto_rx/autorx/__init__.py @@ -12,7 +12,7 @@ # 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.6.3-beta4" +__version__ = "1.6.3-beta5" # Global Variables diff --git a/auto_rx/autorx/config.py b/auto_rx/autorx/config.py index 81884656..27ffd17a 100644 --- a/auto_rx/autorx/config.py +++ b/auto_rx/autorx/config.py @@ -456,7 +456,7 @@ def read_auto_rx_config(filename, no_sdr_test=False): "MEISEI": True, "MTS01": False, # Until we test it "MRZ": False, # .... except for the MRZ, until we know it works. - "WXR301": False, # No fsk_demod chain for this yet. + "WXR301": True, # No fsk_demod chain for this yet. "UDP": False, } diff --git a/auto_rx/autorx/decode.py b/auto_rx/autorx/decode.py index b4023b7e..fc93ba88 100644 --- a/auto_rx/autorx/decode.py +++ b/auto_rx/autorx/decode.py @@ -1266,6 +1266,49 @@ def generate_decoder_command_experimental(self): demod_stats = FSKDemodStats(averaging_time=1.0, peak_hold=True) self.rx_frequency = self.sonde_freq + elif self.sonde_type == "WXR301": + # Weathex WxR-301D Sondes. + + _baud_rate = 4800 + _sample_rate = 96000 + + # Limit FSK estimator window to roughly +/- 40 kHz + _lower = -40000 + _upper = 40000 + + 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, + ss_iq_path = self.ss_iq_path, + rtl_device_idx = self.rtl_device_idx, + ppm = self.ppm, + gain = self.gain, + bias = self.bias, + dc_block = True + ) + + # Add in tee command to save IQ to disk if debugging is enabled. + if self.save_decode_iq: + demod_cmd += f" tee {self.save_decode_iq_path} |" + + demod_cmd += "./fsk_demod --cs16 -s -b %d -u %d --stats=%d 2 %d %d - -" % ( + _lower, + _upper, + _stats_rate, + _sample_rate, + _baud_rate, + ) + + # Soft-decision decoding, inverted. + decode_cmd = f"./weathex301d --softin -i --json 2>/dev/null" + + # Weathex sondes transmit continuously - average over the last frame, and use a peak hold + demod_stats = FSKDemodStats(averaging_time=5.0, peak_hold=True) + self.rx_frequency = self.sonde_freq + else: return None @@ -1660,6 +1703,7 @@ def handle_decoder_line(self, data): "%Y-%m-%dT%H:%M:%SZ" ) + # Grab a snapshot of modem statistics, if we are using an experimental decoder. if self.demod_stats is not None: if self.demod_stats.snr != -999.0: diff --git a/auto_rx/autorx/scan.py b/auto_rx/autorx/scan.py index 050be6fc..45f84d4a 100644 --- a/auto_rx/autorx/scan.py +++ b/auto_rx/autorx/scan.py @@ -486,6 +486,8 @@ def detect_sonde( else: _score = float(_score.strip()) _offset_est = 0.0 + + except Exception as e: logging.error( "Scanner - Error parsing dft_detect output: %s" % ret_output.strip() @@ -608,6 +610,9 @@ def detect_sonde( % (_sdr_name, _score, _offset_est) ) _sonde_type = "WXR301" + # Clear out the offset estimate for WxR-301's as it's not accurate + # to do no whitening on the signal. + _offset_est = 0.0 else: _sonde_type = None diff --git a/auto_rx/autorx/utils.py b/auto_rx/autorx/utils.py index 46567499..f9a7cc32 100644 --- a/auto_rx/autorx/utils.py +++ b/auto_rx/autorx/utils.py @@ -306,6 +306,12 @@ def generate_aprs_id(sonde_data): _id_suffix = int(sonde_data["id"].split("-")[1]) _id_hex = hex(_id_suffix).upper() _object_name = "LMS6" + _id_hex[-5:] + + elif "WXR" in sonde_data["type"]: + # Use the last 6 hex digits of the sonde ID. + _id_suffix = int(sonde_data["id"].split("-")[1]) + _id_hex = hex(_id_suffix).upper() + _object_name = "WXR" + _id_hex[-6:] elif "MEISEI" in sonde_data["type"] or "IMS100" in sonde_data["type"] or "RS11G" in sonde_data["type"]: # Convert the serial number to an int diff --git a/weathex/weathex301d.c b/weathex/weathex301d.c index 529c7cb4..cdd81070 100644 --- a/weathex/weathex301d.c +++ b/weathex/weathex301d.c @@ -34,6 +34,7 @@ int option_verbose = 0, option_b = 0, option_json = 0, option_timestamp = 0, + option_softin = 0, wavloaded = 0; int wav_channel = 0; // audio channel: left @@ -229,6 +230,28 @@ int read_rawbit(FILE *fp, int *bit) { return 0; } + +int f32soft_read(FILE *fp, float *s) { + unsigned int word = 0; + short *b = (short*)&word; + float *f = (float*)&word; + int bps = 32; + + if (fread( &word, bps/8, 1, fp) != 1) return EOF; + + if (bps == 32) { + *s = *f; + } + else { + if (bps == 8) { *b -= 128; } + *s = *b/128.0; + if (bps == 16) { *s /= 256.0; } + } + + return 0; +} + + int compare() { int i=0; while ((i < HEADLEN) && (buf[(bufpos+i) % HEADLEN] == header[HEADLEN+HEADOFS-1-i])) { @@ -428,7 +451,7 @@ int print_frame() { val = xframe[OFS+13] | (xframe[OFS+14]<<8) | (xframe[OFS+15]<<16); val >>= 4; val &= 0x7FFFF; // int19 ? - //if (val & 0x40000) val -= 0x80000; + //if (val & 0x40000) val -= 0x80000; ?? or sign bit ? float alt = val / 10.0f; printf(" alt: %.1f ", alt); // MSL gpx.alt = alt; @@ -436,16 +459,16 @@ int print_frame() { // lat val = xframe[OFS+15] | (xframe[OFS+16]<<8) | (xframe[OFS+17]<<16) | (xframe[OFS+18]<<24); val >>= 7; - val &= 0x1FFFFFF; // int25 ? - if (val & 0x1000000) val -= 0x2000000; // sign ? (or 90 -> -90 wrap ?) + val &= 0x1FFFFFF; // int25 ? ?? sign NMEA N/S ? + //if (val & 0x1000000) val -= 0x2000000; // sign bit ? (or 90 -> -90 wrap ?) float lat = val / 1e5f; printf(" lat: %.4f ", lat); gpx.lat = lat; // lon val = xframe[OFS+19] | (xframe[OFS+20]<<8) | (xframe[OFS+21]<<16)| (xframe[OFS+22]<<24); - val &= 0x3FFFFFF; // int26 ? - if (val & 0x2000000) val -= 0x4000000; // sign ? (or 180 -> -180 wrap ?) + val &= 0x3FFFFFF; // int26 ? ?? sign NMEA E/W ? + //if (val & 0x2000000) val -= 0x4000000; // or sign bit ? (or 180 -> -180 wrap ?) float lon = val / 1e5f; printf(" lon: %.4f ", lon); gpx.lon = lon; @@ -518,6 +541,7 @@ int main(int argc, char **argv) { else if ( (strcmp(*argv, "-v") == 0) || (strcmp(*argv, "--verbose") == 0) ) { option_verbose = 1; } + else if (strcmp(*argv, "--softin") == 0) { option_softin = 1; } // float32 soft input else if (strcmp(*argv, "-b" ) == 0) { option_b = 1; } else if (strcmp(*argv, "-t" ) == 0) { option_timestamp = 1; } else if ( (strcmp(*argv, "-r") == 0) || (strcmp(*argv, "--raw") == 0) ) { @@ -549,8 +573,10 @@ int main(int argc, char **argv) { if (!wavloaded) fp = stdin; - i = read_wav_header(fp); - if (i) return -1; + if ( !option_softin ) { + i = read_wav_header(fp); + if (i) return -1; + } if (cfreq > 0) gpx.jsn_freq = (cfreq+500)/1000; @@ -558,17 +584,18 @@ int main(int argc, char **argv) { bit_count = 0; frames = 0; - while (!read_bits_fsk(fp, &bit, &len)) { - if (len == 0) { - bufpos--; - if (bufpos < 0) bufpos = HEADLEN-1; - buf[bufpos] = 'x'; - continue; - } + if (option_softin) + { + float s = 0.0f; + int bit = 0; + sample_rate = BAUD_RATE; + sample_count = 0; + while (!f32soft_read(fp, &s)) { + + bit = option_inv ? (s<=0.0f) : (s>=0.0f); // softbit s: bit=0 <=> s<0 , bit=1 <=> s>=0 - for (j = 0; j < len; j++) { bufpos--; if (bufpos < 0) bufpos = HEADLEN-1; buf[bufpos] = 0x30 + bit; @@ -597,23 +624,69 @@ int main(int argc, char **argv) { print_frame(); } - + sample_count += 1; } - if (header_found && option_b) { - bitstart = 1; + } + else + { + while (!read_bits_fsk(fp, &bit, &len)) { + + if (len == 0) { + bufpos--; + if (bufpos < 0) bufpos = HEADLEN-1; + buf[bufpos] = 'x'; + continue; + } + + + for (j = 0; j < len; j++) { + bufpos--; + if (bufpos < 0) bufpos = HEADLEN-1; + buf[bufpos] = 0x30 + bit; + + if (!header_found) + { + h = compare(); //h2 = compare2(); + if ((h >= HEADLEN)) { + header_found = 1; + fflush(stdout); + if (option_timestamp) printf("<%8.3f> ", sample_count/(double)sample_rate); + strncpy(frame_bits, header, HEADLEN); + bit_count += HEADLEN; + frames++; + } + } + else + { + frame_bits[bit_count] = 0x30 + bit; + bit_count += 1; + } + + if (bit_count >= BITFRAMELEN) { + bit_count = 0; + header_found = 0; + + print_frame(); + } - while ( bit_count < BITFRAMELEN ) { - if (read_rawbit(fp, &bit) == EOF) break; - frame_bits[bit_count] = 0x30 + bit; - bit_count += 1; } + if (header_found && option_b) { + bitstart = 1; - bit_count = 0; - header_found = 0; + while ( bit_count < BITFRAMELEN ) { + if (read_rawbit(fp, &bit) == EOF) break; + frame_bits[bit_count] = 0x30 + bit; + bit_count += 1; + } - print_frame(); + bit_count = 0; + header_found = 0; + + print_frame(); + } } } + printf("\n"); fclose(fp);