Merge pull request #626 from projecthorus/testing

v1.5.10 release - DFM updates, APRS limits
Hg v1.5.10
Mark Jessop 2022-03-05 11:56:53 +10:30 zatwierdzone przez GitHub
commit 938cc6d17d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
9 zmienionych plików z 615 dodań i 316 usunięć

Wyświetl plik

@ -21,7 +21,8 @@ Vaisala | RS41-SG/SGP/SGM | :heavy_check_mark: | :heavy_check_mark: | :heavy_che
Graw | DFM06/09/17 | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x:
Meteomodem | M10 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | Not Sent | :x:
Meteomodem | M20 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: (For some models) | :x:
Intermet Systems | iMet-4 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | Not Sent | :x:
Intermet Systems | iMet-1 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | Not Sent | :heavy_check_mark:
Intermet Systems | iMet-4 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | Not Sent | :heavy_check_mark:
Intermet Systems | iMet-54 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | Not Sent | :x:
Lockheed Martin | LMS6-400/1680 | :heavy_check_mark: | :x: | :x: | :x: | Not Sent
Meisei | iMS-100 | :heavy_check_mark: | :x: | :x: | :x: | Not Sent

Wyświetl plik

@ -876,7 +876,7 @@ def main():
position_report=config["aprs_position_report"],
aprsis_host=config["aprs_server"],
aprsis_port=config["aprs_port"],
synchronous_upload_time=config["aprs_upload_rate"],
upload_time=config["aprs_upload_rate"],
callsign_validity_threshold=config["payload_id_valid"],
station_beacon=config["station_beacon_enabled"],
station_beacon_rate=config["station_beacon_rate"],

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.9"
__version__ = "1.5.10"
# Global Variables

Wyświetl plik

@ -89,9 +89,6 @@ def telemetry_to_aprs_position(
# TODO: RS41 Burst Timer
# Add on auto_rx version
_aprs_comment += " auto_rx v" + auto_rx_version
# Convert float latitude to APRS format (DDMM.MM)
lat = float(sonde_data["lat"])
lat_degree = abs(int(lat))
@ -308,7 +305,7 @@ class APRSUploader(object):
station_beacon_position=(0.0, 0.0, 0.0),
station_beacon_comment="radiosonde_auto_rx SondeGate v<version>",
station_beacon_icon="/r",
synchronous_upload_time=30,
upload_time=60,
callsign_validity_threshold=5,
upload_queue_size=16,
upload_timeout=5,
@ -339,9 +336,7 @@ class APRSUploader(object):
station_beacon_comment (str): Comment field for the station beacon. <version> will be replaced with the current auto_rx version.
station_beacon_icon (str): The APRS icon to be used, as the two characters (symbol table, symbol index), as per http://www.aprs.org/symbols.html
synchronous_upload_time (int): Upload the most recent telemetry when time.time()%synchronous_upload_time == 0
This is done in an attempt to get multiple stations uploading the same telemetry sentence simultaneously,
and also acts as decimation on the number of sentences uploaded to APRS-IS.
upload_time (int): Upload the most recent telemetry after this time is up.
callsign_validity_threshold (int): Only upload telemetry data if the callsign has been observed more than N times. Default = 5
@ -362,7 +357,8 @@ class APRSUploader(object):
self.aprsis_reconnect = aprsis_reconnect
self.upload_timeout = upload_timeout
self.upload_queue_size = upload_queue_size
self.synchronous_upload_time = synchronous_upload_time
self.upload_time = upload_time
self.next_upload = time.monotonic() + upload_time
self.callsign_validity_threshold = callsign_validity_threshold
self.inhibit = inhibit
@ -653,7 +649,7 @@ class APRSUploader(object):
""" Add packets to the aprs upload queue if it is time for us to upload. """
while self.timer_thread_running:
if int(time.time()) % self.synchronous_upload_time == 0:
if time.monotonic() > self.next_upload:
# Time to upload!
for _id in self.observed_payloads.keys():
# If no data, continue...
@ -677,6 +673,9 @@ class APRSUploader(object):
# Flush APRS-IS RX buffer
self.flush_rx()
# Reset upload timer
self.next_upload = time.monotonic() + self.upload_time
else:
# Not yet time to upload, wait for a bit.
time.sleep(0.1)

Wyświetl plik

@ -352,7 +352,7 @@ def read_auto_rx_config(filename, no_sdr_test=False):
if auto_rx_config["aprs_upload_rate"] < MINIMUM_APRS_UPDATE_RATE:
logging.warning(
"Config - APRS Update Rate clipped to minimum of %d seconds. Please be respectful of other users of APRS-IS."
"Config - APRS Update Rate clipped to minimum of %d seconds."
% MINIMUM_APRS_UPDATE_RATE
)
auto_rx_config["aprs_upload_rate"] = MINIMUM_APRS_UPDATE_RATE
@ -517,7 +517,7 @@ def read_auto_rx_config(filename, no_sdr_test=False):
auto_rx_config["aprs_port"] = config.getint("aprs", "aprs_port")
except:
logging.warning(
"Config - Did not find aprs_port setting - using default of 14590. APRS packets might not be forwarded out to the wider APRS-IS network!"
"Config - Did not find aprs_port setting - using default of 14590."
)
auto_rx_config["aprs_port"] = 14590
@ -652,6 +652,39 @@ def read_auto_rx_config(filename, no_sdr_test=False):
)
auto_rx_config["experimental_decoders"]["MK2LMS"] = False
# As of auto_rx version 1.5.10, we are limiting APRS output to only radiosondy.info,
# and only on the non-forwarding port.
# This decision was not made lightly, and is a result of the considerable amount of
# non-amateur traffic that radiosonde flights are causing within the APRS-IS network.
# Until some form of common format can be agreed to amongst the developers of *all*
# radiosonde tracking software to enable radiosonde telemetry to be de-duped,
# I have decided to help reduce the impact on the wider APRS-IS network by restricting
# the allowed servers and ports.
# If you are using another APRS-IS server that *does not* forward to the wider APRS-IS
# network and want it allowed, then please raise an issue at
# https://github.com/projecthorus/radiosonde_auto_rx/issues
#
# You are of course free to fork and modify this codebase as you wish, but please be aware
# that this goes against the wishes of the radiosonde_auto_rx developers to not be part
# of the bigger problem of APRS-IS congestion.
ALLOWED_APRS_SERVERS = ["radiosondy.info"]
ALLOWED_APRS_PORTS = [14590]
if auto_rx_config["aprs_server"] not in ALLOWED_APRS_SERVERS:
logging.warning(
"Please do not upload to servers which forward to the wider APRS-IS network and cause network congestion. Switching to default server of radiosondy.info. If you believe this to be in error, please raise an issue at https://github.com/projecthorus/radiosonde_auto_rx/issues"
)
auto_rx_config["aprs_server"] = "radiosondy.info"
if auto_rx_config["aprs_port"] not in ALLOWED_APRS_PORTS:
logging.warning(
"Please do not use APRS ports which forward data out to the wider APRS-IS network and cause network congestion. Switching to default port of 14590. If you believe this to be in error, please raise an issue at https://github.com/projecthorus/radiosonde_auto_rx/issues"
)
auto_rx_config["aprs_port"] = 14590
# If we are being called as part of a unit test, just return the config now.
if no_sdr_test:
return auto_rx_config

Wyświetl plik

@ -808,8 +808,12 @@ class SondeDecoder(object):
)
# DFM decoder
if len(self.raw_file_option)>0:
# Use raw ecc detailed raw output for DFM sondes.
self.raw_file_option = "--rawecc"
decode_cmd = (
f"./dfm09mod -vv --ecc --json --dist --auto --softin -i {self.raw_file_option.upper()} 2>/dev/null"
f"./dfm09mod -vv --ecc --json --dist --auto --softin -i {self.raw_file_option} 2>/dev/null"
)
# DFM sondes transmit continuously - average over the last 2 frames, and peak hold
@ -1282,8 +1286,18 @@ class SondeDecoder(object):
# in the subtype field, so we can use this directly.
_telemetry["type"] = _telemetry["subtype"]
elif self.sonde_type == "DFM":
# For DFM sondes, we need to use a lookup to convert the subtype field into a model.
_telemetry["type"] = decode_dfm_subtype(_telemetry["subtype"])
# As of 2021-2, the decoder provides a guess of the DFM subtype, provided as
# a subtype field of "0xX:GUESS", e.g. "0xD:DFM17P"
if ":" in _telemetry["subtype"]:
_subtype = _telemetry["subtype"].split(":")[1]
_telemetry["dfmcode"] = _telemetry["subtype"].split(":")[0]
_telemetry["type"] = _subtype
_telemetry["subtype"] = _subtype
else:
_telemetry["type"] = "DFM"
_telemetry["subtype"] = "DFM"
# Check frame ID here to ensure we are on dfm09mod version with the frame number fixes (2020-12).
if _telemetry["frame"] < 256:

Wyświetl plik

@ -172,6 +172,8 @@ class SondehubUploader(object):
_output["type"] = "DFM"
_output["subtype"] = telemetry["type"]
_output["serial"] = telemetry["id"].split("-")[1]
if "dfmcode" in telemetry:
_output["dfmcode"] = telemetry["dfmcode"]
elif telemetry["type"].startswith("M10") or telemetry["type"].startswith("M20"):
_output["manufacturer"] = "Meteomodem"
@ -383,6 +385,14 @@ class SondehubUploader(object):
_retries += 1
continue
elif _req.status_code == 201:
self.log_debug(
"Sondehub reported issue when adding packets to DB. Status Code: %d %s."
% (_req.status_code, _req.text)
)
_upload_success = True
break
else:
self.log_error(
"Error uploading to Sondehub. Status Code: %d %s."

Wyświetl plik

@ -159,37 +159,61 @@ sondehub_contact_email = none@none.com
########################
# APRS UPLOAD SETTINGS #
########################
# Settings for uploading to APRS-IS
# Settings for uploading to radiosondy.info
#
# IMPORTANT APRS NOTE
#
# As of auto_rx version 1.5.10, we are limiting APRS output to only radiosondy.info,
# and only on the non-forwarding port.
# This decision was not made lightly, and is a result of the considerable amount of
# non-amateur traffic that radiosonde flights are causing within the APRS-IS network.
# Until some form of common format can be agreed to amongst the developers of *all*
# radiosonde tracking software to enable radiosonde telemetry to be de-duped,
# I have decided to help reduce the impact on the wider APRS-IS network by restricting
# the allowed servers and ports.
# If you are using another APRS-IS server that *does not* forward to the wider APRS-IS
# network and want it allowed, then please raise an issue at
# https://github.com/projecthorus/radiosonde_auto_rx/issues
#
# You are of course free to fork and modify this codebase as you wish, but please be aware
# that this goes against the wishes of the radiosonde_auto_rx developers to not be part
# of the bigger problem of APRS-IS congestion.
# As of 2022-03-01, radiosonde traffic has been filtered from aprs.fi, so even if you do
# modify the code, you still won't see sondes on that map.
# APRS-IS is a *shared resource*, intended for the use of all amateur radio operators, and
# for many years we have been treating it as a playground to dump large amounts of non-amateur
# traffic into, so we can see weather balloons on a map.
# Instead of congesting this shared resource with this non-amateur traffic, we should instead
# be moving to using databases and sites specialised for this purpose, for example sondehub.org
[aprs]
# Enable APRS upload (you will also need to change some options below!)
aprs_enabled = False
# APRS-IS Login Information
# The aprs_user field can have an SSID on the end if desired, i.e. N0CALL-4
# If you are a licensed amateur radio operator, you may want to change the aprs_port number below
# to 14580, so that your uploaded telemetry makes its way out to the wider APRS network.
aprs_user = N0CALL
# APRS-IS Passcode. You can generate one for your callsign here: https://apps.magicbug.co.uk/passcode/
aprs_pass = 00000
# APRS Upload Rate - Upload a packet every X seconds.
# This has a lower limit of 30 seconds, to avoid flooding the APRS-IS network.
# Please be respectful of other uses of the APRS-IS network, and do not attempt
# to upload faster than this.
# This has a lower limit of 30 seconds, to avoid flooding radiosondy.info
# Please be respectful, and do not attempt to upload faster than this.
upload_rate = 30
# APRS-IS server to upload to.
# Default to radiosondy.info for now, to allow stats to show up on http://radiosondy.info
# Packets are forwarded onto the rest of the APRS-IS network from radiosondy.info.
# If you wish to inject packets directly into the APRS-IS network, use rotate.aprs2.net
# Currently we only support uploading to radiosondy.info
# When using port 14580, packets are not forwarded to the wider APRS-IS network, and hence
# are help reduce the huge amount of non-amateur traffic that ends up in APRS-IS from
# radiosondes.
aprs_server = radiosondy.info
# APRS-IS Port Number to upload to.
# When using radiosondy.info:
# Port 14590 - Packets stay within radiosondy.info. Non-licensed operators can use this.
# Port 14580 - Packets are forwarded out to the wider APRS-IS network. Only licensed amateur radio operators should use this!!
# For all other APRS-IS servers (licensed amateur radio operators only!), use port 14580.
aprs_port = 14590
#
# Port 14590 - Packets stay within radiosondy.info and do not congest the wider APRS-IS
# network.
#
aprs_port = 14580
# APRS Station Location Beaconing
# If enabled, you will show up on APRS using the aprs_user callsign set above.

Wyświetl plik

@ -34,6 +34,28 @@
#include "demod_mod.h"
enum dfmtyp_keys_t {
UNDEF,
UNKNW,
DFM06,
PS15,
DFM09,
DFM09P,
DFM17,
DFM17P
};
static char *DFM_types[] = {
[UNDEF] = "",
[UNKNW] = "DFMxX",
[DFM06] = "DFM06",
[PS15] = "PS15",
[DFM09] = "DFM09",
[DFM09P] = "DFM09P",
[DFM17] = "DFM17",
[DFM17P] = "DFM17P"
};
typedef struct {
i8_t vbs; // verbose output
i8_t raw; // raw frames
@ -75,14 +97,19 @@ typedef struct {
int sonde_typ;
ui32_t SN6;
ui32_t SN;
int week; int gpssec;
int week; int tow; ui32_t sec_gps;
int jahr; int monat; int tag;
int std; int min; float sek;
double lat; double lon; double alt;
double dir; double horiV; double vertV;
float meas24[5+2];
float status[2];
//float T;
float Rf;
float _frmcnt;
float meas24[9];
float status[2];
ui32_t val24[9];
ui8_t cfgchk24[9];
int cfgchk;
char sonde_id[16]; // "ID__:xxxxxxxx\0\0"
hsbit_t frame[BITFRAME_LEN+4]; // char frame_bits[BITFRAME_LEN+4];
char dat_str[9][13+1];
@ -90,8 +117,12 @@ typedef struct {
pcksts_t pck[9];
option_t option;
int ptu_out;
char sensortyp0xC;
char *dfmtyp;
int jsn_freq; // freq/kHz (SDR)
gpsdat_t gps;
int prev_cntsec_diff;
int prev_manpol;
} gpx_t;
@ -434,24 +465,26 @@ static float get_Temp(gpx_t *gpx) { // meas[0..4]
// meas0 = g*(R + Rs)
// meas3 = g*Rs , Rs: dfm6:10k, dfm9:20k
// meas4 = g*Rf , Rf=220k
float T = 0; // T/Kelvin
float f = gpx->meas24[0],
f1 = gpx->meas24[3],
f2 = gpx->meas24[4];
if (gpx->ptu_out >= 0xC) {
if (gpx->sensortyp0xC == 'P') { // 0xC: "P+" DFM-09P , "T-" DFM-17TU ; 0xD: "P-" DFM-17P ?
f = gpx->meas24[0+1];
f1 = gpx->meas24[3+2];
f2 = gpx->meas24[4+2];
}
if (gpx->cfgchk) {
//float *meas = gpx->meas24;
float B0 = 3260.0; // B/Kelvin, fit -55C..+40C
float T0 = 25 + 273.15; // t0=25C
float R0 = 5.0e3; // R0=R25=5k
float Rf = 220e3; // Rf = 220k
float Rf = gpx->Rf; // Rf = DFM09:220k , DFM17:332k
float g = f2/Rf;
float R = (f-f1) / g; // meas[0,3,4] > 0 ?
float T = 0; // T/Kelvin
if (f*f1*f2 == 0) R = 0;
if (R > 0) T = 1/(1/T0 + 1/B0 * log(R/R0));
}
return T - 273.15; // Celsius
// DFM-06: meas20 * 16 = meas24
// -> (meas24[0]-meas24[3])/meas24[4]=(meas20[0]-meas20[3])/meas20[4]
@ -468,7 +501,7 @@ static float get_Temp2(gpx_t *gpx) { // meas[0..4]
float f = gpx->meas24[0],
f1 = gpx->meas24[3],
f2 = gpx->meas24[4];
if (gpx->ptu_out >= 0xC) {
if (gpx->ptu_out >= 0xC && gpx->meas24[6] < 220e3) {
f = gpx->meas24[0+1];
f1 = gpx->meas24[3+2];
f2 = gpx->meas24[4+2];
@ -476,7 +509,7 @@ static float get_Temp2(gpx_t *gpx) { // meas[0..4]
float B0 = 3260.0; // B/Kelvin, fit -55C..+40C
float T0 = 25 + 273.15; // t0=25C
float R0 = 5.0e3; // R0=R25=5k
float Rf2 = 220e3; // Rf2 = Rf = 220k
float Rf2 = 220e3; // Rf2 = Rf = DFM09:220k , DFM17:332k
float g_o = f2/Rf2; // approx gain
float Rs_o = f1/g_o; // = Rf2 * f1/f2;
float Rf1 = Rs_o; // Rf1 = Rs: dfm6:10k, dfm9:20k
@ -494,7 +527,7 @@ static float get_Temp2(gpx_t *gpx) { // meas[0..4]
R = (f-f1)/g; // meas[0,3,4] > 0 ?
if (R > 0) T = 1/(1/T0 + 1/B0 * log(R/R0));
if (gpx->option.ptu && gpx->ptu_out && gpx->option.dbg) {
if (gpx->option.ptu && gpx->ptu_out && gpx->option.dbg && gpx->option.vbs == 3) {
printf(" (Rso: %.1f , Rb: %.1f)", Rs_o/1e3, Rb/1e3);
}
@ -533,13 +566,13 @@ static float get_Temp4(gpx_t *gpx) { // meas[0..4]
float f = gpx->meas24[0],
f1 = gpx->meas24[3],
f2 = gpx->meas24[4];
if (gpx->ptu_out >= 0xC) {
if (gpx->ptu_out >= 0xC && gpx->meas24[6] < 220e3) {
f = gpx->meas24[0+1];
f1 = gpx->meas24[3+2];
f2 = gpx->meas24[4+2];
}
//float *meas = gpx->meas24;
float Rf = 220e3; // Rf = 220k
float Rf = 220e3; // Rf = DFM09:220k , DFM17:332k
float g = f2/Rf;
float R = (f-f1) / g; // f,f1,f2 > 0 ?
float T = 0; // T/Kelvin
@ -550,6 +583,14 @@ static float get_Temp4(gpx_t *gpx) { // meas[0..4]
}
static int reset_cfgchk(gpx_t *gpx) {
int j;
for (j = 0; j < 9; j++) gpx->cfgchk24[j] = 0;
gpx->cfgchk = 0;
gpx->ptu_out = 0;
return 0;
}
#define SNbit 0x0100
static int conf_out(gpx_t *gpx, ui8_t *conf_bits, int ec) {
int ret = 0;
@ -571,6 +612,7 @@ static int conf_out(gpx_t *gpx, ui8_t *conf_bits, int ec) {
{ // reset if 0x5A, 0x5B (DFM-06)
gpx->sonde_typ = 0;
gpx->snc.max_ch = conf_id;
reset_cfgchk(gpx);
}
if (conf_id > 5 && conf_id > gpx->snc.max_ch && ec == 0) { // mind. 6 Kanaele
@ -595,10 +637,11 @@ static int conf_out(gpx_t *gpx, ui8_t *conf_bits, int ec) {
if (SN6 == gpx->SN6 && SN6 != 0) { // nur Nibble-Werte 0..9
gpx->sonde_typ = SNbit | 6;
gpx->ptu_out = 6; // <-> DFM-06
sprintf(gpx->sonde_id, "ID06:%6X", gpx->SN6);
sprintf(gpx->sonde_id, "IDx%1X:%6X", gpx->sonde_typ & 0xF, gpx->SN6);
}
else { // reset
gpx->sonde_typ = 0;
reset_cfgchk(gpx);
}
gpx->SN6 = SN6;
} // SN in last pck/channel, #{pcks} depends on (sensor) config; observed:
@ -614,6 +657,7 @@ static int conf_out(gpx_t *gpx, ui8_t *conf_bits, int ec) {
gpx->snc.chXbit = 0;
gpx->snc.chX[0] = 0;
gpx->snc.chX[1] = 0;
reset_cfgchk(gpx);
}
gpx->snc.sn_ch = sn_ch;
gpx->snc.chX[hl] = (val >> 4) & 0xFFFF;
@ -628,19 +672,17 @@ static int conf_out(gpx_t *gpx, ui8_t *conf_bits, int ec) {
gpx->ptu_out = 0;
if (sn_ch == 0xA /*&& (sn2_ch & 0xF) == 0xC*/) gpx->ptu_out = sn_ch; // <+> DFM-09
if (sn_ch == 0xB /*&& (sn2_ch & 0xF) == 0xC*/) gpx->ptu_out = sn_ch; // <-> DFM-17
if (sn_ch == 0xC) gpx->ptu_out = sn_ch; // <+> DFM-09P(?)
if (sn_ch == 0xD) gpx->ptu_out = sn_ch; // <-> DFM-17P?
if (sn_ch == 0xC) gpx->ptu_out = sn_ch; // <+> DFM-09P(?) , <-> DFM-17TU(?)
if (sn_ch == 0xD) gpx->ptu_out = sn_ch; // <-> DFM-17P(?)
// PS-15 ? (sn2_ch & 0xF) == 0x0 : gpx->ptu_out = 0 // <-> PS-15
if ( (gpx->sonde_typ & 0xF) == 0xA) {
sprintf(gpx->sonde_id, "ID09:%6u", gpx->SN);
}
else {
sprintf(gpx->sonde_id, "ID-%1X:%6u", gpx->sonde_typ & 0xF, gpx->SN);
if ( (gpx->sonde_typ & 0xF) > 6) {
sprintf(gpx->sonde_id, "IDx%1X:%6u", gpx->sonde_typ & 0xF, gpx->SN);
}
}
else { // reset
gpx->sonde_typ = 0;
reset_cfgchk(gpx);
}
gpx->snc.SN_X = SN;
gpx->snc.chXbit = 0;
@ -651,27 +693,38 @@ static int conf_out(gpx_t *gpx, ui8_t *conf_bits, int ec) {
}
if (conf_id >= 0 && conf_id <= 4) {
if (conf_id >= 0 && conf_id <= 8) {
gpx->cfgchk24[conf_id] = 1;
val = bits2val(conf_bits+4, 4*6);
gpx->meas24[conf_id] = fl24(val);
gpx->val24[conf_id] = val;
gpx->meas24[conf_id] = fl24(val); //0xA: 0..4
// DFM-09 (STM32): 24bit 0exxxxx
// DFM-06 (NXP8): 20bit 0exxxx0
// fl20(bits2val(conf_bits+4, 4*5))
// = fl20(exxxx)
// = fl24(exxxx0)/2^4
// meas20 * 16 = meas24
gpx->cfgchk = 0;
if (gpx->ptu_out >= 0x5) gpx->cfgchk = gpx->cfgchk24[0]*gpx->cfgchk24[1]*gpx->cfgchk24[2]
*gpx->cfgchk24[3]*gpx->cfgchk24[4]*gpx->cfgchk24[5];
if (gpx->ptu_out >= 0x7) gpx->cfgchk *= gpx->cfgchk24[6]*gpx->cfgchk24[7];
if (gpx->ptu_out >= 0x8) gpx->cfgchk *= gpx->cfgchk24[8];
}
if (gpx->ptu_out >= 0xC) { // DFM>=09(P)
if (conf_id >= 5 && conf_id <= 6) {
val = bits2val(conf_bits+4, 4*6);
gpx->meas24[conf_id] = fl24(val);
}
gpx->sensortyp0xC = 'T';
gpx->Rf = 220e3;
if (gpx->cfgchk)
{ // 0xC: "P+" DFM-09P , "T-" DFM-17TU ; 0xD: "P-" DFM-17P ?
if (gpx->ptu_out >= 0xD || (gpx->ptu_out >= 0xC && gpx->meas24[6] < 220e3)) { // gpx->meas24[6] < 220e3 <=> gpx->meas24[0] > 1e6 ?
gpx->sensortyp0xC = 'P'; // gpx->meas24[0] > 1e6 ?
}
if ( ((gpx->ptu_out == 0xB || gpx->ptu_out == 0xC) && gpx->sensortyp0xC == 'T') || gpx->ptu_out >= 0xD) gpx->Rf = 332e3; // DFM-17 ?
// STM32-status: Bat, MCU-Temp
if (gpx->ptu_out >= 0xA) { // DFM>=09(P) (STM32)
ui8_t ofs = 0;
if (gpx->ptu_out >= 0xC) ofs = 2;
if (gpx->sensortyp0xC == 'P') ofs = 2;
//
if (conf_id == 0x5+ofs) { // voltage
val = bits2val(conf_bits+8, 4*4);
gpx->status[0] = val/1000.0;
@ -685,7 +738,35 @@ static int conf_out(gpx_t *gpx, ui8_t *conf_bits, int ec) {
gpx->status[0] = 0;
gpx->status[1] = 0;
}
}
/* guess DFM type
V/Ti Tf012 Rf
0xA DFM-09 5/6 0,3,4 'T+' 220k
0xC DFM-09P 7/8 1,5,6 'P+' 220k
0xB DFM-17 5/6 0,3,4 'T-' 332k
0xC DFM-17TU 5/6 0,3,4 'T-' 332k
0xD DFM-17P 7/8 1,5,6 'P-' 332k
*/
gpx->dfmtyp = DFM_types[UNDEF];
switch (gpx->sonde_typ & 0xF) {
case 0x6: gpx->dfmtyp = DFM_types[DFM06];
break;
case 0x7:
case 0x8: gpx->dfmtyp = DFM_types[PS15];
break;
case 0xA: gpx->dfmtyp = DFM_types[DFM09];
break;
case 0xB: gpx->dfmtyp = DFM_types[DFM17];
break;
case 0xC: if (gpx->sensortyp0xC == 'P') gpx->dfmtyp = DFM_types[DFM09P];
else /*'T'*/ gpx->dfmtyp = DFM_types[DFM17];
break;
case 0xD: gpx->dfmtyp = DFM_types[DFM17P];
break;
default: gpx->dfmtyp = DFM_types[UNKNW];
break;
}
return ret;
}
@ -721,6 +802,29 @@ static void print_gpx(gpx_t *gpx) {
jsonout = 0;
}
if (!gpx->option.raw || gpx->option.jsn) {
// seconds since GPS (ignoring leap seconds, DFM=UTC)
datetime2GPSweek(gpx->jahr, gpx->monat, gpx->tag, gpx->std, gpx->min, (int)(gpx->sek+0.5), &(gpx->week), &(gpx->tow));
gpx->sec_gps = gpx->week*604800 + gpx->tow; // SECONDS_IN_WEEK=7*86400=604800
if (contgps) {
ui8_t secmod256 = (ui8_t)gpx->sec_gps; // % 256
int cntsec_diff = secmod256 - gpx->frnr;
if (cntsec_diff < 0) cntsec_diff += 256;
// DFM06: cntsec_diff might drift slowly (30sec sync), but recovers faster
// DFM09: delta(diff)=1 could indicate decoding error
if (gpx->option.jsn && (cntsec_diff != gpx->prev_cntsec_diff || gpx->option.inv != gpx->prev_manpol)) {
// initial state not relevant
jsonout = 0;
gpx->sonde_typ = 0;
reset_cfgchk(gpx);
}
gpx->prev_cntsec_diff = cntsec_diff;
gpx->prev_manpol = gpx->option.inv;
}
}
if (output & 0xF000) {
if (gpx->option.raw == 2) {
@ -731,8 +835,9 @@ static void print_gpx(gpx_t *gpx) {
for (i = 0; i < 9; i++) {
for (j = 0; j < 13; j++) gpx->dat_str[i][j] = ' ';
}
printf("\n");
}
else {
else if (!gpx->option.raw) {
if (gpx->option.aut && gpx->option.vbs >= 2) printf("<%c> ", gpx->option.inv?'-':'+');
printf("[%3d] ", gpx->frnr);
printf("%4d-%02d-%02d ", gpx->jahr, gpx->monat, gpx->tag);
@ -745,38 +850,48 @@ static void print_gpx(gpx_t *gpx) {
printf(" vH: %5.2f ", gpx->horiV);
printf(" D: %5.1f ", gpx->dir);
printf(" vV: %5.2f ", gpx->vertV);
if (gpx->cfgchk)
{
if (gpx->option.ptu && gpx->ptu_out) {
float t = get_Temp(gpx);
if (t > -270.0) printf(" T=%.1fC ", t);
if (t > -270.0) {
printf(" T=%.1fC ", t); // 0xC:P+ DFM-09P , 0xC:T- DFM-17TU , 0xD:P- DFM-17P ?
if (gpx->option.vbs == 3) printf(" (0x%X:%c%c) ", gpx->sonde_typ & 0xF, gpx->sensortyp0xC, gpx->option.inv?'-':'+');
}
if (gpx->option.dbg) {
float t2 = get_Temp2(gpx);
float t4 = get_Temp4(gpx);
if (t2 > -270.0) printf(" T2=%.1fC ", t2);
if (t4 > -270.0) printf(" T4=%.1fC ", t4);
printf(" f0: %.2f ", gpx->meas24[0]);
printf(" f1: %.2f ", gpx->meas24[1]);
printf(" f2: %.2f ", gpx->meas24[2]);
printf(" f3: %.2f ", gpx->meas24[3]);
printf(" f4: %.2f ", gpx->meas24[4]);
if (gpx->ptu_out >= 0xC) {
printf(" f5: %.2f ", gpx->meas24[5]);
printf(" f6: %.2f ", gpx->meas24[6]);
}
}
}
if (gpx->option.vbs == 3 && gpx->ptu_out >= 0xA) {
printf(" U: %.2fV ", gpx->status[0]);
printf(" Ti: %.1fK ", gpx->status[1]);
if (gpx->status[0]> 0.0) printf(" U: %.2fV ", gpx->status[0]);
if (gpx->status[1]> 0.0) printf(" Ti: %.1fK ", gpx->status[1]);
}
}
if (gpx->option.dbg) {
printf(" f0:%.1f", gpx->meas24[0]);
printf(" f1:%.1f", gpx->meas24[1]);
printf(" f2:%.1f", gpx->meas24[2]);
printf(" f3:%.1f", gpx->meas24[3]);
printf(" f4:%.1f", gpx->meas24[4]);
if (gpx->ptu_out >= 0xA /*0xC*/) {
printf(" f5:%.1f", gpx->meas24[5]);
printf(" f6:%.1f", gpx->meas24[6]);
}
printf(" ");
}
if (gpx->option.vbs)
{
if (gpx->sonde_typ & SNbit) {
printf(" (%s) ", gpx->sonde_id);
printf(" (%s", gpx->sonde_id);
if (gpx->option.vbs > 1 && *gpx->dfmtyp) printf(":%s", gpx->dfmtyp);
printf(") ");
gpx->sonde_typ ^= SNbit;
}
}
}
printf("\n");
if (gpx->option.sat) {
@ -788,30 +903,26 @@ static void print_gpx(gpx_t *gpx) {
printf(" )");
printf("\n");
}
}
if (gpx->option.jsn && jsonout && gpx->sek < 60.0)
{
char *ver_jsn = NULL;
unsigned long sec_gps = 0;
int week = 0;
int tow = 0;
char json_sonde_id[] = "DFM-xxxxxxxx\0\0";
ui8_t dfm_typ = (gpx->sonde_typ & 0xF);
switch ( dfm_typ ) {
ui8_t dfmXtyp = (gpx->sonde_typ & 0xF);
switch ( dfmXtyp ) {
case 0: sprintf(json_sonde_id, "DFM-xxxxxxxx"); break; //json_sonde_id[0] = '\0';
case 6: sprintf(json_sonde_id, "DFM-%6X", gpx->SN6); break; // DFM-06
case 0xA: sprintf(json_sonde_id, "DFM-%6u", gpx->SN); break; // DFM-09
// 0x7:PS-15?, 0xB:DFM-17? 0xC:DFM-09P? 0xD:DFM-17P?
// 0x7:PS-15?, 0xB:DFM-17? 0xC:DFM-09P?DFM-17TU? 0xD:DFM-17P?
default : sprintf(json_sonde_id, "DFM-%6u", gpx->SN);
}
// JSON frame counter: seconds since GPS (ignoring leap seconds, DFM=UTC)
datetime2GPSweek(gpx->jahr, gpx->monat, gpx->tag, gpx->std, gpx->min, (int)(gpx->sek+0.5), &week, &tow);
sec_gps = week*604800 + tow; // SECONDS_IN_WEEK=7*86400=604800
// JSON frame counter: gpx->sec_gps , seconds since GPS (ignoring leap seconds, DFM=UTC)
// Print JSON blob // valid sonde_ID?
printf("{ \"type\": \"%s\"", "DFM");
printf(", \"frame\": %lu, ", sec_gps); // gpx->frnr
printf(", \"frame\": %u, ", gpx->sec_gps); // gpx->frnr
printf("\"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",
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->gps.nSV);
if (gpx->ptu_out >= 0xA && gpx->status[0] > 0) { // DFM>=09(P): Battery (STM32)
@ -821,7 +932,12 @@ static void print_gpx(gpx_t *gpx) {
float t = get_Temp(gpx); // ecc-valid temperature?
if (t > -270.0) printf(", \"temp\": %.1f", t);
}
if (dfm_typ > 0) printf(", \"subtype\": \"0x%1X\"", dfm_typ);
//if (dfmXtyp > 0) printf(", \"subtype\": \"0x%1X\"", dfmXtyp);
if (dfmXtyp > 0) {
printf(", \"subtype\": \"0x%1X", dfmXtyp);
if (*gpx->dfmtyp) printf(":%s", gpx->dfmtyp);
printf("\"");
}
if (gpx->jsn_freq > 0) {
printf(", \"freq\": %d", gpx->jsn_freq);
}
@ -862,7 +978,28 @@ static int print_frame(gpx_t *gpx) {
ret2 = hamming(gpx->option.ecc, hamming_dat2, 13, block_dat2);
ret = ret0 | ret1 | ret2;
if (gpx->option.raw == 1) {
if (gpx->option.raw == 9) {
int diff = 0;
for (i = 0; i < CONF; i++) {
diff += ( (gpx->frame[i].hb & 1) != (dfm_header[i] & 1) );
}
if (diff == 0)
{
ui8_t byte = 0;
printf("%c", gpx->option.inv?'-':'+');
printf("<%7.1f> ", gpx->_frmcnt);
for (i = CONF; i < BITFRAME_LEN; i++) {
if (i == DAT1 || i == DAT2) printf(" ");
byte |= (gpx->frame[i].hb&1)<<(i%4); // little endian
if (i % 4 == 3) {
printf("%1X", byte & 0xF);
byte = 0;
}
}
printf("\n");
}
}
else if (gpx->option.raw == 1) {
for (i = 0; i < 7; i++) {
nib = bits2val(block_conf+S*i, S);
@ -902,7 +1039,9 @@ static int print_frame(gpx_t *gpx) {
printf("\n");
}
else if (gpx->option.ecc) {
if ( (gpx->option.raw&1) != 1 || gpx->option.jsn )
{
if (gpx->option.ecc) {
if (ret0 == 0 || ret0 > 0 || gpx->option.ecc == 2) {
conf_out(gpx, block_conf, ret0);
@ -926,6 +1065,7 @@ static int print_frame(gpx_t *gpx) {
if (frid == 8) print_gpx(gpx);
}
}
return ret;
}
@ -947,6 +1087,7 @@ int main(int argc, char **argv) {
int option_iqdc = 0;
int option_lp = 0;
int option_dc = 0;
int option_noLUT = 0;
int option_bin = 0;
int option_softin = 0;
int option_json = 0; // JSON blob output (for auto_rx)
@ -954,6 +1095,7 @@ int main(int argc, char **argv) {
int wavloaded = 0;
int sel_wavch = 0; // audio channel: left
int spike = 0;
int rawhex = 0;
int cfreq = -1;
FILE *fp = NULL;
@ -1021,6 +1163,9 @@ int main(int argc, char **argv) {
else if ( (strcmp(*argv, "-R") == 0) || (strcmp(*argv, "--RAW") == 0) ) {
option_raw = 2;
}
else if ( (strcmp(*argv, "--rawecc") == 0) ) {
option_raw = 9;
}
else if ( (strcmp(*argv, "-i") == 0) || (strcmp(*argv, "--invert") == 0) ) {
option_inv = 0x1;
}
@ -1076,21 +1221,24 @@ int main(int argc, char **argv) {
dsp.xlt_fq = -fq; // S(t) -> S(t)*exp(-f*2pi*I*t)
option_iq = 5;
}
else if (strcmp(*argv, "--lp") == 0) { option_lp = 1; } // IQ lowpass
else if (strcmp(*argv, "--dc") == 0) { option_dc = 1; }
else if (strcmp(*argv, "--min") == 0) {
option_min = 1;
}
else if (strcmp(*argv, "--dbg") == 0) { gpx.option.dbg = 1; }
else if (strcmp(*argv, "--lpIQ") == 0) { option_lp |= LP_IQ; } // IQ/IF lowpass
else if (strcmp(*argv, "--lpbw") == 0) { // IQ lowpass BW / kHz
double bw = 0.0;
++argv;
if (*argv) bw = atof(*argv);
else return -1;
if (bw > 4.6 && bw < 24.0) lpIQ_bw = bw*1e3;
option_lp = 1;
option_lp |= LP_IQ;
}
else if (strcmp(*argv, "--lpFM") == 0) { option_lp |= LP_FM; } // FM lowpass
else if (strcmp(*argv, "--dc") == 0) { option_dc = 1; }
else if (strcmp(*argv, "--noLUT") == 0) { option_noLUT = 1; }
else if (strcmp(*argv, "--min") == 0) {
option_min = 1;
}
else if (strcmp(*argv, "--dbg") == 0) { gpx.option.dbg = 1; }
else if (strcmp(*argv, "--sat") == 0) { gpx.option.sat = 1; }
else if (strcmp(*argv, "--rawhex") == 0) { rawhex = 1; } // raw hex input
else if (strcmp(*argv, "-") == 0) {
int sample_rate = 0, bits_sample = 0, channels = 0;
++argv;
@ -1119,6 +1267,14 @@ int main(int argc, char **argv) {
}
if (!wavloaded) fp = stdin;
if (option_iq == 5 && option_dc) option_lp |= LP_FM;
// LUT faster for decM, however frequency correction after decimation
// LUT recommonded if decM > 2
//
if (option_noLUT && option_iq == 5) dsp.opt_nolut = 1; else dsp.opt_nolut = 0;
// ecc2-soft_decision accepts also 2-error words,
// so the probability for 3 errors is high and will
// produce wrong codewords. hence ecc2 is not recommended
@ -1167,6 +1323,9 @@ int main(int argc, char **argv) {
}
#endif
if (!rawhex) {
if (!option_bin && !option_softin) {
if (option_iq == 0 && option_pcmraw) {
@ -1339,8 +1498,8 @@ int main(int argc, char **argv) {
bitpos += 1;
}
ret = print_frame(&gpx);
if (pos < BITFRAME_LEN) break;
ret = print_frame(&gpx);
pos = 0;
frm += 1;
//if (ret < 0) frms += 1;
@ -1356,6 +1515,65 @@ int main(int argc, char **argv) {
else {
if (hdb.buf) { free(hdb.buf); hdb.buf = NULL; }
}
}
else //if (rawhex)
{
#define BUFLEN (BITFRAME_LEN/4+12)
char buffer_rawhex[BUFLEN+1]; // no header
char *pbuf = NULL;
int len, i, j;
int ch = 0;
int pos = 0;
float _frmcnt = -1.0f;
memset(buffer_rawhex, BUFLEN+1, 0);
while ( (ch=fgetc(fp)) != EOF)
{
if (ch == ' ') continue;
if (ch == '\n') {
buffer_rawhex[pos] = '\0';
//
// +/-<gpx._frmcnt> gpx.frame[].hb ...
//
gpx.option.inv = buffer_rawhex[0]=='-' ? 1 : 0;
sscanf(buffer_rawhex+1, "<%f>", &_frmcnt);
pbuf = strchr(buffer_rawhex, '>');
if (pbuf != NULL)
{
pbuf++;
len = strlen(pbuf);
if (len*4 == BITFRAME_LEN-CONF) {
gpx._frmcnt = _frmcnt;
for (i = 0; i < len; i++) {
ui8_t nib = 0xFF;
sscanf(pbuf+i, "%1hhx", &nib);
for (j = 0; j < 4; j++) {
gpx.frame[CONF+4*i+j].hb = (nib>>j) & 0x1; // little endian
gpx.frame[CONF+4*i+j].sb = 2*gpx.frame[CONF+4*i+j].hb - 1;
}
}
print_frame(&gpx);
}
}
pos = 0;
}
else {
if ( ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F'
|| ch == '+' || ch == '-' || ch == '<' || ch == '.' || ch == '>') {
if (pos < BUFLEN) {
buffer_rawhex[pos] = ch;
pos++;
}
}
else {
// frame error
}
}
}
}
fclose(fp);