2017-04-29 02:00:10 +00:00
#!/usr/bin/env python
#
2018-05-26 09:18:53 +00:00
# Radiosonde Auto RX Service - V2.0
2017-04-29 02:00:10 +00:00
#
2018-05-26 09:18:53 +00:00
# Copyright (C) 2018 Mark Jessop <vk5qi@rfhead.net>
# Released under GNU GPL v3 or later
2017-04-29 02:00:10 +00:00
#
2018-05-26 09:18:53 +00:00
# Refer github page for instructions on setup and usage.
# https://github.com/projecthorus/radiosonde_auto_rx/
2017-04-29 14:47:26 +00:00
#
2023-07-03 16:03:18 +00:00
# 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
2017-05-05 12:56:41 +00:00
import argparse
2017-04-29 14:06:47 +00:00
import datetime
2018-05-26 09:18:53 +00:00
import logging
import re
import sys
2017-04-29 14:06:47 +00:00
import time
import traceback
2019-01-27 18:17:05 +00:00
import os
2021-03-29 07:12:30 +00:00
from dateutil . parser import parse
2022-09-20 22:57:39 +00:00
from queue import Queue
2017-05-06 23:42:46 +00:00
2021-04-03 00:53:59 +00:00
if sys . version_info < ( 3 , 6 ) :
print ( " CRITICAL - radiosonde_auto_rx requires Python 3.6 or newer! " )
sys . exit ( 1 )
2018-06-18 11:17:38 +00:00
import autorx
2018-05-26 09:18:53 +00:00
from autorx . scan import SondeScanner
2019-04-24 12:17:20 +00:00
from autorx . decode import SondeDecoder , VALID_SONDE_TYPES , DRIFTY_SONDE_TYPES
2018-05-26 09:18:53 +00:00
from autorx . logger import TelemetryLogger
2018-07-04 08:13:58 +00:00
from autorx . email_notification import EmailNotification
2018-05-26 09:18:53 +00:00
from autorx . habitat import HabitatUploader
2018-05-26 11:51:55 +00:00
from autorx . aprs import APRSUploader
2018-05-27 10:59:49 +00:00
from autorx . ozimux import OziUploader
2021-01-29 11:05:48 +00:00
from autorx . sondehub import SondehubUploader
2018-10-04 11:45:18 +00:00
from autorx . rotator import Rotator
2020-12-18 06:47:54 +00:00
from autorx . utils import (
rtlsdr_test ,
position_info ,
check_rs_utils ,
2021-04-17 02:20:19 +00:00
version_startup_check ,
2020-12-18 06:47:54 +00:00
)
2018-05-26 09:18:53 +00:00
from autorx . config import read_auto_rx_config
2020-12-18 06:47:54 +00:00
from autorx . web import (
start_flask ,
stop_flask ,
flask_emit_event ,
2023-07-03 16:03:18 +00:00
flask_running ,
2020-12-18 06:47:54 +00:00
WebHandler ,
WebExporter ,
)
2019-06-22 08:15:46 +00:00
from autorx . gpsd import GPSDAdaptor
2022-07-08 05:22:18 +00:00
from autorx . sdr_wrappers import shutdown_sdr
2017-07-16 10:11:13 +00:00
2017-04-29 14:06:47 +00:00
2018-05-26 09:18:53 +00:00
# Logging level
# INFO = Basic status messages
# DEBUG = Adds detailed information on submodule operations.
logging_level = logging . INFO
2017-04-29 14:06:47 +00:00
2018-05-26 09:18:53 +00:00
#
# Global Variables
#
2017-04-29 14:06:47 +00:00
2018-05-26 09:18:53 +00:00
RS_PATH = " ./ "
2017-04-29 14:06:47 +00:00
2018-05-26 09:18:53 +00:00
# Optional override for RS92 ephemeris data.
rs92_ephemeris = None
2017-04-29 14:06:47 +00:00
2018-06-01 11:32:24 +00:00
# Global configuration dictionary. Populated on startup.
2018-05-26 09:18:53 +00:00
config = None
2017-04-29 14:06:47 +00:00
2018-05-26 09:18:53 +00:00
# Exporter Lists
2020-12-18 06:47:54 +00:00
exporter_objects = (
[ ]
) # This list will hold references to each exporter instance that is created.
exporter_functions = (
[ ]
) # This list will hold references to the exporter add functions, which will be passed onto the decoders.
2017-04-29 14:06:47 +00:00
2020-07-03 13:24:06 +00:00
# Separate reference to the e-mail exporter, as we may want to use this for error notifications.
email_exporter = None
2017-07-16 10:11:13 +00:00
2019-06-22 08:15:46 +00:00
# GPSDAdaptor Instance, if used.
gpsd_adaptor = None
2019-04-14 05:12:42 +00:00
# Temporary frequency block list
# This contains frequncies that should be blocked for a short amount of time.
temporary_block_list = { }
2017-04-29 14:06:47 +00:00
2020-12-18 06:47:54 +00:00
def allocate_sdr ( check_only = False , task_description = " " ) :
2021-04-28 09:36:30 +00:00
""" Allocate an un-used SDR for a task.
2017-05-06 23:42:46 +00:00
2018-05-26 09:18:53 +00:00
Args :
check_only ( bool ) : If True , don ' t set the free SDR as in-use. Used to check if there are any free SDRs.
2017-12-17 01:44:20 +00:00
2018-05-26 09:18:53 +00:00
Returns :
( str ) : The device index / serial number of the free / allocated SDR , if one is free , else None .
"""
2017-07-16 10:11:13 +00:00
2020-07-04 00:44:48 +00:00
for _idx in sorted ( autorx . sdr_list . keys ( ) ) :
2020-12-18 06:47:54 +00:00
if autorx . sdr_list [ _idx ] [ " in_use " ] == False :
2018-05-26 09:18:53 +00:00
# Found a free SDR!
if check_only :
# If we are just checking to see if there are any SDRs free, we don't allocate it.
pass
else :
# Otherwise, set the SDR as in-use.
2020-12-18 06:47:54 +00:00
autorx . sdr_list [ _idx ] [ " in_use " ] = True
logging . info (
" Task Manager - SDR # %s has been allocated to %s . "
% ( str ( _idx ) , task_description )
)
2019-01-27 18:17:05 +00:00
2018-05-26 09:18:53 +00:00
return _idx
# Otherwise, no SDRs are free.
return None
def start_scanner ( ) :
2021-04-28 09:36:30 +00:00
""" Start a scanner thread on the first available SDR """
2019-05-04 03:12:24 +00:00
global config , RS_PATH , temporary_block_list
2018-05-26 09:18:53 +00:00
2020-12-18 06:47:54 +00:00
if " SCAN " in autorx . task_list :
2018-05-26 09:18:53 +00:00
# Already a scanner running! Return.
2020-12-18 06:47:54 +00:00
logging . debug (
" Task Manager - Attempted to start a scanner, but one already running. "
)
2018-05-26 09:18:53 +00:00
return
# Attempt to allocate a SDR.
_device_idx = allocate_sdr ( task_description = " Scanner " )
if _device_idx is None :
logging . debug ( " Task Manager - No SDRs free to run Scanner. " )
return
2017-12-17 01:44:20 +00:00
else :
2018-05-26 09:18:53 +00:00
# Create entry in task list.
2020-12-18 06:47:54 +00:00
autorx . task_list [ " SCAN " ] = { " device_idx " : _device_idx , " task " : None }
2018-05-26 09:18:53 +00:00
# Init Scanner using settings from the global config.
2018-06-01 11:32:24 +00:00
# TODO: Nicer way of passing in the huge list of args.
2020-12-18 06:47:54 +00:00
autorx . task_list [ " SCAN " ] [ " task " ] = SondeScanner (
callback = autorx . scan_results . put ,
auto_start = True ,
min_freq = config [ " min_freq " ] ,
max_freq = config [ " max_freq " ] ,
search_step = config [ " search_step " ] ,
2021-04-28 09:35:43 +00:00
only_scan = config [ " only_scan " ] ,
always_scan = config [ " always_scan " ] ,
never_scan = config [ " never_scan " ] ,
2020-12-18 06:47:54 +00:00
snr_threshold = config [ " snr_threshold " ] ,
min_distance = config [ " min_distance " ] ,
quantization = config [ " quantization " ] ,
scan_dwell_time = config [ " scan_dwell_time " ] ,
2021-05-05 08:16:45 +00:00
scan_delay = config [ " scan_delay " ] ,
2020-12-18 06:47:54 +00:00
detect_dwell_time = config [ " detect_dwell_time " ] ,
max_peaks = config [ " max_peaks " ] ,
rs_path = RS_PATH ,
2022-04-02 09:38:13 +00:00
sdr_type = config [ " sdr_type " ] ,
# Network SDR Options
sdr_hostname = config [ " sdr_hostname " ] ,
sdr_port = config [ " sdr_port " ] ,
2022-05-14 07:59:15 +00:00
ss_iq_path = config [ " ss_iq_path " ] ,
ss_power_path = config [ " ss_power_path " ] ,
2022-04-02 09:38:13 +00:00
rtl_power_path = config [ " sdr_power " ] ,
rtl_fm_path = config [ " sdr_fm " ] ,
rtl_device_idx = _device_idx ,
2020-12-18 06:47:54 +00:00
gain = autorx . sdr_list [ _device_idx ] [ " gain " ] ,
ppm = autorx . sdr_list [ _device_idx ] [ " ppm " ] ,
bias = autorx . sdr_list [ _device_idx ] [ " bias " ] ,
save_detection_audio = config [ " save_detection_audio " ] ,
2023-08-14 05:28:26 +00:00
wideband_sondes = config [ " wideband_sondes " ] ,
2020-12-18 06:47:54 +00:00
temporary_block_list = temporary_block_list ,
temporary_block_time = config [ " temporary_block_time " ] ,
)
2018-05-26 09:18:53 +00:00
# Add a reference into the sdr_list entry
2020-12-18 06:47:54 +00:00
autorx . sdr_list [ _device_idx ] [ " task " ] = autorx . task_list [ " SCAN " ] [ " task " ]
2019-01-27 18:17:05 +00:00
2018-06-29 13:02:58 +00:00
# Indicate to the web client that the task list has been updated.
2020-12-18 06:47:54 +00:00
flask_emit_event ( " task_event " )
2018-05-26 09:18:53 +00:00
def stop_scanner ( ) :
2021-04-28 09:36:30 +00:00
""" Stop a currently running scan thread, and release the SDR it was using. """
2018-05-26 09:18:53 +00:00
2020-12-18 06:47:54 +00:00
if " SCAN " not in autorx . task_list :
2018-05-26 09:18:53 +00:00
# No scanner thread running!
# This means we likely have a SDR free already.
return
2018-05-07 12:03:31 +00:00
else :
2018-05-26 09:18:53 +00:00
logging . info ( " Halting Scanner to decode detected radiosonde. " )
2020-12-18 06:47:54 +00:00
_scan_sdr = autorx . task_list [ " SCAN " ] [ " device_idx " ]
2018-05-26 09:18:53 +00:00
# Stop the scanner.
2020-12-18 06:47:54 +00:00
autorx . task_list [ " SCAN " ] [ " task " ] . stop ( )
2018-05-26 09:18:53 +00:00
# Relase the SDR.
2020-12-18 06:47:54 +00:00
autorx . sdr_list [ _scan_sdr ] [ " in_use " ] = False
autorx . sdr_list [ _scan_sdr ] [ " task " ] = None
2018-05-26 09:18:53 +00:00
# Remove the scanner task from the task list
2020-12-18 06:47:54 +00:00
autorx . task_list . pop ( " SCAN " )
2018-05-26 09:18:53 +00:00
2018-06-01 11:32:24 +00:00
2022-05-14 08:30:54 +00:00
def start_decoder ( freq , sonde_type , continuous = False ) :
2021-04-28 09:36:30 +00:00
""" Attempt to start a decoder thread for a given sonde.
2018-06-01 11:32:24 +00:00
Args :
freq ( float ) : Radiosonde frequency in Hz .
2019-03-17 11:28:09 +00:00
sonde_type ( str ) : The radiosonde type ( ' RS41 ' , ' RS92 ' , ' DFM ' , ' M10, ' iMet ' )
2022-05-14 08:30:54 +00:00
continuous ( bool ) : If true , don ' t use a decode timeout.
2018-06-01 11:32:24 +00:00
"""
2019-04-14 05:12:42 +00:00
global config , RS_PATH , exporter_functions , rs92_ephemeris , temporary_block_list
2018-05-26 09:18:53 +00:00
# Allocate a SDR.
2020-12-18 06:47:54 +00:00
_device_idx = allocate_sdr (
task_description = " Decoder ( %s , %.3f MHz) " % ( sonde_type , freq / 1e6 )
)
2018-05-26 09:18:53 +00:00
if _device_idx is None :
logging . error ( " Could not allocate SDR for decoder! " )
return
2017-04-29 14:06:47 +00:00
else :
2018-05-26 09:18:53 +00:00
# Add an entry to the task list
2020-12-18 06:47:54 +00:00
autorx . task_list [ freq ] = { " device_idx " : _device_idx , " task " : None }
2018-05-26 09:18:53 +00:00
# Set the SDR to in-use
2020-12-18 06:47:54 +00:00
autorx . sdr_list [ _device_idx ] [ " in_use " ] = True
2018-05-26 09:18:53 +00:00
2020-12-18 06:47:54 +00:00
if sonde_type . startswith ( " - " ) :
2019-06-01 11:40:51 +00:00
_exp_sonde_type = sonde_type [ 1 : ]
else :
_exp_sonde_type = sonde_type
2022-05-14 08:30:54 +00:00
if continuous :
_timeout = 0
else :
_timeout = config [ " rx_timeout " ]
2018-05-26 09:18:53 +00:00
# Initialise a decoder.
2020-12-18 06:47:54 +00:00
autorx . task_list [ freq ] [ " task " ] = SondeDecoder (
sonde_type = sonde_type ,
sonde_freq = freq ,
rs_path = RS_PATH ,
2022-04-02 09:38:13 +00:00
sdr_type = config [ " sdr_type " ] ,
# Network SDR Options
sdr_hostname = config [ " sdr_hostname " ] ,
sdr_port = config [ " sdr_port " ] ,
2022-05-14 07:59:15 +00:00
ss_iq_path = config [ " ss_iq_path " ] ,
2022-03-27 07:13:39 +00:00
# RTLSDR Options
rtl_fm_path = config [ " sdr_fm " ] ,
rtl_device_idx = _device_idx ,
2020-12-18 06:47:54 +00:00
gain = autorx . sdr_list [ _device_idx ] [ " gain " ] ,
ppm = autorx . sdr_list [ _device_idx ] [ " ppm " ] ,
bias = autorx . sdr_list [ _device_idx ] [ " bias " ] ,
2022-03-27 07:13:39 +00:00
# Other options
2020-12-18 06:47:54 +00:00
save_decode_audio = config [ " save_decode_audio " ] ,
save_decode_iq = config [ " save_decode_iq " ] ,
exporter = exporter_functions ,
2022-05-14 08:30:54 +00:00
timeout = _timeout ,
2020-12-18 06:47:54 +00:00
telem_filter = telemetry_filter ,
rs92_ephemeris = rs92_ephemeris ,
rs41_drift_tweak = config [ " rs41_drift_tweak " ] ,
experimental_decoder = config [ " experimental_decoders " ] [ _exp_sonde_type ] ,
2023-08-14 05:28:26 +00:00
save_raw_hex = config [ " save_raw_hex " ] ,
wideband_sondes = config [ " wideband_sondes " ]
2020-12-18 06:47:54 +00:00
)
autorx . sdr_list [ _device_idx ] [ " task " ] = autorx . task_list [ freq ] [ " task " ]
2018-05-26 09:18:53 +00:00
2018-06-29 13:02:58 +00:00
# Indicate to the web client that the task list has been updated.
2020-12-18 06:47:54 +00:00
flask_emit_event ( " task_event " )
2018-05-26 09:18:53 +00:00
def handle_scan_results ( ) :
2021-04-28 09:36:30 +00:00
""" Read in Scan results via the scan results Queue.
2018-05-26 09:18:53 +00:00
Depending on how many SDRs are available , two things can happen :
- If there is a free SDR , allocate it to a decoder .
- If there is no free SDR , but a scanner is running , stop the scanner and start decoding .
"""
2019-05-04 03:12:24 +00:00
global config , temporary_block_list
2018-06-01 11:32:24 +00:00
2019-05-04 03:12:24 +00:00
if autorx . scan_results . qsize ( ) > 0 :
2018-06-01 11:32:24 +00:00
# Grab the latest detections from the scan result queue.
2019-05-04 03:12:24 +00:00
_scan_data = autorx . scan_results . get ( )
2018-05-26 09:18:53 +00:00
for _sonde in _scan_data :
2018-06-01 11:32:24 +00:00
# Extract frequency & type info
2018-05-26 09:18:53 +00:00
_freq = _sonde [ 0 ]
_type = _sonde [ 1 ]
2018-06-18 11:17:38 +00:00
if _freq in autorx . task_list :
2018-05-26 09:18:53 +00:00
# Already decoding this sonde, continue.
2017-12-20 04:23:29 +00:00
continue
2018-05-26 09:18:53 +00:00
else :
2019-02-02 04:53:26 +00:00
2022-11-17 08:26:06 +00:00
# Handle an inverted sonde detection.
if _type . startswith ( " - " ) :
_inverted = " (Inverted) "
_check_type = _type [ 1 : ]
else :
_check_type = _type
_inverted = " "
# Note: We don't indicate if it's been detected as inverted here.
logging . info (
" Task Manager - Detected new %s sonde on %.3f MHz! "
% ( _check_type , _freq / 1e6 )
)
# Break if we don't support this sonde type.
if _check_type not in VALID_SONDE_TYPES :
logging . warning (
" Task Manager - Unsupported sonde type: %s " % _check_type
)
# TODO - Potentially add the frequency of the unsupported sonde to the temporary block list?
continue
2019-05-04 03:12:24 +00:00
# Check that we are not attempting to start a decoder too close to an existing decoder for known 'drifty' radiosonde types.
# 'Too close' is defined by the 'decoder_spacing_limit' advanced coniguration option.
_too_close = False
for _key in autorx . task_list . keys ( ) :
# Iterate through the task list, and only attempt to compare with those that are a decoder task.
# This is indicated by the task key being an integer (the sonde frequency).
if ( type ( _key ) == int ) or ( type ( _key ) == float ) :
# Extract the currently decoded sonde type from the currently running decoder.
2020-12-18 06:47:54 +00:00
_decoding_sonde_type = autorx . task_list [ _key ] [ " task " ] . sonde_type
2023-07-03 16:03:18 +00:00
2022-11-17 08:26:06 +00:00
# Remove any inverted decoder information for the comparison.
if _decoding_sonde_type . startswith ( " - " ) :
_decoding_sonde_type = _decoding_sonde_type [ 1 : ]
2019-05-04 03:12:24 +00:00
# Only check the frequency spacing if we have a known 'drifty' sonde type, *and* the new sonde type is of the same type.
2020-12-18 06:47:54 +00:00
if ( _decoding_sonde_type in DRIFTY_SONDE_TYPES ) and (
2022-11-17 08:26:06 +00:00
_decoding_sonde_type == _check_type
2020-12-18 06:47:54 +00:00
) :
if abs ( _key - _freq ) < config [ " decoder_spacing_limit " ] :
2019-05-04 03:12:24 +00:00
# At this point, we can be pretty sure that there is another decoder already decoding this particular sonde ID.
# Without actually starting another decoder and matching IDs, we can't be 100% sure, but it's a good chance.
2021-11-10 23:42:26 +00:00
logging . info (
2020-12-18 06:47:54 +00:00
" Task Manager - Detected %s sonde on %.3f MHz, but this is within %d kHz of an already running decoder. (This limit can be set using the ' decoder_spacing_limit ' advanced config option.) "
% (
_type ,
_freq / 1e6 ,
config [ " decoder_spacing_limit " ] / 1e3 ,
)
)
2019-05-04 03:12:24 +00:00
_too_close = True
continue
# Continue to the next scan result if this one is too close to a currently running decoder.
if _too_close :
continue
2020-12-18 06:47:54 +00:00
# Check the frequency is not in our temporary block list
2019-05-04 03:12:24 +00:00
# (This may happen from time-to-time depending on the timing of the scan thread)
if _freq in temporary_block_list . keys ( ) :
2020-12-18 06:47:54 +00:00
if temporary_block_list [ _freq ] > (
time . time ( ) - config [ " temporary_block_time " ] * 60
) :
2021-04-23 12:32:29 +00:00
logging . warning (
2020-12-18 06:47:54 +00:00
" Task Manager - Attempted to start a decoder on a temporarily blocked frequency ( %.3f MHz) "
% ( _freq / 1e6 )
)
2019-05-04 03:12:24 +00:00
continue
else :
# This frequency should not be blocked any more, remove it from the block list.
2020-12-18 06:47:54 +00:00
logging . info (
" Task Manager - Removed %.3f MHz from temporary block list. "
% ( _freq / 1e6 )
)
2019-05-04 03:12:24 +00:00
temporary_block_list . pop ( _freq )
2018-08-01 11:37:55 +00:00
2020-12-18 06:47:54 +00:00
if allocate_sdr ( check_only = True ) is not None :
2018-05-26 09:18:53 +00:00
# There is a SDR free! Start the decoder on that SDR
start_decoder ( _freq , _type )
2020-12-18 06:47:54 +00:00
elif ( allocate_sdr ( check_only = True ) is None ) and (
" SCAN " in autorx . task_list
) :
2018-05-26 09:18:53 +00:00
# We have run out of SDRs, but a scan thread is running.
# Stop the scan thread and take that receiver!
stop_scanner ( )
start_decoder ( _freq , _type )
else :
2018-06-01 11:32:24 +00:00
# We have no SDRs free.
# TODO: Alert the user that a sonde was detected, but no SDR was available,
# but don't do this EVERY time we detect the sonde...
2018-05-26 09:18:53 +00:00
pass
2017-05-06 23:42:46 +00:00
2017-07-18 12:39:55 +00:00
2018-05-26 09:18:53 +00:00
def clean_task_list ( ) :
2021-04-28 09:36:30 +00:00
""" Check the task list to see if any tasks have stopped running. If so, release the associated SDR """
2017-07-18 12:35:17 +00:00
2019-09-09 12:29:07 +00:00
for _key in autorx . task_list . copy ( ) . keys ( ) :
2018-05-26 09:18:53 +00:00
# Attempt to get the state of the task
try :
2020-12-18 06:47:54 +00:00
_running = autorx . task_list [ _key ] [ " task " ] . running ( )
_task_sdr = autorx . task_list [ _key ] [ " device_idx " ]
_exit_state = autorx . task_list [ _key ] [ " task " ] . exit_state
2018-05-26 09:18:53 +00:00
except Exception as e :
2020-12-18 06:47:54 +00:00
logging . error (
" Task Manager - Error getting task %s state - %s " % ( str ( _key ) , str ( e ) )
)
2018-05-26 09:18:53 +00:00
continue
2017-05-06 23:42:46 +00:00
2018-05-26 09:18:53 +00:00
if _running == False :
2019-04-14 05:12:42 +00:00
# This task has stopped.
# Check the exit state of the task for any abnormalities:
2020-06-20 08:07:22 +00:00
if ( _exit_state == " Encrypted " ) or ( _exit_state == " TempBlock " ) :
# This task was a decoder, and it has encountered an encrypted sonde, or one too far away.
2020-12-18 06:47:54 +00:00
logging . info (
" Task Manager - Adding temporary block for frequency %.3f MHz "
% ( _key / 1e6 )
)
2019-04-14 05:12:42 +00:00
# Add the sonde's frequency to the global temporary block-list
temporary_block_list [ _key ] = time . time ( )
# If there is a scanner currently running, add it to the scanners internal block list.
2020-12-18 06:47:54 +00:00
if " SCAN " in autorx . task_list :
autorx . task_list [ " SCAN " ] [ " task " ] . add_temporary_block ( _key )
2019-04-14 05:12:42 +00:00
2020-12-18 06:47:54 +00:00
if _exit_state == " FAILED SDR " :
2020-07-03 13:24:06 +00:00
# The SDR was not able to be recovered after many attempts.
# Remove it from the SDR list and flag an error.
autorx . sdr_list . pop ( _task_sdr )
2020-12-18 06:47:54 +00:00
_error_msg = (
" Task Manager - Removed SDR %s from SDR list due to repeated failures. "
% ( str ( _task_sdr ) )
)
2020-10-25 05:52:12 +00:00
logging . error ( _error_msg )
2020-12-18 06:47:54 +00:00
2020-10-25 05:52:12 +00:00
# Send email if configured.
email_error ( _error_msg )
2019-04-14 05:12:42 +00:00
2020-07-03 13:24:06 +00:00
else :
2022-07-08 02:12:29 +00:00
# Shutdown the SDR, if required for the particular SDR type.
shutdown_sdr ( config [ " sdr_type " ] , _task_sdr )
2020-07-03 13:24:06 +00:00
# Release its associated SDR.
2020-12-18 06:47:54 +00:00
autorx . sdr_list [ _task_sdr ] [ " in_use " ] = False
autorx . sdr_list [ _task_sdr ] [ " task " ] = None
2019-04-14 05:12:42 +00:00
2018-05-26 09:18:53 +00:00
# Pop the task from the task list.
2018-06-18 11:17:38 +00:00
autorx . task_list . pop ( _key )
2018-06-29 13:02:58 +00:00
# Indicate to the web client that the task list has been updated.
2020-12-18 06:47:54 +00:00
flask_emit_event ( " task_event " )
2017-05-06 23:42:46 +00:00
2019-04-14 05:12:42 +00:00
# Clean out the temporary block list of old entries.
2019-09-09 12:29:07 +00:00
for _freq in temporary_block_list . copy ( ) . keys ( ) :
2020-12-18 06:47:54 +00:00
if temporary_block_list [ _freq ] < (
time . time ( ) - config [ " temporary_block_time " ] * 60
) :
2019-04-14 05:12:42 +00:00
temporary_block_list . pop ( _freq )
2020-12-18 06:47:54 +00:00
logging . info (
" Task Manager - Removed %.3f MHz from temporary block list. "
% ( _freq / 1e6 )
)
2019-04-14 05:12:42 +00:00
2020-12-18 06:47:54 +00:00
# Check if there is a scanner thread still running.
2019-05-27 12:02:36 +00:00
# If not, and if there is a SDR free, start one up again.
# Also check for a global scan inhibit flag.
2020-12-18 06:47:54 +00:00
if (
( " SCAN " not in autorx . task_list )
and ( not autorx . scan_inhibit )
and ( allocate_sdr ( check_only = True ) is not None )
) :
2018-05-26 09:18:53 +00:00
# We have a SDR free, and we are not running a scan thread. Start one.
start_scanner ( )
2017-12-20 04:23:29 +00:00
2022-05-14 08:30:54 +00:00
# Always-on decoders.
if len ( config [ " always_decode " ] ) > 0 :
for _entry in config [ " always_decode " ] :
try :
_freq_hz = float ( _entry [ 0 ] ) * 1e6
_type = str ( _entry [ 1 ] )
except :
logging . warning ( f " Task Manager - Invalid entry found in always_decode list, skipping. " )
continue
if _freq_hz in autorx . task_list :
# Already running a decoder here.
continue
else :
# Try and start up a decoder.
if ( allocate_sdr ( check_only = True ) is not None ) :
logging . info ( f " Task Manager - Starting Always-On Decoder: { _type } , { _freq_hz / 1e6 : .3f } MHz " )
2022-05-27 09:34:31 +00:00
start_decoder ( _freq_hz , _type , continuous = True )
2022-05-14 08:30:54 +00:00
2017-05-06 23:42:46 +00:00
2018-05-26 09:18:53 +00:00
def stop_all ( ) :
2021-04-28 09:36:30 +00:00
""" Shut-down all decoders, scanners, and exporters. """
2018-06-18 11:17:38 +00:00
global exporter_objects
2018-05-26 09:18:53 +00:00
logging . info ( " Starting shutdown of all threads. " )
2018-06-18 11:17:38 +00:00
for _task in autorx . task_list . keys ( ) :
2018-05-26 09:18:53 +00:00
try :
2020-12-18 06:47:54 +00:00
autorx . task_list [ _task ] [ " task " ] . stop ( )
2018-05-26 09:18:53 +00:00
except Exception as e :
logging . error ( " Error stopping task - %s " % str ( e ) )
2017-05-06 23:42:46 +00:00
2018-05-26 09:18:53 +00:00
for _exporter in exporter_objects :
try :
_exporter . close ( )
except Exception as e :
logging . error ( " Error stopping exporter - %s " % str ( e ) )
2017-05-06 23:42:46 +00:00
2019-06-22 08:15:46 +00:00
if gpsd_adaptor != None :
gpsd_adaptor . close ( )
2017-05-06 23:42:46 +00:00
2018-05-26 09:18:53 +00:00
def telemetry_filter ( telemetry ) :
2021-04-28 09:36:30 +00:00
""" Filter incoming radiosonde telemetry based on various factors,
2018-05-26 09:18:53 +00:00
- Invalid Position
- Invalid Altitude
- Abnormal range from receiver .
- Invalid serial number .
2021-03-29 07:12:30 +00:00
- Abnormal date ( more than 6 hours from utcnow )
2017-12-22 12:45:40 +00:00
2018-06-01 11:32:24 +00:00
This function is defined within this script to avoid passing around large amounts of configuration data .
2017-12-22 12:45:40 +00:00
"""
global config
2018-05-09 12:00:56 +00:00
# First Check: zero lat/lon
2020-12-18 06:47:54 +00:00
if ( telemetry [ " lat " ] == 0.0 ) and ( telemetry [ " lon " ] == 0.0 ) :
logging . warning (
" Zero Lat/Lon. Sonde %s does not have GPS lock. " % telemetry [ " id " ]
)
2018-05-09 12:00:56 +00:00
return False
# Second check: Altitude cap.
2020-12-18 06:47:54 +00:00
if telemetry [ " alt " ] > config [ " max_altitude " ] :
_altitude_breach = telemetry [ " alt " ] - config [ " max_altitude " ]
logging . warning (
" Sonde %s position breached altitude cap by %d m. "
% ( telemetry [ " id " ] , _altitude_breach )
)
2017-12-22 12:45:40 +00:00
return False
2019-03-04 10:31:47 +00:00
# Third check: Number of satellites visible.
2020-12-18 06:47:54 +00:00
if " sats " in telemetry :
if telemetry [ " sats " ] < 4 :
logging . warning (
2022-01-30 00:05:17 +00:00
" Sonde %s can only see %d GNSS sats - discarding position as bad. "
2020-12-18 06:47:54 +00:00
% ( telemetry [ " id " ] , telemetry [ " sats " ] )
)
2019-03-04 10:31:47 +00:00
return False
# Fourth check - is the payload more than x km from our listening station.
2017-12-22 12:45:40 +00:00
# Only run this check if a station location has been provided.
2020-12-18 06:47:54 +00:00
if ( config [ " station_lat " ] != 0.0 ) and ( config [ " station_lon " ] != 0.0 ) :
2017-12-22 12:45:40 +00:00
# Calculate the distance from the station to the payload.
2020-12-18 06:47:54 +00:00
_listener = (
config [ " station_lat " ] ,
config [ " station_lon " ] ,
config [ " station_alt " ] ,
)
_payload = ( telemetry [ " lat " ] , telemetry [ " lon " ] , telemetry [ " alt " ] )
2017-12-22 12:45:40 +00:00
# Calculate using positon_info function from rotator_utils.py
_info = position_info ( _listener , _payload )
2020-12-18 06:47:54 +00:00
if _info [ " straight_distance " ] > config [ " max_radius_km " ] * 1000 :
_radius_breach = (
_info [ " straight_distance " ] / 1000.0 - config [ " max_radius_km " ]
)
logging . warning (
" Sonde %s position breached radius cap by %.1f km. "
% ( telemetry [ " id " ] , _radius_breach )
)
2020-06-20 08:07:22 +00:00
2020-12-18 06:47:54 +00:00
if config [ " radius_temporary_block " ] :
logging . warning (
" Blocking for %d minutes. " % config [ " temporary_block_time " ]
)
2020-06-20 08:07:22 +00:00
return " TempBlock "
else :
return False
2020-12-18 06:47:54 +00:00
if ( _info [ " straight_distance " ] < config [ " min_radius_km " ] * 1000 ) and config [
" radius_temporary_block "
] :
logging . warning (
" Sonde %s within minimum radius limit ( %.1f km). Blocking for %d minutes. "
% (
telemetry [ " id " ] ,
config [ " min_radius_km " ] ,
config [ " temporary_block_time " ] ,
)
)
2020-06-20 08:07:22 +00:00
return " TempBlock "
2017-12-22 12:45:40 +00:00
2021-03-29 07:12:30 +00:00
# DateTime Check
2021-04-17 11:34:11 +00:00
_delta_time = (
datetime . datetime . now ( datetime . timezone . utc ) - parse ( telemetry [ " datetime " ] )
) . total_seconds ( )
2021-03-29 07:12:30 +00:00
logging . debug ( " Delta time: %d " % _delta_time )
2021-04-17 11:34:11 +00:00
if abs ( _delta_time ) > ( 3600 * config [ " sonde_time_threshold " ] ) :
2021-03-29 07:12:30 +00:00
logging . warning (
2021-04-17 11:34:11 +00:00
" Sonde reported time too far from current UTC time. Either sonde time or system time is invalid. (Threshold: %d hours) "
% config [ " sonde_time_threshold " ]
2021-03-29 07:12:30 +00:00
)
return False
2018-03-18 07:44:04 +00:00
# Payload Serial Number Checks
2020-12-18 06:47:54 +00:00
_serial = telemetry [ " id " ]
2018-03-18 07:44:04 +00:00
# Run a Regex to match known Vaisala RS92/RS41 serial numbers (YWWDxxxx)
# RS92: https://www.vaisala.com/sites/default/files/documents/Vaisala%20Radiosonde%20RS92%20Serial%20Number.pdf
# RS41: https://www.vaisala.com/sites/default/files/documents/Vaisala%20Radiosonde%20RS41%20Serial%20Number.pdf
# This will need to be re-evaluated if we're still using this code in 2021!
2018-09-28 09:35:42 +00:00
# UPDATE: Had some confirmation that Vaisala will continue to use the alphanumeric numbering up until
# ~2025-2030, so have expanded the regex to match (and also support some older RS92s)
2021-06-03 11:10:18 +00:00
# Modified 2021-06 to be more flexible and match older sondes, and reprogrammed sondes.
# Still needs a letter at the start, but the numbers don't need to match the format exactly.
vaisala_callsign_valid = re . match ( r " [C-Z][ \ d][ \ d][ \ d] \ d {4} " , _serial )
2018-03-18 07:44:04 +00:00
2021-04-21 08:27:30 +00:00
# Just make sure we're not getting the 'xxxxxxxx' unknown serial from the DFM decoder.
if " DFM " in telemetry [ " type " ] :
2021-04-28 09:36:30 +00:00
dfm_callsign_valid = " x " not in _serial . split ( " - " ) [ 1 ]
2021-04-21 08:27:30 +00:00
else :
dfm_callsign_valid = False
2018-05-09 12:40:06 +00:00
2019-09-08 08:46:18 +00:00
# Check Meisei sonde callsigns for validity.
2020-10-30 07:10:48 +00:00
# meisei_ims returns a callsign of IMS100-xxxxxx until it receives the serial number, so we filter based on the x's being present or not.
2022-12-26 09:01:25 +00:00
if " MEISEI " in telemetry [ " type " ] or " IMS100 " in telemetry [ " type " ] or " RS11G " in telemetry [ " type " ] :
2020-12-18 06:47:54 +00:00
meisei_callsign_valid = " x " not in _serial . split ( " - " ) [ 1 ]
2019-09-08 10:31:27 +00:00
else :
meisei_callsign_valid = False
2021-04-17 11:34:11 +00:00
2021-02-26 11:30:49 +00:00
if " MRZ " in telemetry [ " type " ] :
mrz_callsign_valid = " x " not in _serial . split ( " - " ) [ 1 ]
else :
mrz_callsign_valid = False
2019-09-08 08:46:18 +00:00
2022-11-11 01:08:08 +00:00
# If Vaisala or DFMs, check the callsigns are valid. If M10/M20, iMet, MTS01 or LMS6, just pass it through - we get callsigns immediately and reliably from these.
2020-12-18 06:47:54 +00:00
if (
vaisala_callsign_valid
or dfm_callsign_valid
or meisei_callsign_valid
2021-02-26 11:30:49 +00:00
or mrz_callsign_valid
2020-12-18 06:47:54 +00:00
or ( " M10 " in telemetry [ " type " ] )
or ( " M20 " in telemetry [ " type " ] )
or ( " LMS " in telemetry [ " type " ] )
or ( " IMET " in telemetry [ " type " ] )
2022-11-11 01:08:08 +00:00
or ( " MTS01 " in telemetry [ " type " ] )
2023-09-13 03:59:10 +00:00
or ( " WXR " in telemetry [ " type " ] )
2020-12-18 06:47:54 +00:00
) :
2020-06-20 08:07:22 +00:00
return " OK "
2018-03-18 07:44:04 +00:00
else :
2020-12-18 06:47:54 +00:00
_id_msg = " Payload ID %s is invalid. " % telemetry [ " id " ]
2019-02-02 04:53:26 +00:00
# Add in a note about DFM sondes and their oddness...
2020-12-18 06:47:54 +00:00
if " DFM " in telemetry [ " id " ] :
2019-02-02 04:53:26 +00:00
_id_msg + = " Note: DFM sondes may take a while to get an ID. "
2021-04-17 11:34:11 +00:00
2021-02-26 11:30:49 +00:00
if " MRZ " in telemetry [ " id " ] :
_id_msg + = " Note: MRZ sondes may take a while to get an ID. "
2019-02-02 04:53:26 +00:00
logging . warning ( _id_msg )
2018-03-18 07:44:04 +00:00
return False
2019-06-22 08:15:46 +00:00
def station_position_update ( position ) :
2021-04-28 09:36:30 +00:00
""" Handle a callback from GPSDAdaptor object, and update each exporter object. """
2019-06-22 08:15:46 +00:00
global exporter_objects
# Quick sanity check of the incoming data
2020-12-18 06:47:54 +00:00
if " valid " not in position :
2019-06-22 08:15:46 +00:00
return
for _exporter in exporter_objects :
try :
2020-12-18 06:47:54 +00:00
_exporter . update_station_position (
position [ " latitude " ] , position [ " longitude " ] , position [ " altitude " ]
)
2019-06-22 08:15:46 +00:00
except AttributeError :
# This exporter does not require station position data.
pass
except Exception as e :
traceback . print_exc ( )
logging . error ( " Error updating exporter station position. " )
2020-10-25 05:52:12 +00:00
def email_error ( message = " foo " ) :
2021-04-28 09:36:30 +00:00
""" Helper function to email an error message, if the email exporter is available """
2020-10-25 05:52:12 +00:00
global email_exporter
2020-12-18 06:47:54 +00:00
if email_exporter and config [ " email_error_notifications " ] :
2020-10-25 05:52:12 +00:00
try :
email_exporter . send_notification_email ( message = message )
except Exception as e :
logging . error ( " Error attempting to send notification email: %s " % str ( e ) )
else :
logging . debug ( " Not sending Email notification, as Email not configured. " )
2019-06-22 08:15:46 +00:00
2018-05-26 09:18:53 +00:00
def main ( ) :
2021-04-28 09:36:30 +00:00
""" Main Loop """
2020-07-03 13:24:06 +00:00
global config , exporter_objects , exporter_functions , logging_level , rs92_ephemeris , gpsd_adaptor , email_exporter
2018-03-18 07:44:04 +00:00
2019-01-27 18:17:05 +00:00
# Command line arguments.
2018-05-26 09:18:53 +00:00
parser = argparse . ArgumentParser ( )
2020-12-18 06:47:54 +00:00
parser . add_argument (
" -c " ,
" --config " ,
default = " station.cfg " ,
help = " Receive Station Configuration File. Default: station.cfg " ,
)
parser . add_argument (
" -l " ,
" --log " ,
default = " ./log/ " ,
help = " Receive Station Log Path. Default: ./log/ " ,
)
parser . add_argument (
" -f " ,
" --frequency " ,
type = float ,
default = 0.0 ,
2021-04-28 10:19:20 +00:00
help = " Sonde Frequency Override (MHz). This overrides the only_scan list with the supplied frequency. " ,
2020-12-18 06:47:54 +00:00
)
parser . add_argument (
" -m " ,
" --type " ,
type = str ,
default = None ,
2021-02-26 11:30:49 +00:00
help = " Immediately start a decoder for a provided sonde type (Valid Types: RS41, RS92, DFM, M10, M20, IMET, IMET5, LMS6, MK2LMS, MEISEI, MRZ) " ,
2020-12-18 06:47:54 +00:00
)
parser . add_argument (
" -t " ,
" --timeout " ,
type = int ,
default = 0 ,
help = " Close auto_rx system after N minutes. Use 0 to run continuously. " ,
)
parser . add_argument (
" -v " , " --verbose " , help = " Enable debug output. " , action = " store_true "
)
parser . add_argument (
" -e " ,
" --ephemeris " ,
type = str ,
default = " None " ,
help = " Use a manually obtained ephemeris file when decoding RS92 Sondes. " ,
)
parser . add_argument (
" --systemlog " ,
action = " store_true " ,
default = False ,
help = " Write a auto_rx system log-file to ./log/ (default=False) " ,
)
2018-05-26 09:18:53 +00:00
args = parser . parse_args ( )
2017-04-29 02:00:10 +00:00
2018-06-01 11:32:24 +00:00
# Copy out timeout value, and convert to seconds,
2020-12-18 06:47:54 +00:00
_timeout = args . timeout * 60
2018-06-01 11:32:24 +00:00
# Copy out RS92 ephemeris value, if provided.
if args . ephemeris != " None " :
rs92_ephemeris = args . ephemeris
2018-05-26 09:18:53 +00:00
# Set log-level to DEBUG if requested
if args . verbose :
logging_level = logging . DEBUG
2017-04-29 02:00:10 +00:00
2019-01-27 18:17:05 +00:00
# Define the default logging path
logging_path = " ./log/ "
# Validate the user supplied log path
if os . path . isdir ( args . log ) :
logging_path = os . path . abspath ( args . log )
else :
2020-12-18 06:47:54 +00:00
# Using print because logging may not be established yet
2019-01-27 18:17:05 +00:00
print ( " Invalid logging path, using default. Does the folder exist? " )
2018-06-17 12:40:43 +00:00
2021-05-09 01:52:33 +00:00
# Update Global logging path, used by other modules.
autorx . logging_path = logging_path
2018-06-01 11:32:24 +00:00
# Configure logging
2019-01-31 07:22:26 +00:00
_log_suffix = datetime . datetime . utcnow ( ) . strftime ( " % Y % m %d - % H % M % S_system.log " )
_log_path = os . path . join ( logging_path , _log_suffix )
2017-04-29 14:06:47 +00:00
2022-10-22 22:50:36 +00:00
system_log_enabled = False
2019-03-05 10:38:28 +00:00
if args . systemlog :
# Only write out a logs to a system log file if we have been asked to.
# Systemd will capture and logrotate our logs anyway, so writing to our own log file is less useful.
2020-12-18 06:47:54 +00:00
logging . basicConfig (
format = " %(asctime)s %(levelname)s : %(message)s " ,
filename = _log_path ,
level = logging_level ,
)
2019-03-05 10:38:28 +00:00
logging . info ( " Opened new system log file: %s " % _log_path )
# Also add a separate stdout logger.
2020-12-18 06:47:54 +00:00
stdout_format = logging . Formatter ( " %(asctime)s %(levelname)s : %(message)s " )
2019-03-05 10:38:28 +00:00
stdout_handler = logging . StreamHandler ( sys . stdout )
stdout_handler . setFormatter ( stdout_format )
logging . getLogger ( ) . addHandler ( stdout_handler )
2022-10-22 22:50:36 +00:00
system_log_enabled = True
2019-03-05 10:38:28 +00:00
else :
# Otherwise, we only need the stdout logger, which if we don't specify a filename to logging.basicConfig,
# is the default...
2020-12-18 06:47:54 +00:00
logging . basicConfig (
format = " %(asctime)s %(levelname)s : %(message)s " , level = logging_level
)
2019-03-05 10:38:28 +00:00
2018-06-17 12:40:43 +00:00
2019-03-05 10:38:28 +00:00
# Set the requests/socketio loggers (and related) to only display critical log messages.
2018-06-17 12:40:43 +00:00
logging . getLogger ( " requests " ) . setLevel ( logging . CRITICAL )
logging . getLogger ( " urllib3 " ) . setLevel ( logging . CRITICAL )
2020-12-18 06:47:54 +00:00
logging . getLogger ( " werkzeug " ) . setLevel ( logging . ERROR )
logging . getLogger ( " socketio " ) . setLevel ( logging . ERROR )
logging . getLogger ( " engineio " ) . setLevel ( logging . ERROR )
logging . getLogger ( " geventwebsocket " ) . setLevel ( logging . ERROR )
2017-05-05 12:56:41 +00:00
2023-07-03 16:03:18 +00:00
# Check all the RS utilities exist.
2023-08-21 06:06:21 +00:00
logging . debug ( " Checking if required binaries exist " )
if not check_rs_utils ( config ) :
2023-07-03 16:03:18 +00:00
sys . exit ( 1 )
2018-05-26 09:18:53 +00:00
# Attempt to read in config file
logging . info ( " Reading configuration file... " )
_temp_cfg = read_auto_rx_config ( args . config )
if _temp_cfg is None :
logging . critical ( " Error in configuration file! Exiting... " )
sys . exit ( 1 )
2017-12-20 09:28:24 +00:00
else :
2018-05-26 09:18:53 +00:00
config = _temp_cfg
2020-12-18 06:47:54 +00:00
autorx . sdr_list = config [ " sdr_settings " ]
2017-12-20 09:28:24 +00:00
2023-08-21 06:06:21 +00:00
2022-10-22 22:50:36 +00:00
# Apply any logging changes based on configuration file settings.
if config [ " save_system_log " ] :
# Enable system logging.
if system_log_enabled == False :
# Clear all existing handlers, and add new ones.
logging . basicConfig (
format = " %(asctime)s %(levelname)s : %(message)s " ,
filename = _log_path ,
level = logging_level ,
force = True # This removes all existing handlers before adding new ones.
)
# Also add a separate stdout logger.
stdout_format = logging . Formatter ( " %(asctime)s %(levelname)s : %(message)s " )
stdout_handler = logging . StreamHandler ( sys . stdout )
stdout_handler . setFormatter ( stdout_format )
logging . getLogger ( ) . addHandler ( stdout_handler )
system_log_enabled = True
logging . info ( " Opened new system log file: %s " % _log_path )
if config [ " enable_debug_logging " ] :
# Set log level to logging.DEBUG
logging . getLogger ( ) . setLevel ( logging . DEBUG )
logging . debug ( " Log level set to DEBUG based on configuration file setting. " )
2022-11-17 00:03:40 +00:00
# Add the web interface logging handler.
web_handler = WebHandler ( )
logging . getLogger ( ) . addHandler ( web_handler )
2022-10-22 22:50:36 +00:00
2018-08-03 11:01:56 +00:00
2021-02-27 23:50:04 +00:00
# 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
# allows it to run indefinitely.
if args . type != None :
if args . type in VALID_SONDE_TYPES :
2021-04-17 11:34:11 +00:00
logging . warning (
" Overriding RX timeout for manually specified radiosonde type. Decoders will not automatically stop! "
)
2021-02-27 23:50:04 +00:00
config [ " rx_timeout " ] = 0
autorx . scan_results . put ( [ [ args . frequency * 1e6 , args . type ] ] )
else :
logging . error ( " Unknown Radiosonde Type: %s . Exiting. " % args . type )
sys . exit ( 1 )
2018-06-21 12:59:10 +00:00
# Start up the flask server.
# This needs to occur AFTER logging is setup, else logging breaks horribly for some reason.
2020-12-18 06:47:54 +00:00
start_flask ( host = config [ " web_host " ] , port = config [ " web_port " ] )
2018-06-21 12:59:10 +00:00
2021-04-28 09:35:43 +00:00
# If we have been supplied a frequency via the command line, override the only_scan list settings
2018-06-01 11:32:24 +00:00
# to only include the supplied frequency.
2018-05-26 09:18:53 +00:00
if args . frequency != 0.0 :
2021-04-28 09:35:43 +00:00
config [ " only_scan " ] = [ args . frequency ]
2017-05-05 12:56:41 +00:00
2018-05-26 09:18:53 +00:00
# Start our exporter options
# Telemetry Logger
2020-12-18 06:47:54 +00:00
if config [ " per_sonde_log " ] :
2023-10-03 10:05:08 +00:00
_logger = TelemetryLogger (
log_directory = logging_path ,
save_cal_data = config [ " save_cal_data " ]
)
2018-05-26 09:18:53 +00:00
exporter_objects . append ( _logger )
exporter_functions . append ( _logger . add )
2017-05-05 12:56:41 +00:00
2020-12-18 06:47:54 +00:00
if config [ " email_enabled " ] :
2018-07-04 08:13:58 +00:00
_email_notification = EmailNotification (
2020-12-18 06:47:54 +00:00
smtp_server = config [ " email_smtp_server " ] ,
smtp_port = config [ " email_smtp_port " ] ,
smtp_authentication = config [ " email_smtp_authentication " ] ,
smtp_login = config [ " email_smtp_login " ] ,
smtp_password = config [ " email_smtp_password " ] ,
mail_from = config [ " email_from " ] ,
mail_to = config [ " email_to " ] ,
mail_subject = config [ " email_subject " ] ,
2021-12-31 08:11:35 +00:00
mail_nearby_landing_subject = config [ " email_nearby_landing_subject " ] ,
2020-12-18 06:47:54 +00:00
station_position = (
config [ " station_lat " ] ,
config [ " station_lon " ] ,
config [ " station_alt " ] ,
) ,
2020-12-18 09:44:11 +00:00
launch_notifications = config [ " email_launch_notifications " ] ,
landing_notifications = config [ " email_landing_notifications " ] ,
2023-05-18 08:45:47 +00:00
encrypted_sonde_notifications = config [ " email_encrypted_sonde_notifications " ] ,
2020-12-18 09:44:11 +00:00
landing_range_threshold = config [ " email_landing_range_threshold " ] ,
2021-01-29 12:08:23 +00:00
landing_altitude_threshold = config [ " email_landing_altitude_threshold " ] ,
2020-12-18 06:47:54 +00:00
)
2020-07-03 13:24:06 +00:00
email_exporter = _email_notification
2018-07-04 08:13:58 +00:00
exporter_objects . append ( _email_notification )
exporter_functions . append ( _email_notification . add )
2021-04-23 11:06:59 +00:00
# Habitat Uploader - DEPRECATED - Sondehub DB now in use (>1.5.0)
# if config["habitat_enabled"]:
# if config["habitat_upload_listener_position"] is False:
# _habitat_station_position = None
# else:
# _habitat_station_position = (
# config["station_lat"],
# config["station_lon"],
# config["station_alt"],
# )
# _habitat = HabitatUploader(
# user_callsign=config["habitat_uploader_callsign"],
# user_antenna=config["habitat_uploader_antenna"],
# station_position=_habitat_station_position,
# synchronous_upload_time=config["habitat_upload_rate"],
# callsign_validity_threshold=config["payload_id_valid"],
# url=config["habitat_url"],
# )
# exporter_objects.append(_habitat)
# exporter_functions.append(_habitat.add)
2018-01-06 12:04:47 +00:00
2018-06-01 11:32:24 +00:00
# APRS Uploader
2020-12-18 06:47:54 +00:00
if config [ " aprs_enabled " ] :
2020-10-02 02:46:10 +00:00
2020-12-18 06:47:54 +00:00
if ( config [ " aprs_object_id " ] == " <id> " ) or (
config [ " aprs_use_custom_object_id " ] == False
) :
2018-05-26 11:51:55 +00:00
_aprs_object = None
else :
2020-12-18 06:47:54 +00:00
_aprs_object = config [ " aprs_object_id " ]
2018-05-26 11:51:55 +00:00
_aprs = APRSUploader (
2020-12-18 06:47:54 +00:00
aprs_callsign = config [ " aprs_user " ] ,
aprs_passcode = config [ " aprs_pass " ] ,
object_name_override = _aprs_object ,
object_comment = config [ " aprs_custom_comment " ] ,
position_report = config [ " aprs_position_report " ] ,
aprsis_host = config [ " aprs_server " ] ,
2020-12-22 23:11:13 +00:00
aprsis_port = config [ " aprs_port " ] ,
2022-03-01 07:20:02 +00:00
upload_time = config [ " aprs_upload_rate " ] ,
2020-12-18 06:47:54 +00:00
callsign_validity_threshold = config [ " payload_id_valid " ] ,
station_beacon = config [ " station_beacon_enabled " ] ,
station_beacon_rate = config [ " station_beacon_rate " ] ,
station_beacon_position = (
config [ " station_lat " ] ,
config [ " station_lon " ] ,
config [ " station_alt " ] ,
) ,
station_beacon_comment = config [ " station_beacon_comment " ] ,
station_beacon_icon = config [ " station_beacon_icon " ] ,
)
2018-05-26 11:51:55 +00:00
exporter_objects . append ( _aprs )
exporter_functions . append ( _aprs . add )
2017-07-16 10:11:13 +00:00
2019-01-27 18:17:05 +00:00
# OziExplorer
2020-12-18 06:47:54 +00:00
if config [ " ozi_enabled " ] or config [ " payload_summary_enabled " ] :
if config [ " ozi_enabled " ] :
_ozi_port = config [ " ozi_port " ]
2018-05-27 10:59:49 +00:00
else :
_ozi_port = None
2020-12-18 06:47:54 +00:00
if config [ " payload_summary_enabled " ] :
_summary_port = config [ " payload_summary_port " ]
2018-05-27 10:59:49 +00:00
else :
_summary_port = None
_ozimux = OziUploader (
2020-12-18 06:47:54 +00:00
ozimux_port = _ozi_port ,
payload_summary_port = _summary_port ,
update_rate = config [ " ozi_update_rate " ] ,
station = config [ " habitat_uploader_callsign " ] ,
)
2018-05-27 10:59:49 +00:00
exporter_objects . append ( _ozimux )
exporter_functions . append ( _ozimux . add )
2017-04-29 02:00:10 +00:00
2019-01-27 18:17:05 +00:00
# Rotator
2020-12-18 06:47:54 +00:00
if config [ " rotator_enabled " ] :
2018-10-04 11:45:18 +00:00
_rotator = Rotator (
2020-12-18 06:47:54 +00:00
station_position = (
config [ " station_lat " ] ,
config [ " station_lon " ] ,
config [ " station_alt " ] ,
) ,
rotctld_host = config [ " rotator_hostname " ] ,
rotctld_port = config [ " rotator_port " ] ,
rotator_update_rate = config [ " rotator_update_rate " ] ,
rotator_update_threshold = config [ " rotation_threshold " ] ,
rotator_homing_enabled = config [ " rotator_homing_enabled " ] ,
rotator_homing_delay = config [ " rotator_homing_delay " ] ,
rotator_home_position = [
config [ " rotator_home_azimuth " ] ,
config [ " rotator_home_elevation " ] ,
] ,
)
2019-01-27 18:17:05 +00:00
2018-10-04 11:45:18 +00:00
exporter_objects . append ( _rotator )
exporter_functions . append ( _rotator . add )
2021-04-23 11:06:59 +00:00
# Sondehub v2 Database
2021-01-29 11:05:48 +00:00
if config [ " sondehub_enabled " ] :
2021-01-30 12:00:45 +00:00
if config [ " habitat_upload_listener_position " ] is False :
_sondehub_station_position = None
else :
_sondehub_station_position = (
config [ " station_lat " ] ,
config [ " station_lon " ] ,
config [ " station_alt " ] ,
)
2021-04-17 11:34:11 +00:00
2021-01-29 11:05:48 +00:00
_sondehub = SondehubUploader (
user_callsign = config [ " habitat_uploader_callsign " ] ,
2021-01-30 12:00:45 +00:00
user_position = _sondehub_station_position ,
user_antenna = config [ " habitat_uploader_antenna " ] ,
2021-03-28 10:06:58 +00:00
contact_email = config [ " sondehub_contact_email " ] ,
2021-02-21 01:25:44 +00:00
upload_rate = config [ " sondehub_upload_rate " ] ,
2021-01-29 11:05:48 +00:00
)
exporter_objects . append ( _sondehub )
exporter_functions . append ( _sondehub . add )
2020-12-18 06:47:54 +00:00
_web_exporter = WebExporter ( max_age = config [ " web_archive_age " ] )
2018-06-17 13:16:30 +00:00
exporter_objects . append ( _web_exporter )
exporter_functions . append ( _web_exporter . add )
2019-06-22 08:15:46 +00:00
# GPSD Startup
2020-12-18 06:47:54 +00:00
if config [ " gpsd_enabled " ] :
2019-06-22 08:15:46 +00:00
gpsd_adaptor = GPSDAdaptor (
2020-12-18 06:47:54 +00:00
hostname = config [ " gpsd_host " ] ,
port = config [ " gpsd_port " ] ,
callback = station_position_update ,
)
2018-03-18 07:44:04 +00:00
2021-04-17 02:20:19 +00:00
version_startup_check ( )
2019-03-24 06:18:19 +00:00
2018-06-01 11:32:24 +00:00
# Note the start time.
_start_time = time . time ( )
2017-04-29 02:00:10 +00:00
2021-02-27 23:50:04 +00:00
# If we have been asked to start decoding a specific radiosonde type, we need to start up
# the decoder immediately, before a scanner thread is started.
2019-03-10 02:32:27 +00:00
if args . type != None :
handle_scan_results ( )
2019-01-27 18:17:05 +00:00
# Loop.
2018-05-26 09:18:53 +00:00
while True :
2018-06-01 11:32:24 +00:00
# Check for finished tasks.
2018-05-26 09:18:53 +00:00
clean_task_list ( )
2018-06-01 11:32:24 +00:00
# Handle any new scan results.
2018-05-26 09:18:53 +00:00
handle_scan_results ( )
2018-06-01 11:32:24 +00:00
# Sleep a little bit.
2018-05-26 09:18:53 +00:00
time . sleep ( 2 )
2017-04-29 02:00:10 +00:00
2020-07-03 13:24:06 +00:00
if len ( autorx . sdr_list ) == 0 :
# No Functioning SDRs!
logging . critical ( " Task Manager - No SDRs available! Cannot continue... " )
2020-10-25 05:52:12 +00:00
email_error ( " auto_rx exited due to all SDRs being marked as failed. " )
2020-07-03 13:24:06 +00:00
raise IOError ( " No SDRs available! " )
2018-06-01 11:32:24 +00:00
# Allow a timeout after a set time, for users who wish to run auto_rx
# within a cronjob.
2020-12-18 06:47:54 +00:00
if ( _timeout > 0 ) and ( ( time . time ( ) - _start_time ) > _timeout ) :
2018-06-01 11:32:24 +00:00
logging . info ( " Shutdown time reached. Closing. " )
2020-12-18 06:47:54 +00:00
stop_flask ( host = config [ " web_host " ] , port = config [ " web_port " ] )
2018-06-01 11:32:24 +00:00
stop_all ( )
2023-07-03 16:03:18 +00:00
sys . exit ( 2 )
2018-06-01 11:32:24 +00:00
2018-05-07 12:03:31 +00:00
2018-05-26 09:18:53 +00:00
if __name__ == " __main__ " :
2017-12-20 04:23:29 +00:00
2018-05-26 09:18:53 +00:00
try :
main ( )
2017-12-20 04:23:29 +00:00
except KeyboardInterrupt :
2018-06-01 11:32:24 +00:00
# Upon CTRL+C, shutdown all threads and exit.
2020-12-18 06:47:54 +00:00
stop_flask ( host = config [ " web_host " ] , port = config [ " web_port " ] )
2018-05-26 09:18:53 +00:00
stop_all ( )
2023-07-03 16:03:18 +00:00
sys . exit ( 0 )
2018-05-26 09:18:53 +00:00
except Exception as e :
2018-06-01 11:32:24 +00:00
# Upon exceptions, attempt to shutdown threads and exit.
2018-05-26 09:18:53 +00:00
traceback . print_exc ( )
print ( " Main Loop Error - %s " % str ( e ) )
2023-07-03 16:03:18 +00:00
if flask_running ( ) :
stop_flask ( host = config [ " web_host " ] , port = config [ " web_port " ] )
2018-05-26 09:18:53 +00:00
stop_all ( )
2023-07-03 16:03:18 +00:00
sys . exit ( 3 )