Merge pull request #782 from projecthorus/testing

v1.6.2 release
pull/809/head v1.6.2
Mark Jessop 2023-08-04 10:16:07 +09:30 zatwierdzone przez GitHub
commit dbd5ed7325
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
27 zmienionych plików z 421 dodań i 169 usunięć

1
.gitignore vendored
Wyświetl plik

@ -41,6 +41,7 @@ auto_rx/dfm09mod
auto_rx/dft_detect
auto_rx/fsk_demod
auto_rx/imet1rs_dft
auto_rx/iq_dec
auto_rx/lms6Xmod
auto_rx/lms6mod
auto_rx/m10mod

Wyświetl plik

@ -8,6 +8,16 @@
# Refer github page for instructions on setup and usage.
# https://github.com/projecthorus/radiosonde_auto_rx/
#
# exit status codes:
#
# 0 - normal termination (ctrl-c)
# 1 - critical error, needs human attention to fix
# 2 - exit because continous running timeout reached
# 3 - exception occurred, can rerun after resetting SDR
# 4 - some of the threads failed to join, SDR reset and restart required
# this is mostly caused by hung external utilities
import argparse
import datetime
import logging
@ -44,6 +54,7 @@ from autorx.web import (
start_flask,
stop_flask,
flask_emit_event,
flask_running,
WebHandler,
WebExporter,
)
@ -322,7 +333,7 @@ def handle_scan_results():
if (type(_key) == int) or (type(_key) == float):
# Extract the currently decoded sonde type from the currently running decoder.
_decoding_sonde_type = autorx.task_list[_key]["task"].sonde_type
# Remove any inverted decoder information for the comparison.
if _decoding_sonde_type.startswith("-"):
_decoding_sonde_type = _decoding_sonde_type[1:]
@ -806,6 +817,11 @@ def main():
logging.getLogger("engineio").setLevel(logging.ERROR)
logging.getLogger("geventwebsocket").setLevel(logging.ERROR)
# Check all the RS utilities exist.
logging.debug("Checking if utils exist")
if not check_rs_utils():
sys.exit(1)
# Attempt to read in config file
logging.info("Reading configuration file...")
_temp_cfg = read_auto_rx_config(args.config)
@ -844,9 +860,6 @@ def main():
web_handler = WebHandler()
logging.getLogger().addHandler(web_handler)
# Check all the RS utilities exist.
if not check_rs_utils():
sys.exit(1)
# If a sonde type has been provided, insert an entry into the scan results,
# and immediately start a decoder. This also sets the decoder time to 0, which
@ -897,6 +910,7 @@ def main():
),
launch_notifications=config["email_launch_notifications"],
landing_notifications=config["email_landing_notifications"],
encrypted_sonde_notifications=config["email_encrypted_sonde_notifications"],
landing_range_threshold=config["email_landing_range_threshold"],
landing_altitude_threshold=config["email_landing_altitude_threshold"],
)
@ -1073,7 +1087,7 @@ def main():
logging.info("Shutdown time reached. Closing.")
stop_flask(host=config["web_host"], port=config["web_port"])
stop_all()
break
sys.exit(2)
if __name__ == "__main__":
@ -1084,9 +1098,13 @@ if __name__ == "__main__":
# Upon CTRL+C, shutdown all threads and exit.
stop_flask(host=config["web_host"], port=config["web_port"])
stop_all()
sys.exit(0)
except Exception as e:
# Upon exceptions, attempt to shutdown threads and exit.
traceback.print_exc()
print("Main Loop Error - %s" % str(e))
stop_flask(host=config["web_host"], port=config["web_port"])
if flask_running():
stop_flask(host=config["web_host"], port=config["web_port"])
stop_all()
sys.exit(3)

Wyświetl plik

@ -6,7 +6,9 @@
# NOTE: If running this from crontab, make sure to set the appropriate PATH env-vars,
# else utilities like rtl_power and rtl_fm won't be found.
#
# WARNING - THIS IS DEPRECATED - PLEASE USE THE SYSTEMD SERVICE
# WARNING - THIS IS DEPRECATED - PLEASE USE THE SYSTEMD SERVICE OR DOCKER IMAGE
# See: https://github.com/projecthorus/radiosonde_auto_rx/wiki#451-option-1---operation-as-a-systemd-service-recommended
# Or: https://github.com/projecthorus/radiosonde_auto_rx/wiki/Docker
#
# change into appropriate directory
@ -15,7 +17,4 @@ cd $(dirname $0)
# Clean up old files
rm log_power*.csv
# Start auto_rx process with a 3 hour timeout.
# auto_rx will exit after this time.
python3 auto_rx.py -t 180
python3 auto_rx.py -t 180

Wyświetl plik

@ -12,7 +12,7 @@ from queue import Queue
# 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.1"
__version__ = "1.6.2"
# Global Variables

Wyświetl plik

@ -759,13 +759,19 @@ class APRSUploader(object):
# Wait for all threads to close.
if self.upload_thread is not None:
self.upload_thread.join()
self.upload_thread.join(60)
if self.upload_thread.is_alive():
self.log_error("aprs upload thread failed to join")
if self.timer_thread is not None:
self.timer_thread.join()
self.timer_thread.join(60)
if self.timer_thread.is_alive():
self.log_error("aprs timer thread failed to join")
if self.input_thread is not None:
self.input_thread.join()
self.input_thread.join(60)
if self.input_thread.is_alive():
self.log_error("aprs input thread failed to join")
def log_debug(self, line):
""" Helper function to log a debug message with a descriptive heading.

Wyświetl plik

@ -683,8 +683,8 @@ def read_auto_rx_config(filename, no_sdr_test=False):
# 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]
ALLOWED_APRS_SERVERS = ["radiosondy.info", "wettersonde.net", "localhost"]
ALLOWED_APRS_PORTS = [14580, 14590]
if auto_rx_config["aprs_server"] not in ALLOWED_APRS_SERVERS:
logging.warning(
@ -748,6 +748,17 @@ def read_auto_rx_config(filename, no_sdr_test=False):
"Config - Did not find system / debug logging options, using defaults (disabled, unless set as a command-line option.)"
)
# 1.6.2 - Encrypted Sonde Email Notifications
try:
auto_rx_config["email_encrypted_sonde_notifications"] = config.getboolean(
"email", "encrypted_sonde_notifications"
)
except:
logging.warning(
"Config - Did not find encrypted_sonde_notifications setting (new in v1.6.2), using default (True)"
)
auto_rx_config["email_encrypted_sonde_notifications"] = True
# If we are being called as part of a unit test, just return the config now.
if no_sdr_test:
@ -872,7 +883,7 @@ def read_auto_rx_config(filename, no_sdr_test=False):
if len(auto_rx_config["sdr_settings"].keys()) == 0:
# We have no SDRs to use!!
logging.error("Config - No working SDRs! Cannot run...")
return None
raise SystemError("No working SDRs!")
else:
# Create a global copy of the configuration file at this point
global_config = copy.deepcopy(auto_rx_config)
@ -891,7 +902,8 @@ def read_auto_rx_config(filename, no_sdr_test=False):
web_password = auto_rx_config["web_password"]
return auto_rx_config
except SystemError as e:
raise e
except:
traceback.print_exc()
logging.error("Could not parse config file.")

Wyświetl plik

@ -23,6 +23,7 @@ from .gps import get_ephemeris, get_almanac
from .sonde_specific import fix_datetime, imet_unique_id
from .fsk_demod import FSKDemodStats
from .sdr_wrappers import test_sdr, get_sdr_iq_cmd, get_sdr_fm_cmd, get_sdr_name
from .email_notification import EmailNotification
# Global valid sonde types list.
VALID_SONDE_TYPES = [
@ -1424,6 +1425,20 @@ class SondeDecoder(object):
"Radiosonde %s has encrypted telemetry (Possible encrypted RS41-SGM)! We cannot decode this, closing decoder."
% _telemetry["id"]
)
# Overwrite the datetime field to make the email notifier happy
_telemetry['datetime_dt'] = datetime.datetime.utcnow()
_telemetry["freq"] = "%.3f MHz" % (self.sonde_freq / 1e6)
# Send this to only the Email Notifier, if it exists.
for _exporter in self.exporters:
try:
if _exporter.__self__.__module__ == EmailNotification.__module__:
_exporter(_telemetry)
except Exception as e:
self.log_error("Exporter Error %s" % str(e))
# Close the decoder.
self.exit_state = "Encrypted"
self.decoder_running = False
return False

Wyświetl plik

@ -43,6 +43,7 @@ class EmailNotification(object):
station_position=None,
launch_notifications=True,
landing_notifications=True,
encrypted_sonde_notifications=True,
landing_range_threshold=50,
landing_altitude_threshold=1000,
landing_descent_trip=10,
@ -60,6 +61,7 @@ class EmailNotification(object):
self.station_position = station_position
self.launch_notifications = launch_notifications
self.landing_notifications = landing_notifications
self.encrypted_sonde_notifications = encrypted_sonde_notifications
self.landing_range_threshold = landing_range_threshold
self.landing_altitude_threshold = landing_altitude_threshold
self.landing_descent_trip = landing_descent_trip
@ -119,6 +121,7 @@ class EmailNotification(object):
self.sondes[_id] = {
"last_time": time.time(),
"descending_trip": 0,
"ascent_trip": False,
"descent_notified": False,
"track": GenericTrack(max_elements=20),
}
@ -133,18 +136,44 @@ class EmailNotification(object):
}
)
if self.launch_notifications:
if "encrypted" in telemetry:
if telemetry["encrypted"] and self.encrypted_sonde_notifications:
try:
# This is a new Encrypted Radiosonde, send an email.
msg = "Encrypted Radiosonde Detected:\n"
msg += "\n"
if "subtype" in telemetry:
telemetry["type"] = telemetry["subtype"]
msg += "Serial: %s\n" % _id
msg += "Type: %s\n" % telemetry["type"]
msg += "Frequency: %s\n" % telemetry["freq"]
msg += "Time Detected: %sZ\n" % telemetry["datetime_dt"].isoformat()
# Construct subject
_subject = self.mail_subject
_subject = _subject.replace("<id>", telemetry["id"])
_subject = _subject.replace("<type>", telemetry["type"])
_subject = _subject.replace("<freq>", telemetry["freq"])
if "encrypted" in telemetry:
if telemetry["encrypted"] == True:
_subject += " - ENCRYPTED SONDE"
self.send_notification_email(subject=_subject, message=msg)
except Exception as e:
self.log_error("Error sending E-mail - %s" % str(e))
elif self.launch_notifications:
try:
# This is a new sonde. Send the email.
msg = "Sonde launch detected:\n"
msg += "\n"
if "encrypted" in telemetry:
if telemetry["encrypted"] == True:
msg += "ENCRYPTED RADIOSONDE DETECTED!\n"
msg += "Callsign: %s\n" % _id
msg += "Serial: %s\n" % _id
msg += "Type: %s\n" % telemetry["type"]
msg += "Frequency: %s\n" % telemetry["freq"]
msg += "Position: %.5f,%.5f\n" % (
@ -175,10 +204,6 @@ class EmailNotification(object):
_subject = _subject.replace("<type>", telemetry["type"])
_subject = _subject.replace("<freq>", telemetry["freq"])
if "encrypted" in telemetry:
if telemetry["encrypted"] == True:
_subject += " - ENCRYPTED SONDE"
self.send_notification_email(subject=_subject, message=msg)
except Exception as e:
@ -200,14 +225,21 @@ class EmailNotification(object):
# We have seen this sonde recently. Let's check it's descending...
if self.sondes[_id]["descent_notified"] == False and _sonde_state:
# Set a flag if the sonde has passed above the landing altitude threshold.
# This is used along with the descending trip to trigger a landing email notification.
if (telemetry["alt"] > self.landing_altitude_threshold):
self.sondes[_id]["ascent_trip"] = True
# If the sonde is below our threshold altitude, *and* is descending at a reasonable rate, increment.
if (telemetry["alt"] < self.landing_altitude_threshold) and (
_sonde_state["ascent_rate"] < -2.0
):
self.sondes[_id]["descending_trip"] += 1
if self.sondes[_id]["descending_trip"] > self.landing_descent_trip:
# We've seen this sonde descending for enough time now.
if (self.sondes[_id]["descending_trip"] > self.landing_descent_trip) and self.sondes[_id]["ascent_trip"]:
# We've seen this sonde descending for enough time now AND we have also seen it go above the landing threshold,
# so it's likely been on a flight and isnt just bouncing around on the ground.
# Note that we've passed the descent threshold, so we shouldn't analyze anything from this sonde anymore.
self.sondes[_id]["descent_notified"] = True
@ -237,7 +269,7 @@ class EmailNotification(object):
msg = "Nearby sonde landing detected:\n\n"
msg += "Callsign: %s\n" % _id
msg += "Serial: %s\n" % _id
msg += "Type: %s\n" % telemetry["type"]
msg += "Frequency: %s\n" % telemetry["freq"]
msg += "Position: %.5f,%.5f\n" % (
@ -347,7 +379,9 @@ class EmailNotification(object):
self.input_processing_running = False
if self.input_thread is not None:
self.input_thread.join()
self.input_thread.join(60)
if self.input_thread.is_alive():
self.log_error("email notification input thread failed to join")
def running(self):
""" Check if the logging thread is running.
@ -433,6 +467,29 @@ if __name__ == "__main__":
}
)
time.sleep(10)
print("Testing encrypted sonde alert.")
_email_notification.add(
{
"id": "R1234557",
"frame": 10,
"lat": 0.0,
"lon": 0.0,
"alt": 0,
"temp": 1.0,
"type": "RS41",
"subtype": "RS41-SGM",
"freq": "401.520 MHz",
"freq_float": 401.52,
"heading": 0.0,
"vel_h": 5.1,
"vel_v": -5.0,
"datetime_dt": datetime.datetime.utcnow(),
"encrypted": True
}
)
# Wait a little bit before shutting down.
time.sleep(5)

Wyświetl plik

@ -335,7 +335,9 @@ class GPSDAdaptor(object):
self.gpsd_thread_running = False
# Wait for the thread to close.
if self.gpsd_thread != None:
self.gpsd_thread.join()
self.gpsd_thread.join(60)
if self.gpsd_thread.is_alive():
logging.error("GPS thread failed to join")
def send_to_callback(self, data):
"""

Wyświetl plik

@ -831,13 +831,20 @@ class HabitatUploader(object):
# Wait for all threads to close.
if self.upload_thread is not None:
self.upload_thread.join()
self.upload_thread.join(60)
if self.upload_thread.is_alive():
self.log_error("habitat upload thread failed to join")
if self.timer_thread is not None:
self.timer_thread.join()
self.timer_thread.join(60)
if self.timer_thread.is_alive():
self.log_error("habitat timer thread failed to join")
if self.input_thread is not None:
self.input_thread.join()
self.input_thread.join(60)
if self.input_thread.is_alive():
self.log_error("habitat input thread failed to join")
def log_debug(self, line):
""" Helper function to log a debug message with a descriptive heading.

Wyświetl plik

@ -455,9 +455,8 @@ def calculate_skewt_data(
break
except Exception as e:
print(str(e))
# Continue through the data..
logging.exception(f"Exception {str(e)} in calculate_skewt_data")
raise
return _skewt

Wyświetl plik

@ -252,7 +252,9 @@ class OziUploader(object):
self.input_processing_running = False
if self.input_thread is not None:
self.input_thread.join()
self.input_thread.join(60)
if self.input_thread.is_alive():
self.log_error("ozimux input thread failed to join")
def log_debug(self, line):
""" Helper function to log a debug message with a descriptive heading.

Wyświetl plik

@ -320,7 +320,9 @@ class Rotator(object):
self.rotator_thread_running = False
if self.rotator_thread is not None:
self.rotator_thread.join()
self.rotator_thread.join(60)
if self.rotator_thread.is_alive():
self.log_error("rotator control thread failed to join")
self.log_debug("Stopped rotator control thread.")

Wyświetl plik

@ -9,6 +9,7 @@ import datetime
import logging
import numpy as np
import os
import sys
import platform
import subprocess
import time
@ -22,6 +23,7 @@ from .utils import (
reset_rtlsdr_by_serial,
reset_all_rtlsdrs,
peak_decimation,
timeout_cmd
)
from .sdr_wrappers import test_sdr, reset_sdr, get_sdr_name, get_sdr_iq_cmd, get_sdr_fm_cmd, get_power_spectrum
@ -91,18 +93,10 @@ def run_rtl_power(
if os.path.exists(filename):
os.remove(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).
if "Darwin" in platform.platform():
timeout_kill = ""
else:
timeout_kill = "-k 30 "
rtl_power_cmd = (
"timeout %s%d %s %s-f %d:%d:%d -i %d -1 -c 25%% -p %d -d %s %s%s"
"%s %d %s %s-f %d:%d:%d -i %d -1 -c 25%% -p %d -d %s %s%s"
% (
timeout_kill,
timeout_cmd(),
dwell + 10,
rtl_power_path,
bias_option,
@ -314,7 +308,7 @@ def detect_sonde(
if _mode == "IQ":
# IQ decoding
rx_test_command = f"timeout {dwell_time * 2} "
rx_test_command = f"{timeout_cmd()} {dwell_time * 2} "
rx_test_command += get_sdr_iq_cmd(
sdr_type=sdr_type,
@ -331,8 +325,9 @@ def detect_sonde(
)
# rx_test_command = (
# "timeout %ds %s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |"
# "%s %ds %s %s-p %d -d %s %s-M raw -F9 -s %d -f %d 2>/dev/null |"
# % (
# timeout_cmd(),
# dwell_time * 2,
# rtl_fm_path,
# bias_option,
@ -360,7 +355,7 @@ def detect_sonde(
# Sample Source (rtl_fm)
rx_test_command = f"timeout {dwell_time * 2} "
rx_test_command = f"{timeout_cmd()} {dwell_time * 2} "
rx_test_command += get_sdr_fm_cmd(
sdr_type=sdr_type,
@ -379,8 +374,9 @@ def detect_sonde(
)
# rx_test_command = (
# "timeout %ds %s %s-p %d -d %s %s-M fm -F9 -s %d -f %d 2>/dev/null |"
# "%s %ds %s %s-p %d -d %s %s-M fm -F9 -s %d -f %d 2>/dev/null |"
# % (
# timeout_cmd(),
# dwell_time * 2,
# rtl_fm_path,
# bias_option,
@ -783,9 +779,9 @@ class SondeScanner(object):
def start(self):
# Start the scan loop (if not already running)
if self.sonde_scan_thread is None:
self.sonde_scanner_running = True
self.sonde_scan_thread = Thread(target=self.scan_loop)
self.sonde_scan_thread.start()
self.sonde_scanner_running = True
else:
self.log_warning("Sonde scan already running!")
@ -854,22 +850,32 @@ class SondeScanner(object):
sdr_hostname = self.sdr_hostname,
sdr_port = self.sdr_port
)
time.sleep(10)
for _ in range(10):
if not self.sonde_scanner_running:
break
time.sleep(1)
continue
except Exception as e:
traceback.print_exc()
self.log_error("Caught other error: %s" % str(e))
time.sleep(10)
for _ in range(10):
if not self.sonde_scanner_running:
break
time.sleep(1)
else:
# Scan completed successfuly! Reset the error counter.
self.error_retries = 0
# Sleep before starting the next scan.
time.sleep(self.scan_delay)
for _ in range(self.scan_delay):
if not self.sonde_scanner_running:
self.log_debug("Breaking out of scan loop.")
break
time.sleep(1)
self.log_info("Scanner Thread Closed.")
self.sonde_scanner_running = False
self.sonde_scanner_thread = None
def sonde_search(self, first_only=False):
"""Perform a frequency scan across a defined frequency range, and test each detected peak for the presence of a radiosonde.
@ -1139,12 +1145,16 @@ class SondeScanner(object):
def stop(self, nowait=False):
"""Stop the Scan Loop"""
self.log_info("Waiting for current scan to finish...")
self.sonde_scanner_running = False
if self.sonde_scanner_running:
self.log_info("Waiting for current scan to finish...")
self.sonde_scanner_running = False
# Wait for the sonde scanner thread to close, if there is one.
if self.sonde_scan_thread != None and (not nowait):
self.sonde_scan_thread.join()
# Wait for the sonde scanner thread to close, if there is one.
if self.sonde_scan_thread != None and (not nowait):
self.sonde_scan_thread.join(60)
if self.sonde_scan_thread.is_alive():
self.log_error("Scanning thread did not finish, terminating")
sys.exit(4)
def running(self):
"""Check if the scanner is running"""

Wyświetl plik

@ -11,7 +11,7 @@ import platform
import subprocess
import numpy as np
from .utils import rtlsdr_test, reset_rtlsdr_by_serial, reset_all_rtlsdrs
from .utils import rtlsdr_test, reset_rtlsdr_by_serial, reset_all_rtlsdrs, timeout_cmd
def test_sdr(
@ -67,7 +67,7 @@ def test_sdr(
return False
_cmd = (
f"timeout 10 " # Add a timeout, because connections to non-existing IPs seem to block.
f"{timeout_cmd()} 10 " # Add a timeout, because connections to non-existing IPs seem to block.
f"{ss_iq_path} "
f"-f {check_freq} "
f"-s 48000 "
@ -480,17 +480,7 @@ def get_power_spectrum(
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}"
_timeout_cmd = f"{timeout_cmd()} {integration_time+10} "
_gain = ""
if gain:
@ -564,17 +554,7 @@ def get_power_spectrum(
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}"
_timeout_cmd = f"{timeout_cmd()} {integration_time+10} "
_frequency_centre = int(frequency_start + (frequency_stop-frequency_start)/2.0)

Wyświetl plik

@ -60,3 +60,18 @@
.icon-cog:before { content: '\e802'; } /* '' */
.icon-angle-down:before { content: '\f107'; } /* '' */
.icon-history:before { content: '\f1da'; } /* '' */
#task_status {
display: flex;
flex-wrap: wrap;
gap: 2px;
}
.sdrinfo-element {
margin: 0px 4px;
padding: 4px;
border: 2px solid rgb(135, 135, 135);
border-radius: 1px;
}

Wyświetl plik

@ -2,7 +2,7 @@
function update_task_list(){
// Grab the latest task list.
$.getJSON("/get_task_list", function(data){
$.getJSON("get_task_list", function(data){
var task_info = "";
$('#stop-frequency-select').children().remove();
@ -10,8 +10,14 @@ function update_task_list(){
added_decoders = false;
for (_task in data){
// Append the current task to the task list text.
task_info += "SDR #" + _task + ": " + data[_task]["task"] + " ";
// Append the current task to the task list.
if(_task.includes("SPY")){
task_detail = _task + " - "
}else{
task_detail = "SDR:" + _task + " - "
}
if(data[_task]["freq"] > 0.0){
$('#stop-frequency-select')
.append($("<option></option>")
@ -19,7 +25,22 @@ function update_task_list(){
.text( (parseFloat( data[_task]["freq"] )/1e6).toFixed(3)));
added_decoders = true;
task_detail += (parseFloat( data[_task]["freq"] )/1e6).toFixed(3);
if (data[_task].hasOwnProperty("type")){
task_detail += " " + data[_task]["type"];
}
} else {
if(data[_task]["task"] == "Scanning"){
task_detail += "Scan";
} else {
task_detail += "Idle";
}
}
task_info += "<div class='sdrinfo-element'>" + task_detail + "</div>"
}
if(added_decoders == false){
@ -30,7 +51,7 @@ function update_task_list(){
}
// Update page with latest task.
$('#task_status').text(task_info);
$('#task_status').html(task_info);
setTimeout(resume_web_controls,2000);
});
@ -96,7 +117,7 @@ function verify_password(){
// Do the request
$.post(
"/check_password",
"check_password",
{"password": _api_password},
function(data){
// If OK, update the header to indicate the password was OK.
@ -125,7 +146,7 @@ function disable_scanner(){
// Do the request
$.post(
"/disable_scanner",
"disable_scanner",
{"password": _api_password},
function(data){
//console.log(data);
@ -162,7 +183,7 @@ function enable_scanner(){
// Do the request
$.post(
"/enable_scanner",
"enable_scanner",
{"password": _api_password},
function(data){
//console.log(data);
@ -194,7 +215,7 @@ function stop_decoder(){
// Do the request
$.post(
"/stop_decoder",
"stop_decoder",
{password: _api_password, freq: _decoder},
function(data){
//console.log(data);
@ -245,7 +266,7 @@ function start_decoder(){
// Do the request
$.post(
"/start_decoder",
"start_decoder",
{password: _api_password, freq: _freq_hz, type: _type},
function(data){
alert("Added requested decoder to results queue.")
@ -259,4 +280,4 @@ function start_decoder(){
$("#password-header").html("<h2>Incorrect Password</h2>");
}
});
}
}

Wyświetl plik

@ -16,12 +16,12 @@ var sondeDescentIcons = {};
// TODO: Make these /static URLS be filled in with templates (or does it not matter?)
for (_col in colour_values){
sondeAscentIcons[colour_values[_col]] = L.icon({
iconUrl: "/static/img/balloon-" + colour_values[_col] + '.png',
iconUrl: "static/img/balloon-" + colour_values[_col] + '.png',
iconSize: [46, 85],
iconAnchor: [23, 76]
});
sondeDescentIcons[colour_values[_col]] = L.icon({
iconUrl: "/static/img/parachute-" + colour_values[_col] + '.png',
iconUrl: "static/img/parachute-" + colour_values[_col] + '.png',
iconSize: [46, 84],
iconAnchor: [23, 76]
});

Wyświetl plik

@ -46,12 +46,13 @@
quickLandings = false;
namespace = '/update_status';
var socket_path = "{{ url_for("static", filename="") }}".replace('static/', 'socket.io')
var socket = io.connect(location.origin+namespace, {'path': socket_path});
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
$.ajax({
// Get station.cfg file.
url: "/get_config",
url: "get_config",
dataType: 'json',
async: false,
success: function(data) {
@ -64,7 +65,7 @@
$.ajax({
// Get list of sonde.
url: "/get_log_list",
url: "get_log_list",
dataType: 'json',
async: false,
success: function(data) {
@ -462,7 +463,7 @@
highest = i;
}
$.ajax({
url: "/get_log_by_serial/" + selectedrows[i]['serial'],
url: "get_log_by_serial/" + selectedrows[i]['serial'],
dataType: 'json',
async: true,
success: function(data) {
@ -731,7 +732,7 @@
table.selectRow();
mymap.eachLayer(function(layer){
try {
if (layer['options']['icon']['options']['iconUrl'] == "/static/img/landing_marker.png" || layer['options']['icon']['options']['iconUrl'] == "/static/img/launch_marker.png") {
if (layer['options']['icon']['options']['iconUrl'] == "static/img/landing_marker.png" || layer['options']['icon']['options']['iconUrl'] == "static/img/launch_marker.png") {
new_icon = layer['options']['icon'];
new_icon.options.iconSize = [20, 20];
new_icon.options.iconAnchor = [10, 10];
@ -746,7 +747,7 @@
table.deselectRow();
mymap.eachLayer(function(layer){
try {
if (layer['options']['icon']['options']['iconUrl'] == "/static/img/landing_marker.png" || layer['options']['icon']['options']['iconUrl'] == "/static/img/launch_marker.png") {
if (layer['options']['icon']['options']['iconUrl'] == "static/img/landing_marker.png" || layer['options']['icon']['options']['iconUrl'] == "static/img/launch_marker.png") {
new_icon = layer['options']['icon'];
new_icon.options.iconSize = [15, 15];
new_icon.options.iconAnchor = [7.5, 7.5];
@ -770,7 +771,7 @@
mymap.eachLayer(function(layer){
try {
if (layer['options']['icon']['options']['iconUrl'] == "/static/img/landing_marker.png" || layer['options']['icon']['options']['iconUrl'] == "/static/img/launch_marker.png") {
if (layer['options']['icon']['options']['iconUrl'] == "static/img/landing_marker.png" || layer['options']['icon']['options']['iconUrl'] == "static/img/launch_marker.png") {
if (layer['options']['icon']['options']['iconSize'][0] == 15) {
if (!shown.includes(layer['options']['title'])) {
mymap.removeLayer(layer);
@ -857,7 +858,7 @@
_serial = selectedrows[selectedrows.length-1]['serial'];
_type = selectedrows[selectedrows.length-1]['type'];
$.post(
"/get_log_detail",
"get_log_detail",
{serial: _serial, decimation:decimation},
async function(data){
try {
@ -947,13 +948,13 @@
if(_serial_list.length == table.getData().length){
// Request all log files
window.open("/export_all_log_files" , '_blank');
window.open("export_all_log_files" , '_blank');
}else {
// Just request the selected ones.
// Convert the list to JSON, and then to base64
b64 = btoa(JSON.stringify(_serial_list));
// Make the request in a new tab
window.open("/export_log_files/"+b64 , '_blank');
window.open("export_log_files/"+b64 , '_blank');
}
}
}
@ -1529,4 +1530,4 @@
</div>
</div>
</body>
</html>
</html>

Wyświetl plik

@ -99,13 +99,13 @@
$( document ).ready(function() {
namespace = '/update_status';
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
namespace = '/update_status';
var socket_path = "{{ url_for("static", filename="") }}".replace('static/', 'socket.io')
var socket = io.connect(location.origin+namespace, {'path': socket_path});
$.ajax({
// Get station.cfg file.
url: "/get_config",
url: "get_config",
dataType: 'json',
async: false,
success: function(data) {
@ -129,7 +129,7 @@
$.ajax({
// Get local version number.
url: "/get_version",
url: "get_version",
dataType: 'json',
async: true,
success: function(data) {
@ -196,7 +196,7 @@
socket.on('scan_event', function(msg) {
// There is Scan data ready for us!
// Grab the latest set of data.
$.getJSON("/get_scan_data", function(data){
$.getJSON("get_scan_data", function(data){
// Load the data into our data stores.
scan_chart_spectra.columns[0] = ['x_spectra'].concat(data.freq);
scan_chart_spectra.columns[1] = ['Spectra'].concat(data.power);
@ -736,7 +736,7 @@
var initial_load_complete = false;
selected_sonde = "";
$.ajax({ // Get archived data.
url: "/get_telemetry_archive",
url: "get_telemetry_archive",
dataType: 'json',
async: true,
success: function(data) {
@ -1571,7 +1571,7 @@
<h2 style="display:inline;vertical-align:middle;">Live KML</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<button onclick="window.location.href='/rs.kml'">SHOW</button>
<button onclick="window.location.href='rs.kml'">SHOW</button>
</div>
<br>
<br>
@ -1592,7 +1592,7 @@
<h2 style="display:inline;vertical-align:middle;">Historical View</h2>
&nbsp;
<div style="display:inline;vertical-align:middle;">
<button onclick="window.location.href='/historical.html'">OPEN</button>
<button onclick="window.location.href='historical.html'">OPEN</button>
</div>
<br>
<br>
@ -1611,7 +1611,7 @@
</div>
<span style="font-size:2vh;font-size:calc(var(--vh, 1vh) * 2);" id="footertext"></span>
<p style="font-size:2vh;font-size:calc(var(--vh, 1vh) * 2);">Station: <span id="station_callsign">???</span></p>
<p style="font-size:2vh;font-size:calc(var(--vh, 1vh) * 2);">Current Task: <span id="task_status">???</span></p>
<p style="font-size:2vh;font-size:calc(var(--vh, 1vh) * 2);">Tasking: <span id="task_status"></span></p>
<div id="tableid">
<div id="telem_table"></div>
</div>

Wyświetl plik

@ -59,7 +59,7 @@
_decim = $('#decimation-input').val();
$.post(
"/get_log_detail",
"get_log_detail",
{serial: _serial, decimation:_decim},
function(data){
console.log(data);

Wyświetl plik

@ -19,6 +19,7 @@ import threading
import time
import numpy as np
import semver
import shutil
from dateutil.parser import parse
from datetime import datetime, timedelta
from math import radians, degrees, sin, cos, atan2, sqrt, pi
@ -45,6 +46,21 @@ REQUIRED_RS_UTILS = [
"iq_dec"
]
_timeout_cmd = None
def timeout_cmd():
global _timeout_cmd
if not _timeout_cmd:
t=shutil.which("gtimeout")
if t:
_timeout_cmd = "gtimeout -k 30 "
else:
if not shutil.which("timeout"):
logging.critical("timeout command-line tool not present in system. try installing gtimeout.")
sys.exit(1)
else:
_timeout_cmd = "timeout -k 30 "
return _timeout_cmd
def check_rs_utils():
""" Check the required RS decoder binaries exist
@ -54,7 +70,7 @@ def check_rs_utils():
if not os.path.isfile(_file):
logging.critical("Binary %s does not exist - did you run build.sh?" % _file)
return False
_ = timeout_cmd()
return True
@ -776,10 +792,10 @@ def is_rtlsdr(vid, pid):
def reset_rtlsdr_by_serial(serial):
""" Attempt to reset a RTLSDR with a provided serial number """
# If not Linux, return immediately.
# If not Linux, raise exception and let auto_rx.py convert it to exit status code.
if is_not_linux():
logging.debug("RTLSDR - Not a native Linux system, skipping reset attempt.")
return
raise SystemError("SDR unresponsive")
lsusb_info = lsusb()
bus_num = None
@ -853,10 +869,10 @@ def find_rtlsdr(serial=None):
def reset_all_rtlsdrs():
""" Reset all RTLSDR devices found in the lsusb tree """
# If not Linux, return immediately.
# If not Linux, raise exception and let auto_rx.py convert it to exit status code.
if is_not_linux():
logging.debug("RTLSDR - Not a native Linux system, skipping reset attempt.")
return
raise SystemError("SDR unresponsive")
lsusb_info = lsusb()
bus_num = None
@ -906,11 +922,12 @@ def rtlsdr_test(device_idx="0", rtl_sdr_path="rtl_sdr", retries=5):
logging.debug("RTLSDR - TCP Device, skipping RTLSDR test step.")
return True
_rtl_cmd = "timeout 5 %s -d %s -n 200000 - > /dev/null" % (
_rtl_cmd = "%s 5 %s -d %s -f 400000000 -n 200000 - > /dev/null" % (
timeout_cmd(),
rtl_sdr_path,
str(device_idx),
)
# First, check if the RTLSDR with a provided serial number is present.
if device_idx == "0":
# Check for the presence of any RTLSDRs.

Wyświetl plik

@ -21,11 +21,13 @@ import autorx.scan
from autorx.geometry import GenericTrack
from autorx.utils import check_autorx_versions
from autorx.log_files import list_log_files, read_log_by_serial, zip_log_files
from autorx.decode import SondeDecoder
from queue import Queue
from threading import Thread
import flask
from flask import request, abort, make_response, send_file
from flask_socketio import SocketIO
from werkzeug.middleware.proxy_fix import ProxyFix
import re
try:
@ -43,13 +45,14 @@ cli.show_server_banner = lambda *x: None
# Instantiate our Flask app.
app = flask.Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_prefix=1)
app.config["SECRET_KEY"] = "secret!"
app.config["TEMPLATES_AUTO_RELOAD"] = True
app.jinja_env.auto_reload = True
# This thread will hold the currently running flask application thread.
flask_app_thread = None
# A key that needs to be matched to allow shutdown.
flask_shutdown_key = "temp"
flask_shutdown_key = None
# SocketIO instance
socketio = SocketIO(app, async_mode="threading")
@ -105,6 +108,7 @@ def flask_get_version():
def flask_get_task_list():
""" Return the current list of active SDRs, and their active task names """
# Read in the task list, index by SDR ID.
_task_list = {}
for _task in autorx.task_list.keys():
@ -124,9 +128,16 @@ def flask_get_task_list():
"task": "Decoding (%.3f MHz)" % (_task_list[str(_sdr)] / 1e6),
"freq": _task_list[str(_sdr)],
}
except:
_sdr_list[str(_sdr)] = {"task": "Decoding (?? MHz)", "freq": 0}
# Try and add on sonde type.
try:
_sdr_list[str(_sdr)]['type'] = autorx.task_list[_task_list[str(_sdr)]]['task'].sonde_type
except:
pass
# Convert the task list to a JSON blob, and return.
return json.dumps(_sdr_list)
@ -139,7 +150,7 @@ def flask_get_kml():
kml = Kml()
netlink = kml.newnetworklink(name="Radiosonde Auto-RX Live Telemetry")
netlink.open = 1
netlink.link.href = flask.request.host_url + "rs_feed.kml"
netlink.link.href = flask.request.url_root + "rs_feed.kml"
try:
netlink.link.refreshinterval = _config["kml_refresh_rate"]
except KeyError:
@ -162,7 +173,7 @@ def flask_get_kml_feed():
description="AutoRX Ground Station",
)
pnt.open = 1
pnt.iconstyle.icon.href = flask.request.host_url + "static/img/antenna-green.png"
pnt.iconstyle.icon.href = flask.request.url_root + "static/img/antenna-green.png"
pnt.coords = [
(
autorx.config.global_config["station_lon"],
@ -183,15 +194,15 @@ def flask_get_kml_feed():
Altitude: {alt:.1f} m
Heading: {heading:.1f} degrees
Ground Speed: {vel_h:.2f} m/s
Ascent Rate: {vel_v:.2} m/s
Ascent Rate: {vel_v:.2f} m/s
Temperature: {temp:.1f} C
Humidity: {humidity:.1f} %
Pressure: {pressure:.1f} hPa
"""
if flask_telemetry_store[rs_id]["latest_telem"]["vel_v"] > -5:
icon = flask.request.host_url + "static/img/balloon-green.png"
icon = flask.request.url_root + "static/img/balloon-green.png"
else:
icon = flask.request.host_url + "static/img/parachute-green.png"
icon = flask.request.url_root + "static/img/parachute-green.png"
# Add folder
fol = kml.newfolder(name=rs_id)
@ -289,6 +300,9 @@ def flask_get_log_list():
""" Return a list of log files, as a list of objects """
return json.dumps(list_log_files(quicklook=True))
def flask_running():
global flask_shutdown_key
return flask_shutdown_key is not None
@app.route("/get_log_by_serial/<serial>")
def flask_get_log_by_serial(serial):

Wyświetl plik

@ -6,3 +6,4 @@ numpy
requests
semver
simplekml
simple-websocket

Wyświetl plik

@ -200,6 +200,9 @@ sondehub_enabled = True
# How often to push data to the SondeHub Database. (seconds)
# All received positions are cached and uploaded every X seconds.
# Uploads are gzip compressed, so don't require much data transfer.
# Users receiving Graw DFM sondes may want to set this to 30 to improve
# the chances of uploads not being rejected by our Z-check.
# (Refer: https://github.com/projecthorus/sondehub-infra/wiki/DFM-radiosonde-above-1000-and-not-enough-data-to-perform-z-check )
sondehub_upload_rate = 15
# An optional contact e-mail address.
@ -362,6 +365,9 @@ launch_notifications = True
# Send e-mails when a radiosonde is detected descending near your station location
landing_notifications = True
# Send e-mails when an encrypted radiosonde is detected.
encrypted_sonde_notifications = True
# Range threshold for Landing notifications (km from your station location)
landing_range_threshold = 30

Wyświetl plik

@ -201,6 +201,9 @@ sondehub_enabled = True
# How often to push data to the SondeHub Database. (seconds)
# All received positions are cached and uploaded every X seconds.
# Uploads are gzip compressed, so don't require much data transfer.
# Users receiving Graw DFM sondes may want to set this to 30 to improve
# the chances of uploads not being rejected by our Z-check.
# (Refer: https://github.com/projecthorus/sondehub-infra/wiki/DFM-radiosonde-above-1000-and-not-enough-data-to-perform-z-check )
sondehub_upload_rate = 15
# An optional contact e-mail address.
@ -363,6 +366,9 @@ launch_notifications = True
# Send e-mails when a radiosonde is detected descending near your station location
landing_notifications = True
# Send e-mails when an encrypted radiosonde is detected.
encrypted_sonde_notifications = True
# Range threshold for Landing notifications (km from your station location)
landing_range_threshold = 30

Wyświetl plik

@ -104,8 +104,10 @@ typedef struct {
double vH; double vD; double vV;
double vx; double vy; double vD2;
float T; float RH; float TH; float P;
float batV;
ui8_t numSV;
ui8_t utc_ofs;
//ui8_t utc_ofs;
ui8_t fwVer;
char SN[12+4];
ui8_t SNraw[3];
ui8_t frame_bytes[FRAME_LEN+AUX_LEN+4];
@ -193,12 +195,12 @@ frame[0x08..0x0A]: GPS altitude
frame[0x0B..0x0E]: GPS hor.Vel. (velE,velN)
frame[0x0F..0x11]: GPS TOW
frame[0x15]: counter
frame[0x16..0x17]: block check
frame[0x16..0x17]: block check (fwVer < 0x06) ; frame[0x16]: SPI1 P[0] (fwVer >= 0x07), frame[0x17]=0x00
frame[0x18..0x19]: GPS ver.Vel. (velU)
frame[0x1A..0x1B]: GPS week
frame[0x1C..0x1F]: GPS latitude
frame[0x20..0x23]: GPS longitude
frame[0x24..0x25]: SPI1 P[1..2] (if pressure sensor)
frame[0x44..0x45]: frame check
*/
@ -218,7 +220,8 @@ frame[0x44..0x45]: frame check
#define pos_SN 0x12 // 3 byte
#define pos_CNT 0x15 // 1 byte
#define pos_BlkChk 0x16 // 2 byte
#define pos_Check (stdFLEN-1) // 2 byte
#define pos_stdFW 0x43 // 1 byte
#define pos_stdCheck (stdFLEN-1) // 2 byte
#define len_BlkChk 0x16 // frame[0x02..0x17] , incl. chk16
@ -250,6 +253,10 @@ frame[0x44..0x45]: frame check
#define col_CSoo "\x1b[38;5;220m"
#define col_CSno "\x1b[38;5;1m"
#define col_CNST "\x1b[38;5;58m" // 3 byte
#define col_ptuP "\x1b[38;5;180m"
#define col_ptuT "\x1b[38;5;110m"
#define col_ptuU "\x1b[38;5;120m"
#define col_ptuTH "\x1b[38;5;115m"
/*
$ for code in {0..255}
@ -701,18 +708,33 @@ static float get_RH(gpx_t *gpx) {
}
static float get_P(gpx_t *gpx) {
// cf. DF9DQ
//
float hPa = 0.0f;
ui16_t val = (gpx->frame_bytes[0x25] << 8) | gpx->frame_bytes[0x24];
ui32_t val = (gpx->frame_bytes[0x25] << 8) | gpx->frame_bytes[0x24]; // cf. DF9DQ
ui8_t p0 = 0x00;
if (gpx->fwVer >= 0x07) { // SPI1_P[0]
p0 = gpx->frame_bytes[0x16];
}
val = (val << 8) | p0;
if (val > 0) {
hPa = val/16.0f;
hPa = val/(float)(16*256); // 4096=0x1000
}
return hPa;
}
static float get_BatV(gpx_t *gpx) {
float batV = 0.0f;
ui8_t val = gpx->frame_bytes[0x26]; // cf. DF9DQ
batV = val * (3.3f/255); // upper 8 bits ADC
return batV;
}
/* -------------------------------------------------------------------------- */
static int print_pos(gpx_t *gpx, int bcOK, int csOK) {
@ -741,6 +763,8 @@ static int print_pos(gpx_t *gpx, int bcOK, int csOK) {
gpx->P = get_P(gpx); // (optional) pressure
}
gpx->batV = get_BatV(gpx); // battery V
if ( !gpx->option.slt )
{
if (gpx->option.col) {
@ -763,10 +787,11 @@ static int print_pos(gpx_t *gpx, int bcOK, int csOK) {
}
if (gpx->option.vbs >= 1) {
fprintf(stdout, " # ");
if (bcOK > 0) fprintf(stdout, " "col_CSok"(ok)"col_TXT);
else if (bcOK < 0) fprintf(stdout, " "col_CSoo"(oo)"col_TXT);
else fprintf(stdout, " "col_CSno"(no)"col_TXT);
//
if (gpx->fwVer < 0x07) {
if (bcOK > 0) fprintf(stdout, " "col_CSok"(ok)"col_TXT);
else if (bcOK < 0) fprintf(stdout, " "col_CSoo"(oo)"col_TXT);
else fprintf(stdout, " "col_CSno"(no)"col_TXT);
}
if (csOK) fprintf(stdout, " "col_CSok"[OK]"col_TXT);
else fprintf(stdout, " "col_CSno"[NO]"col_TXT);
}
@ -778,10 +803,14 @@ static int print_pos(gpx_t *gpx, int bcOK, int csOK) {
if (gpx->TH > -273.0f) fprintf(stdout, " TH:%.1fC", gpx->TH);
}
if (gpx->P > 0.0f) {
if (gpx->P < 100.0f) fprintf(stdout, " P=%.2fhPa ", gpx->P);
else fprintf(stdout, " P=%.1fhPa ", gpx->P);
if (gpx->P < 10.0f) fprintf(stdout, " P=%.3fhPa ", gpx->P);
else if (gpx->P < 100.0f) fprintf(stdout, " P=%.2fhPa ", gpx->P);
else fprintf(stdout, " P=%.1fhPa ", gpx->P);
}
}
if (gpx->option.vbs >= 3 && csOK) {
fprintf(stdout, " (bat:%.2fV)", gpx->batV);
}
fprintf(stdout, ANSI_COLOR_RESET"");
}
else {
@ -803,11 +832,12 @@ static int print_pos(gpx_t *gpx, int bcOK, int csOK) {
}
if (gpx->option.vbs >= 1) {
fprintf(stdout, " # ");
//if (bcOK) fprintf(stdout, " (ok)"); else fprintf(stdout, " (no)");
if (bcOK > 0) fprintf(stdout, " (ok)");
else if (bcOK < 0) fprintf(stdout, " (oo)");
else fprintf(stdout, " (no)");
//
if (gpx->fwVer < 0x07) {
//if (bcOK) fprintf(stdout, " (ok)"); else fprintf(stdout, " (no)");
if (bcOK > 0) fprintf(stdout, " (ok)");
else if (bcOK < 0) fprintf(stdout, " (oo)");
else fprintf(stdout, " (no)");
}
if (csOK) fprintf(stdout, " [OK]"); else fprintf(stdout, " [NO]");
}
if (gpx->option.ptu && csOK) {
@ -818,10 +848,14 @@ static int print_pos(gpx_t *gpx, int bcOK, int csOK) {
if (gpx->TH > -273.0f) fprintf(stdout, " TH:%.1fC", gpx->TH);
}
if (gpx->P > 0.0f) {
if (gpx->P < 100.0f) fprintf(stdout, " P=%.2fhPa ", gpx->P);
else fprintf(stdout, " P=%.1fhPa ", gpx->P);
if (gpx->P < 10.0f) fprintf(stdout, " P=%.3fhPa ", gpx->P);
else if (gpx->P < 100.0f) fprintf(stdout, " P=%.2fhPa ", gpx->P);
else fprintf(stdout, " P=%.1fhPa ", gpx->P);
}
}
if (gpx->option.vbs >= 3 && csOK) {
fprintf(stdout, " (bat:%.2fV)", gpx->batV);
}
}
fprintf(stdout, "\n");
}
@ -846,6 +880,7 @@ static int print_pos(gpx_t *gpx, int bcOK, int csOK) {
if (gpx->RH > -0.5f) fprintf(stdout, ", \"humidity\": %.1f", gpx->RH );
if (gpx->P > 0.0f) fprintf(stdout, ", \"pressure\": %.2f", gpx->P );
}
fprintf(stdout, ", \"batt\": %.2f", gpx->batV);
fprintf(stdout, ", \"rawid\": \"M20_%02X%02X%02X\"", gpx->frame_bytes[pos_SN], gpx->frame_bytes[pos_SN+1], gpx->frame_bytes[pos_SN+2]); // gpx->type
fprintf(stdout, ", \"subtype\": \"0x%02X\"", gpx->type);
if (gpx->jsn_freq > 0) {
@ -876,6 +911,8 @@ static int print_frame(gpx_t *gpx, int pos, int b2B) {
int cs1, cs2;
int bc1, bc2, bc;
int flen = stdFLEN; // stdFLEN=0x64, auxFLEN=0x76; M20:0x45 ?
int pos_fw = pos_stdFW;
int pos_check = pos_stdCheck;
if (b2B) {
bits2bytes(gpx->frame_bits, gpx->frame_bytes);
@ -885,10 +922,21 @@ static int print_frame(gpx_t *gpx, int pos, int b2B) {
else {
gpx->auxlen = flen - stdFLEN;
//if (gpx->auxlen < 0 || gpx->auxlen > AUX_LEN) gpx->auxlen = 0; // 0x43,0x45
if (gpx->auxlen < 0) {
gpx->auxlen = 0;
pos_fw = flen-2; // only if flen < stdFLEN
}
else if (gpx->auxlen > AUX_LEN) {
gpx->auxlen = AUX_LEN;
flen = stdFLEN+AUX_LEN;
}
}
pos_check = flen-1;
gpx->fwVer = gpx->frame_bytes[pos_fw];
if (gpx->fwVer > 0x20) gpx->fwVer = 0;
cs1 = (gpx->frame_bytes[pos_Check+gpx->auxlen] << 8) | gpx->frame_bytes[pos_Check+gpx->auxlen+1];
cs2 = checkM10(gpx->frame_bytes, pos_Check+gpx->auxlen);
cs1 = (gpx->frame_bytes[pos_check] << 8) | gpx->frame_bytes[pos_check+1];
cs2 = checkM10(gpx->frame_bytes, pos_check);
bc1 = (gpx->frame_bytes[pos_BlkChk] << 8) | gpx->frame_bytes[pos_BlkChk+1];
bc2 = blk_checkM10(len_BlkChk, gpx->frame_bytes+2); // len(essentialBlock+chk16) = 0x16
@ -921,16 +969,27 @@ static int print_frame(gpx_t *gpx, int pos, int b2B) {
if ((i >= pos_GPSvU) && (i < pos_GPSvU+2)) fprintf(stdout, col_GPSvel);
if ((i >= pos_SN) && (i < pos_SN+3)) fprintf(stdout, col_SN);
if (i == pos_CNT) fprintf(stdout, col_CNT);
if ((i >= pos_BlkChk) && (i < pos_BlkChk+2)) fprintf(stdout, col_Check);
if ((i >= pos_Check+gpx->auxlen) && (i < pos_Check+gpx->auxlen+2)) fprintf(stdout, col_Check);
if (gpx->fwVer < 0x07) {
if ((i >= pos_BlkChk) && (i < pos_BlkChk+2)) fprintf(stdout, col_Check);
} else {
if ((i >= pos_BlkChk+1) && (i < pos_BlkChk+2)) fprintf(stdout, col_Check);
}
if (i >= 0x02 && i <= 0x03) fprintf(stdout, col_ptuU);
if (i >= 0x04 && i <= 0x05) fprintf(stdout, col_ptuT);
if (i >= 0x06 && i <= 0x07) fprintf(stdout, col_ptuTH);
if (i == 0x16 && gpx->fwVer >= 0x07 || i >= 0x24 && i <= 0x25) fprintf(stdout, col_ptuP);
if ((i >= pos_check) && (i < pos_check+2)) fprintf(stdout, col_Check);
fprintf(stdout, "%02x", byte);
fprintf(stdout, col_FRTXT);
}
if (gpx->option.vbs) {
fprintf(stdout, " # "col_Check"%04x"col_FRTXT, cs2);
if (bc > 0) fprintf(stdout, " "col_CSok"(ok)"col_TXT);
else if (bc < 0) fprintf(stdout, " "col_CSoo"(oo)"col_TXT);
else fprintf(stdout, " "col_CSno"(no)"col_TXT);
if (gpx->fwVer < 0x07) {
if (bc > 0) fprintf(stdout, " "col_CSok"(ok)"col_TXT);
else if (bc < 0) fprintf(stdout, " "col_CSoo"(oo)"col_TXT);
else fprintf(stdout, " "col_CSno"(no)"col_TXT);
}
if (cs1 == cs2) fprintf(stdout, " "col_CSok"[OK]"col_TXT);
else fprintf(stdout, " "col_CSno"[NO]"col_TXT);
}
@ -943,9 +1002,11 @@ static int print_frame(gpx_t *gpx, int pos, int b2B) {
}
if (gpx->option.vbs) {
fprintf(stdout, " # %04x", cs2);
if (bc > 0) fprintf(stdout, " (ok)");
else if (bc < 0) fprintf(stdout, " (oo)");
else fprintf(stdout, " (no)");
if (gpx->fwVer < 0x07) {
if (bc > 0) fprintf(stdout, " (ok)");
else if (bc < 0) fprintf(stdout, " (oo)");
else fprintf(stdout, " (no)");
}
if (cs1 == cs2) fprintf(stdout, " [OK]"); else fprintf(stdout, " [NO]");
}
fprintf(stdout, "\n");