Skip to content

Commit

Permalink
Merge pull request #782 from projecthorus/testing
Browse files Browse the repository at this point in the history
v1.6.2 release
  • Loading branch information
darksidelemm authored Aug 4, 2023
2 parents 3e0d8ef + 0296175 commit dbd5ed7
Show file tree
Hide file tree
Showing 27 changed files with 421 additions and 169 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 24 additions & 6 deletions auto_rx/auto_rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -44,6 +54,7 @@
start_flask,
stop_flask,
flask_emit_event,
flask_running,
WebHandler,
WebExporter,
)
Expand Down Expand Up @@ -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:]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"],
)
Expand Down Expand Up @@ -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__":
Expand All @@ -1084,9 +1098,13 @@ def 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)

9 changes: 4 additions & 5 deletions auto_rx/auto_rx.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
2 changes: 1 addition & 1 deletion auto_rx/autorx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# MINOR - New sonde type support, other fairly big changes that may result in telemetry or config file incompatability issus.
# PATCH - Small changes, or minor feature additions.

__version__ = "1.6.1"
__version__ = "1.6.2"


# Global Variables
Expand Down
12 changes: 9 additions & 3 deletions auto_rx/autorx/aprs.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,13 +759,19 @@ def close(self):

# 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.
Expand Down
20 changes: 16 additions & 4 deletions auto_rx/autorx/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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.")
Expand Down
15 changes: 15 additions & 0 deletions auto_rx/autorx/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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 = [
Expand Down Expand Up @@ -1424,6 +1425,20 @@ def handle_decoder_line(self, data):
"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
Expand Down
85 changes: 71 additions & 14 deletions auto_rx/autorx/email_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(
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,
Expand All @@ -60,6 +61,7 @@ def __init__(
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
Expand Down Expand Up @@ -119,6 +121,7 @@ def process_telemetry(self, telemetry):
self.sondes[_id] = {
"last_time": time.time(),
"descending_trip": 0,
"ascent_trip": False,
"descent_notified": False,
"track": GenericTrack(max_elements=20),
}
Expand All @@ -133,18 +136,44 @@ def process_telemetry(self, telemetry):
}
)

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" % (
Expand Down Expand Up @@ -175,10 +204,6 @@ def process_telemetry(self, telemetry):
_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:
Expand All @@ -200,14 +225,21 @@ def process_telemetry(self, telemetry):
# 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

Expand Down Expand Up @@ -237,7 +269,7 @@ def process_telemetry(self, telemetry):

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" % (
Expand Down Expand Up @@ -347,7 +379,9 @@ def close(self):
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.
Expand Down Expand Up @@ -433,6 +467,29 @@ def log_error(self, line):
}
)

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)

Expand Down
4 changes: 3 additions & 1 deletion auto_rx/autorx/gpsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,9 @@ def close(self):
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):
"""
Expand Down
Loading

0 comments on commit dbd5ed7

Please sign in to comment.