From 6f94fdecc27144fb52b3432b990e9cab5150a000 Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Fri, 20 Mar 2020 16:48:42 +1030 Subject: [PATCH 1/7] Remove payload document creation step. --- auto_rx/autorx/__init__.py | 2 +- auto_rx/autorx/config.py | 9 +++++++- auto_rx/autorx/habitat.py | 44 +++++++++++++++++++++----------------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/auto_rx/autorx/__init__.py b/auto_rx/autorx/__init__.py index d8301e1b..27b1aac8 100644 --- a/auto_rx/autorx/__init__.py +++ b/auto_rx/autorx/__init__.py @@ -17,7 +17,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.3.0" +__version__ = "1.3.1-beta1" # Global Variables diff --git a/auto_rx/autorx/config.py b/auto_rx/autorx/config.py index 750aa0b5..7d8fcf21 100644 --- a/auto_rx/autorx/config.py +++ b/auto_rx/autorx/config.py @@ -8,6 +8,7 @@ import copy import logging +import os import traceback import json from .utils import rtlsdr_test @@ -147,6 +148,12 @@ def read_auto_rx_config(filename, no_sdr_test=False): try: + + # Check the file exists. + if not os.path.isfile(filename): + logging.critical("Config file %s does not exist!" % filename) + return None + config = RawConfigParser(auto_rx_config) config.read(filename) @@ -168,7 +175,7 @@ def read_auto_rx_config(filename, no_sdr_test=False): if auto_rx_config['email_smtp_authentication'] not in ['None', 'TLS', 'SSL']: logging.error("Config - Invalid email authentication setting. Must be None, TLS or SSL.") - raise Exception() + return None except: logging.error("Config - Invalid or missing email settings. Disabling.") diff --git a/auto_rx/autorx/habitat.py b/auto_rx/autorx/habitat.py index 763c36a0..d1c1a00c 100644 --- a/auto_rx/autorx/habitat.py +++ b/auto_rx/autorx/habitat.py @@ -629,27 +629,31 @@ def handle_telem_dict(self, telem, immediate=False): # race conditions resulting in multiple payload docs being created. self.upload_lock.acquire() - # Create a habitat document if one does not already exist: - if not self.observed_payloads[telem['id']]['habitat_document']: - # Check if there has already been telemetry from this ID observed on Habhub - _document_exists = check_callsign(_callsign) - # If so, we don't need to create a new document - if _document_exists: - self.observed_payloads[telem['id']]['habitat_document'] = True - else: - # Otherwise, we attempt to create a new document. - if self.inhibit: - # If we have an upload inhibit, don't create a payload doc. - _created = True - else: - _created = initPayloadDoc(_callsign, description="Meteorology Radiosonde", frequency=telem['freq_float']) + + # Habitat Payload document creation has been disabled as of 2020-03-20. + # We now use a common payload document for all radiosonde telemetry. + # + # # Create a habitat document if one does not already exist: + # if not self.observed_payloads[telem['id']]['habitat_document']: + # # Check if there has already been telemetry from this ID observed on Habhub + # _document_exists = check_callsign(_callsign) + # # If so, we don't need to create a new document + # if _document_exists: + # self.observed_payloads[telem['id']]['habitat_document'] = True + # else: + # # Otherwise, we attempt to create a new document. + # if self.inhibit: + # # If we have an upload inhibit, don't create a payload doc. + # _created = True + # else: + # _created = initPayloadDoc(_callsign, description="Meteorology Radiosonde", frequency=telem['freq_float']) - if _created: - self.observed_payloads[telem['id']]['habitat_document'] = True - else: - self.log_error("Error creating payload document!") - self.upload_lock.release() - return + # if _created: + # self.observed_payloads[telem['id']]['habitat_document'] = True + # else: + # self.log_error("Error creating payload document!") + # self.upload_lock.release() + # return if immediate: self.log_info("Performing immediate upload for first telemetry sentence of %s." % telem['id']) From 0aef52eb468ba49f1df27252f83f762889a85d2a Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Fri, 20 Mar 2020 17:03:07 +1030 Subject: [PATCH 2/7] Add vert/horix velocity and heading to log file. --- auto_rx/autorx/logger.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/auto_rx/autorx/logger.py b/auto_rx/autorx/logger.py index 656ce2a0..5b84b640 100644 --- a/auto_rx/autorx/logger.py +++ b/auto_rx/autorx/logger.py @@ -33,10 +33,12 @@ class TelemetryLogger(object): # Close any open file handles after X seconds of no activity. # This will help avoid having lots of file handles open for a long period of time if we are handling telemetry # from multiple sondes. - FILE_ACTIVITY_TIMEOUT = 30 + FILE_ACTIVITY_TIMEOUT = 300 # We require the following fields to be present in the input telemetry dict. - REQUIRED_FIELDS = ['frame', 'id', 'datetime', 'lat', 'lon', 'alt', 'temp', 'humidity', 'type', 'freq', 'datetime_dt'] + REQUIRED_FIELDS = ['frame', 'id', 'datetime', 'lat', 'lon', 'alt', 'temp', 'humidity', 'type', 'freq', 'datetime_dt', 'vel_v', 'vel_h', 'heading'] + + LOG_HEADER = "timestamp,serial,frame,lat,lon,alt,vel_v,vel_h,heading,temp,humidity,type,freq,other\n" def __init__(self, log_directory = "./log"): @@ -116,13 +118,17 @@ def telemetry_to_string(self, telemetry): Args: telemetry (dict): Telemetry dictionary to process. """ - _log_line = "%s,%s,%d,%.5f,%.5f,%.1f,%.1f,%.1f,%s,%.3f" % ( + # timestamp,serial,frame,lat,lon,alt,vel_v,vel_h,heading,temp,humidity,type,freq,other + _log_line = "%s,%s,%d,%.5f,%.5f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%s,%.3f" % ( telemetry['datetime'], telemetry['id'], telemetry['frame'], telemetry['lat'], telemetry['lon'], telemetry['alt'], + telemetry['vel_v'], + telemetry['vel_h'], + telemetry['heading'], telemetry['temp'], telemetry['humidity'], telemetry['type'], @@ -188,7 +194,10 @@ def write_telemetry(self, telemetry): _log_file_name = os.path.join(self.log_directory, _log_suffix) self.log_info("Opening new log file: %s" % _log_file_name) # Create entry in open logs dictionary - self.open_logs[_id] = {'log':open(_log_file_name,'a'), 'last_time':time.time()} + self.open_logs[_id] = {'log':open(_log_file_name,'a'), 'last_time':time.time()} + + # Write in a header line. + self.open_logs[_id]['log'].write(self.LOG_HEADER) # Produce log file sentence. From ec9aa3de3e94acd6a6cd72a8728955e52994b455 Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Fri, 20 Mar 2020 17:15:07 +1030 Subject: [PATCH 3/7] Minor web interface tweaks. --- auto_rx/autorx/templates/index.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/auto_rx/autorx/templates/index.html b/auto_rx/autorx/templates/index.html index 63e28d2e..53352f9b 100644 --- a/auto_rx/autorx/templates/index.html +++ b/auto_rx/autorx/templates/index.html @@ -181,6 +181,7 @@ {title:"Latitude", field:"lat", headerSort:false}, {title:"Longitude", field:"lon", headerSort:false}, {title:"Alt (m)", field:"alt", headerSort:false}, + {title:"Vel (kph)", field:"vel_h", headerSort:false}, {title:"Asc (m/s)", field:"vel_v", headerSort:false}, {title:"Temp (°C)", field:"temp", headerSort:false}, {title:"RH (%)", field:"humidity", headerSort:false}, @@ -219,8 +220,8 @@ var _look_angles = calculate_lookangles(_station, _bal); - sonde_id_data.azimuth = _look_angles.azimuth.toFixed(0); - sonde_id_data.elevation = _look_angles.elevation.toFixed(0); + sonde_id_data.azimuth = _look_angles.azimuth.toFixed(1); + sonde_id_data.elevation = _look_angles.elevation.toFixed(1); sonde_id_data.range = (_look_angles.range/1000).toFixed(1); } else{ // Insert blank data. @@ -234,6 +235,7 @@ sonde_id_data.lon = sonde_id_data.lon.toFixed(5); sonde_id_data.alt = sonde_id_data.alt.toFixed(1); sonde_id_data.vel_v = sonde_id_data.vel_v.toFixed(1); + sonde_id_data.vel_h = (sonde_id_data.vel_h*3.6).toFixed(1); // Add a link to HabHub if we have habitat enabled. if ("habitat_enabled" in autorx_config){ if(autorx_config.habitat_enabled ==true){ @@ -255,6 +257,9 @@ sonde_id_data.other += "BT " + new Date(sonde_id_data.bt*1000).toISOString().substr(11, 8) + " "; } } + if (sonde_id_data.hasOwnProperty('batt')){ + sonde_id_data.other += sonde_id_data.batt.toFixed(1) + " V"; + } telem_data.push(sonde_id_data); }); From b9f9ffb10bc9886c1a73e67f097786663fbf3335 Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Fri, 20 Mar 2020 17:24:46 +1030 Subject: [PATCH 4/7] Add battery voltage output to DFM and M10, Add additional PTU calculations to M10 --- demod/mod/dfm09mod.c | 4 +- demod/mod/m10mod.c | 100 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/demod/mod/dfm09mod.c b/demod/mod/dfm09mod.c index e5d21c72..9bfed88c 100644 --- a/demod/mod/dfm09mod.c +++ b/demod/mod/dfm09mod.c @@ -668,8 +668,8 @@ static void print_gpx(gpx_t *gpx) { } // Print JSON blob // valid sonde_ID? - printf("{ \"frame\": %d, \"id\": \"%s\", \"datetime\": \"%04d-%02d-%02dT%02d:%02d:%06.3fZ\", \"lat\": %.5f, \"lon\": %.5f, \"alt\": %.5f, \"vel_h\": %.5f, \"heading\": %.5f, \"vel_v\": %.5f", - gpx->frnr, json_sonde_id, gpx->jahr, gpx->monat, gpx->tag, gpx->std, gpx->min, gpx->sek, gpx->lat, gpx->lon, gpx->alt, gpx->horiV, gpx->dir, gpx->vertV); + printf("{ \"frame\": %d, \"id\": \"%s\", \"datetime\": \"%04d-%02d-%02dT%02d:%02d:%06.3fZ\", \"lat\": %.5f, \"lon\": %.5f, \"alt\": %.5f, \"vel_h\": %.5f, \"heading\": %.5f, \"vel_v\": %.5f, \"batt\": %.2f", + gpx->frnr, json_sonde_id, gpx->jahr, gpx->monat, gpx->tag, gpx->std, gpx->min, gpx->sek, gpx->lat, gpx->lon, gpx->alt, gpx->horiV, gpx->dir, gpx->vertV, gpx->status[0]); if (gpx->ptu_out) { // get temperature float t = get_Temp(gpx); // ecc-valid temperature? if (t > -270.0) printf(", \"temp\": %.1f", t); diff --git a/demod/mod/m10mod.c b/demod/mod/m10mod.c index 98cdedd2..e9e6d95a 100644 --- a/demod/mod/m10mod.c +++ b/demod/mod/m10mod.c @@ -85,6 +85,7 @@ typedef struct { char frame_bits[BITFRAME_LEN+BITAUX_LEN+8]; int auxlen; // 0 .. 0x76-0x64 option_t option; + double bLevel; } gpx_t; @@ -442,6 +443,23 @@ static int get_SN(gpx_t *gpx) { return 0; } +// Battery Voltage +static int get_BatteryLevel(gpx_t *gpx) { + + double batteryLevel = 0.0; + + unsigned short batLvl; + + batLvl = (gpx->frame_bytes[70] << 8) | gpx->frame_bytes[69]; + + // Thanks F5MVO for the formula ! + batteryLevel = (double)batLvl/1000.*6.62; + + gpx->bLevel = batteryLevel ; + + return 0; +} + /* -------------------------------------------------------------------------- */ /* g : F^n -> F^16 // checksum, linear @@ -654,33 +672,64 @@ static float get_Tntc2(gpx_t *gpx, int csOK) { #define LN2 0.693147181 #define ADR_108A 1000.0 // 0x3E8=1000 +float get_count_55(gpx_t *gpx) { // CalRef 55%RH , T=20C ? + ui32_t TBCREF_1000 = gpx->frame_bytes[0x32] | (gpx->frame_bytes[0x33]<<8) | (gpx->frame_bytes[0x34]<<16); + return TBCREF_1000 / ADR_108A; +} + static float get_count_RH(gpx_t *gpx) { // capture 1000 rising edges ui32_t TBCCR1_1000 = gpx->frame_bytes[0x35] | (gpx->frame_bytes[0x36]<<8) | (gpx->frame_bytes[0x37]<<16); return TBCCR1_1000 / ADR_108A; } -static float get_TLC555freq(gpx_t *gpx) { +static float get_TLC555freq(gpx_t *gpx, float count) { return FREQ_CAPCLK / get_count_RH(gpx); } -/* -double get_C_RH() { // TLC555 astable: R_A=3.65k, R_B=338k - double R_B = 338e3; - double R_A = 3.65e3; - double C_RH = 1/get_TLC555freq() / (LN2 * (R_A + 2*R_B)); + +float get_C_RH(float freq, float T) { // TLC555 astable: R_A=3.65k, R_B=338k + float R_B = 338e3; + float R_A = 3.65e3; + float td = 0; + float C_RH = (1/freq - 2*td) / (LN2 * (R_A + 2*R_B)); + // freq/T compensation ... return C_RH; } -double get_RH(int csOK) { + +float cRHc55_RH(gpx_t *gpx, float cRHc55) { // C_RH / C_55 // U.P.S.I. // C_RH/C_55 = 0.8955 + 0.002*RH , T=20C // C_RH = C_RH(RH,T) , RH = RH(C_RH,T) // C_RH/C_55 approx.eq. count_RH/count_ref -// c55=270pF? diff=C_55-c55, T=20C - ui32_t c = gpx->frame_bytes[0x32] | (gpx->frame_bytes[0x33]<<8) | (gpx->frame_bytes[0x34]<<16); // CalRef 55%RH , T=20C ? - double count_ref = c / ADR_108A; // CalRef 55%RH , T=20C ? - double C_RH = get_C_RH(); - double T = get_Tntc2(csOK); - return 0; + float TH = get_Tntc2(gpx, 0); + float Tc = get_Temp(gpx, 0); + float rh = (cRHc55-0.8955)/0.002; // UPSI linear transfer function + // temperature compensation + float T0 = 0.0, T1 = -30.0; // T/C + float T = Tc; // TH, TH-Tc (sensorT - T) + if (T < T0) rh += T0 - T/5.5; // approx/empirical + if (T < T1) rh *= 1.0 + (T1-T)/75.0; // approx/empirical + if (rh < 0.0) rh = 0.0; + if (rh > 100.0) rh = 100.0; + return rh; } -*/ + +float get_RHc(gpx_t *gpx, int csOK) { // experimental/raw, errors~10% + float Tc = get_Temp(gpx, 0); + float count_ref = get_count_55(gpx); // CalRef 55%RH , T=20C ? + float count_RH = get_count_RH(gpx); + float C_55 = get_C_RH(get_TLC555freq(gpx, count_ref), 20.0); // CalRef 55%RH , T=20C ? + float C_RH = get_C_RH(get_TLC555freq(gpx, count_RH), Tc); // Tc == T_555 ? + float cRHc55 = C_RH / C_55; + return cRHc55_RH(gpx, cRHc55); +} + +float get_RH(gpx_t *gpx, int csOK) { // experimental/raw, errors~10% + //ui32_t TBCREF_1000 = frame_bytes[0x32] | (frame_bytes[0x33]<<8) | (frame_bytes[0x34]<<16); // CalRef 55%RH , T=20C ? + //ui32_t TBCCR1_1000 = frame_bytes[0x35] | (frame_bytes[0x36]<<8) | (frame_bytes[0x37]<<16); // FrqCnt TLC555 + //float cRHc55 = TBCCR1_1000 / (float)TBCREF_1000; // CalRef 55%RH , T=20C ? + float cRHc55 = get_count_RH(gpx) / get_count_55(gpx); // CalRef 55%RH , T=20C ? + return cRHc55_RH(gpx, cRHc55); +} + /* -------------------------------------------------------------------------- */ static int print_pos(gpx_t *gpx, int csOK) { @@ -693,6 +742,7 @@ static int print_pos(gpx_t *gpx, int csOK) { err |= get_GPSlon(gpx); err |= get_GPSalt(gpx); err2 = get_GPSvel(gpx); + err |= get_BatteryLevel(gpx); if (!err) { @@ -709,7 +759,7 @@ static int print_pos(gpx_t *gpx, int csOK) { fprintf(stdout, " alt: "col_GPSalt"%.2f"col_TXT" ", gpx->alt); if (!err2) { //if (gpx->option.vbs == 2) fprintf(stdout, " "col_GPSvel"(%.1f , %.1f : %.1f)"col_TXT" ", gpx->vx, gpx->vy, gpx->vD2); - fprintf(stdout, " vH: "col_GPSvel"%.1f"col_TXT" D: "col_GPSvel"%.1f"col_TXT" vV: "col_GPSvel"%.1f"col_TXT" ", gpx->vH, gpx->vD, gpx->vV); + fprintf(stdout, " vH: "col_GPSvel"%.1f"col_TXT" D: "col_GPSvel"%.1f"col_TXT" vV: "col_GPSvel"%.1f"col_TXT" BV: "col_GPSvel"%.2f"col_TXT, gpx->vH, gpx->vD, gpx->vV, gpx->bLevel); } if (gpx->option.vbs >= 2) { get_SN(gpx); @@ -722,11 +772,14 @@ static int print_pos(gpx_t *gpx, int csOK) { } if (gpx->option.ptu) { float t = get_Temp(gpx, csOK); + float rh = get_RH(gpx, csOK); if (t > -270.0) fprintf(stdout, " T=%.1fC ", t); + if (gpx->option.vbs >= 3) { if (rh > -0.5) fprintf(stdout, "_RH=%.0f%% ", rh); } if (gpx->option.vbs >= 3) { float t2 = get_Tntc2(gpx, csOK); - float fq555 = get_TLC555freq(gpx); + float fq555 = get_TLC555freq(gpx, get_count_RH(gpx)); if (t2 > -270.0) fprintf(stdout, " (T2:%.1fC) (%.3fkHz) ", t2, fq555/1e3); + fprintf(stdout, "(cRH=%.1f%%) ", get_RHc(gpx,csOK)); } } fprintf(stdout, ANSI_COLOR_RESET""); @@ -741,7 +794,7 @@ static int print_pos(gpx_t *gpx, int csOK) { fprintf(stdout, " alt: %.2f ", gpx->alt); if (!err2) { //if (gpx->option.vbs == 2) fprintf(stdout, " (%.1f , %.1f : %.1f) ", gpx->vx, gpx->vy, gpx->vD2); - fprintf(stdout, " vH: %.1f D: %.1f vV: %.1f ", gpx->vH, gpx->vD, gpx->vV); + fprintf(stdout, " vH: %.1f D: %.1f vV: %.1f BV: %.2f", gpx->vH, gpx->vD, gpx->vV, gpx->bLevel); } if (gpx->option.vbs >= 2) { get_SN(gpx); @@ -753,11 +806,14 @@ static int print_pos(gpx_t *gpx, int csOK) { } if (gpx->option.ptu) { float t = get_Temp(gpx, csOK); + float rh = get_RH(gpx, csOK); if (t > -270.0) fprintf(stdout, " T=%.1fC ", t); + if (gpx->option.vbs >= 3) { if (rh > -0.5) fprintf(stdout, "_RH=%.0f%% ", rh); } if (gpx->option.vbs >= 3) { float t2 = get_Tntc2(gpx, csOK); - float fq555 = get_TLC555freq(gpx); + float fq555 = get_TLC555freq(gpx,get_count_RH(gpx)); if (t2 > -270.0) fprintf(stdout, " (T2:%.1fC) (%.3fkHz) ", t2, fq555/1e3); + fprintf(stdout, "(cRH=%.1f%%) ", get_RHc(gpx,csOK)); } } } @@ -792,8 +848,12 @@ static int print_pos(gpx_t *gpx, int csOK) { fprintf(stdout, "{ "); fprintf(stdout, "\"frame\": %lu ,", (unsigned long)(sec_gps0+0.5)); - fprintf(stdout, "\"id\": \"%s\", \"datetime\": \"%04d-%02d-%02dT%02d:%02d:%06.3fZ\", \"lat\": %.5f, \"lon\": %.5f, \"alt\": %.5f, \"vel_h\": %.5f, \"heading\": %.5f, \"vel_v\": %.5f, \"sats\": %d", - sn_id, utc_jahr, utc_monat, utc_tag, utc_std, utc_min, utc_sek, gpx->lat, gpx->lon, gpx->alt, gpx->vH, gpx->vD, gpx->vV, gpx->numSV); + fprintf(stdout, "\"id\": \"%s\", \"datetime\": \"%04d-%02d-%02dT%02d:%02d:%06.3fZ\", \"lat\": %.5f, \"lon\": %.5f, \"alt\": %.5f, \"vel_h\": %.5f, \"heading\": %.5f, \"vel_v\": %.5f, \"sats\": %d, \"batt\": %.2f", + sn_id, utc_jahr, utc_monat, utc_tag, utc_std, utc_min, utc_sek, gpx->lat, gpx->lon, gpx->alt, gpx->vH, gpx->vD, gpx->vV, gpx->numSV, gpx->bLevel); + float rh = get_RH(gpx, csOK); + if (gpx->option.ptu && rh > -0.5) { + fprintf(stdout, ", \"humidity\": %.1f", rh ); + } // APRS id, 9 characters aprs_id[0] = gpx->frame_bytes[pos_SN+2]; aprs_id[1] = gpx->frame_bytes[pos_SN] & 0xF; From 8d40f03643575beaa2cfd9125aed841912a4de9a Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Fri, 20 Mar 2020 17:30:48 +1030 Subject: [PATCH 5/7] Add UDP to NMEA script. --- auto_rx/utils/listener_nmea_crlf.py | 227 ++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 auto_rx/utils/listener_nmea_crlf.py diff --git a/auto_rx/utils/listener_nmea_crlf.py b/auto_rx/utils/listener_nmea_crlf.py new file mode 100644 index 00000000..7438ab9c --- /dev/null +++ b/auto_rx/utils/listener_nmea_crlf.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python +# +# UDP to NMEA Converter. +# Listen for telemetry messages via UDP, and write NMEA strings to stdout. +# +# Copyright (C) 2018 Mark Jessop +# Copyright (C) 2020 Vigor Geslin +# Released under GNU GPL v3 or later +# + + +import socket, json, sys, traceback +from threading import Thread +from dateutil.parser import parse +from datetime import datetime, timedelta +import time + +try: + from StringIO import StringIO ## for Python 2 +except ImportError: + from io import StringIO ## for Python 3 + +MAX_JSON_LEN = 32768 + + +def fix_datetime(datetime_str, local_dt_str = None): + ''' + Given a HH:MM:SS string from an telemetry sentence, produce a complete timestamp, using the current system time as a guide for the date. + ''' + + if local_dt_str is None: + _now = datetime.utcnow() + else: + _now = parse(local_dt_str) + + # Are we in the rollover window? + if _now.hour == 23 or _now.hour == 0: + _outside_window = False + else: + _outside_window = True + + # Append on a timezone indicator if the time doesn't have one. + if datetime_str.endswith('Z') or datetime_str.endswith('+00:00'): + pass + else: + datetime_str += "Z" + + + # Parsing just a HH:MM:SS will return a datetime object with the year, month and day replaced by values in the 'default' + # argument. + _telem_dt = parse(datetime_str, default=_now) + + if _outside_window: + # We are outside the day-rollover window, and can safely use the current zulu date. + return _telem_dt + else: + # We are within the window, and need to adjust the day backwards or forwards based on the sonde time. + if _telem_dt.hour == 23 and _now.hour == 0: + # Assume system clock running slightly fast, and subtract a day from the telemetry date. + _telem_dt = _telem_dt - timedelta(days=1) + + elif _telem_dt.hour == 00 and _now.hour == 23: + # System clock running slow. Add a day. + _telem_dt = _telem_dt + timedelta(days=1) + + return _telem_dt + +def udp_listener_nmea_callback(info): + ''' Handle a Payload Summary Message from UDPListener ''' + + dateRS = datetime.strptime(info['time'], '%H:%M:%S') + + hms = dateRS.hour*10000.0+dateRS.minute*100.0+dateRS.second+dateRS.microsecond; + dateNMEA = dateRS.year%100+dateRS.month*100+dateRS.day*10000 + + lat = int(info['latitude']) * 100 + (info['latitude'] - int(info['latitude'])) * 60 + ns = '' + if lat < 0 : + lat *= -1.0 + ns = 'S' + else: + ns = 'N' + + lon = int(info['longitude']) * 100 + (info['longitude'] - int(info['longitude'])) * 60 + ew = '' + if lon < 0 : + lon *= -1.0 + ew = 'W' + else: + ew = 'E' + + speed = info['speed'] * 3.6/1.852 + + geoid = 44 + course = info['heading'] + alt = info['altitude'] + + bufGPRMC = StringIO() + bufGPRMC.write('GPRMC,%010.3f,A,%08.3f,%s,%09.3f,%s,%.2f,%.2f,%06d,,' % (hms, lat, ns, lon, ew, speed, course, dateNMEA)) + + gprmc = bufGPRMC.getvalue() + + cs_grpmc = 0 + for c in gprmc: + cs_grpmc ^= ord(c) + bufGPRMC.write('*%02X' % (cs_grpmc)) + + bufLine=bufGPRMC.getvalue() + + sys.stdout.write('$') + sys.stdout.write(bufLine) + sys.stdout.write('\r\n') + + bufGPGGA = StringIO() + + bufGPGGA.write('GPGGA,%010.3f,%08.3f,%s,%09.3f,%s,1,04,0.0,%.3f,M,%.1f,M,,' % (hms, lat, ns, lon, ew, alt - geoid, geoid)) + + gpgga = bufGPGGA.getvalue() + cs_gpgga = 0 + for d in gpgga: + cs_gpgga ^= ord(d) + + bufGPGGA.write('*%02X' % (cs_gpgga)) + + bufLine=bufGPGGA.getvalue() + sys.stdout.write('$') + sys.stdout.write(bufLine) + sys.stdout.write('\r\n') + sys.stdout.flush() + + +class UDPListenerNMEA(object): + ''' UDP Broadcast Packet Listener + Listens for Horuslib UDP broadcast packets, and passes them onto a callback function + ''' + + def __init__(self, + callback=None, + summary_callback = None, + gps_callback = None, + bearing_callback = None, + port=55672): + + self.udp_port = port + self.callback = callback + self.summary_callback = summary_callback + self.gps_callback = gps_callback + self.bearing_callback = bearing_callback + + self.listener_thread = None + self.s = None + self.udp_listener_running = False + + + def handle_udp_packet(self, packet): + ''' Process a received UDP packet ''' + try: + packet_dict = json.loads(packet) + + if self.callback is not None: + self.callback(packet_dict) + + if packet_dict['type'] == 'PAYLOAD_SUMMARY': + if self.summary_callback is not None: + self.summary_callback(packet_dict) + + + except Exception as e: + print("Could not parse packet: %s" % str(e)) + traceback.print_exc() + + + def udp_rx_thread(self): + ''' Listen for Broadcast UDP packets ''' + + self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) + self.s.settimeout(1) + self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except: + pass + self.s.bind(('',self.udp_port)) + #print("Started UDP Listener Thread.") + self.udp_listener_running = True + + while self.udp_listener_running: + try: + m = self.s.recvfrom(MAX_JSON_LEN) + except socket.timeout: + m = None + except: + traceback.print_exc() + + if m != None: + self.handle_udp_packet(m[0]) + + #print("Closing UDP Listener") + self.s.close() + + + def start(self): + if self.listener_thread is None: + self.listener_thread = Thread(target=self.udp_rx_thread) + self.listener_thread.start() + + + def close(self): + self.udp_listener_running = False + self.listener_thread.join() + +if __name__ == '__main__': + + try: + _telem_horus_udp_listener = UDPListenerNMEA(summary_callback=udp_listener_nmea_callback, + gps_callback=None, + bearing_callback=None, + port=55673) + _telem_horus_udp_listener.start() + + while True: + time.sleep(0.1) + + except (KeyboardInterrupt, SystemExit): #when you press ctrl+c + #print "\nKilling Thread..." + _telem_horus_udp_listener.close() +#print "Done.\nExiting." From 0847600578d0ec0eefc0e80b4e761785eb804a06 Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Wed, 25 Mar 2020 07:52:43 +1030 Subject: [PATCH 6/7] Fix typo in station.cfg.example --- auto_rx/station.cfg.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto_rx/station.cfg.example b/auto_rx/station.cfg.example index 54551753..388a31b6 100644 --- a/auto_rx/station.cfg.example +++ b/auto_rx/station.cfg.example @@ -196,7 +196,7 @@ station_beacon_rate = 30 # Station beacon comment. # will be replaced with the current auto_rx version number -station_beacon_commment = radiosonde_auto_rx SondeGate v +station_beacon_comment = radiosonde_auto_rx SondeGate v # Station Beacon Icon # The APRS icon to use, as defined in http://www.aprs.org/symbols.html From f97921acac8f3e84fa26fd6ec57557f318bc66ca Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Wed, 25 Mar 2020 19:15:52 +1030 Subject: [PATCH 7/7] Version update prior to merge. --- auto_rx/autorx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto_rx/autorx/__init__.py b/auto_rx/autorx/__init__.py index 27b1aac8..6efa0d61 100644 --- a/auto_rx/autorx/__init__.py +++ b/auto_rx/autorx/__init__.py @@ -17,7 +17,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.3.1-beta1" +__version__ = "1.3.1" # Global Variables