kopia lustrzana https://github.com/projecthorus/radiosonde_auto_rx
commit
dbd5ed7325
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.")
|
||||
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
_decim = $('#decimation-input').val();
|
||||
|
||||
$.post(
|
||||
"/get_log_detail",
|
||||
"get_log_detail",
|
||||
{serial: _serial, decimation:_decim},
|
||||
function(data){
|
||||
console.log(data);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -6,3 +6,4 @@ numpy
|
|||
requests
|
||||
semver
|
||||
simplekml
|
||||
simple-websocket
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
Ładowanie…
Reference in New Issue