kopia lustrzana https://github.com/projecthorus/radiosonde_auto_rx
Added cleaning of web telemetry archive data. Add web server settings to config.
rodzic
558fc15f37
commit
7d1d98b6a0
|
|
@ -396,7 +396,7 @@ def main():
|
|||
|
||||
# Start up the flask server.
|
||||
# This needs to occur AFTER logging is setup, else logging breaks horribly for some reason.
|
||||
start_flask()
|
||||
start_flask(port=config['web_port'])
|
||||
|
||||
# If we have been supplied a frequency via the command line, override the whitelist settings
|
||||
# to only include the supplied frequency.
|
||||
|
|
@ -476,7 +476,7 @@ def main():
|
|||
exporter_objects.append(_ozimux)
|
||||
exporter_functions.append(_ozimux.add)
|
||||
|
||||
_web_exporter = WebExporter()
|
||||
_web_exporter = WebExporter(max_age=config['web_archive_age'])
|
||||
exporter_objects.append(_web_exporter)
|
||||
exporter_functions.append(_web_exporter.add)
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@ def read_auto_rx_config(filename):
|
|||
'aprs_server' : 'rotate.aprs2.net',
|
||||
'aprs_object_id': '<id>',
|
||||
'aprs_custom_comment': 'Radiosonde Auto-RX <freq>',
|
||||
# Web Settings,
|
||||
'web_port' : 5000,
|
||||
'web_archive_age': 120,
|
||||
# Advanced Parameters
|
||||
'search_step' : 800,
|
||||
'snr_threshold' : 10,
|
||||
|
|
@ -183,6 +186,17 @@ def read_auto_rx_config(filename):
|
|||
logging.error("Config - Missing uploader_antenna setting. Using default.")
|
||||
auto_rx_config['habitat_uploader_antenna'] = '1/4-wave'
|
||||
|
||||
# New settings added in 20180624.
|
||||
try:
|
||||
auto_rx_config['web_port'] = config.getint('web', 'web_port')
|
||||
auto_rx_config['web_archive_age'] = config.getint('web', 'archive_age')
|
||||
except:
|
||||
logging.error("Config - Missing Web Server settings. Using defaults.")
|
||||
auto_rx_config['web_port'] = 5000
|
||||
auto_rx_config['web_archive_age'] = 120
|
||||
|
||||
|
||||
|
||||
# Now we attempt to read in the individual SDR parameters.
|
||||
auto_rx_config['sdr_settings'] = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@
|
|||
// Event handler for Log data.
|
||||
socket.on('log_event', function(msg) {
|
||||
// On receipt, add to the log div.
|
||||
// TODO: Have this div be a scrollable area, and always show the latest log message.
|
||||
$('#log').append('<br>' + $('<div/>').text(msg.timestamp + ' ' + msg.level + ": " + msg.msg).html());
|
||||
// Scroll to the bottom of the log div
|
||||
$("#log").scrollTop($("#log")[0].scrollHeight);
|
||||
|
|
@ -176,12 +175,6 @@
|
|||
// path: Leaflet polyline object.
|
||||
var sonde_positions = {};
|
||||
|
||||
// Add a marker which will be updated with sonde positions.
|
||||
// TODO: Handle multiple sondes!
|
||||
var sonde_marker = L.marker([0.0,0.0],
|
||||
{title:'Sonde', icon: sondeAscentIcon}
|
||||
).addTo(sondemap);
|
||||
|
||||
|
||||
function updateTelemetryText(){
|
||||
// Produce the text to go in the telemetry div.
|
||||
|
|
@ -197,6 +190,31 @@
|
|||
$('#telemetry').html(telem_text);
|
||||
}
|
||||
|
||||
// Grab the recent archive of telemetry data
|
||||
// This should only ever be run once - on page load.
|
||||
// TODO: See how this performs when loading an entire flights worth of position data (could be a lot!)
|
||||
var initial_load_complete = false;
|
||||
$.ajax({
|
||||
url: "/get_telemetry_archive",
|
||||
dataType: 'json',
|
||||
async: true,
|
||||
success: function(data) {
|
||||
// Populate sonde_positions with data from the telemetry archive.
|
||||
for (sonde_id in data){
|
||||
var telem = data[sonde_id].latest_telem;
|
||||
sonde_positions[sonde_id] = {
|
||||
latest_data: telem,
|
||||
path: L.polyline(data[sonde_id].path,{title:telem.id + " Path", color:'blue'}).addTo(sondemap),
|
||||
marker : L.marker([telem.lat, telem.lon, telem.alt],{title:telem.id, icon: sondeAscentIcon}).addTo(sondemap),
|
||||
};
|
||||
if (telem.vel_v < 0){
|
||||
sonde_positions[sonde_id].marker.setIcon(sondeDescentIcon);
|
||||
}
|
||||
}
|
||||
initial_load_complete = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Telemetry event handler.
|
||||
// We will get one of these with every line of telemetry decoded
|
||||
|
|
@ -204,19 +222,24 @@
|
|||
// Telemetry Event messages contain the entire telemetry dictionary, as produced by the SondeDecoder class.
|
||||
// This includes the fields: ['frame', 'id', 'datetime', 'lat', 'lon', 'alt', 'temp', 'type', 'freq', 'freq_float']
|
||||
|
||||
if(initial_load_complete == false){
|
||||
// If we have not completed our initial load of telemetry data, discard this data.
|
||||
return
|
||||
}
|
||||
|
||||
// Have we seen this sonde before?
|
||||
if (sonde_positions.hasOwnProperty(msg.id) == false){
|
||||
// Nope, add a property to the sonde_positions object, and setup markers for the sonde.
|
||||
sonde_positions[msg.id] = {
|
||||
latest_data : msg,
|
||||
marker : L.marker([msg.lat, msg.lon],{title:msg.id, icon: sondeAscentIcon}).addTo(sondemap),
|
||||
path: L.polyline([[msg.lat, msg.lon]],{title:msg.id + " Path", color:'blue'}).addTo(sondemap)
|
||||
marker : L.marker([msg.lat, msg.lon, msg.alt],{title:msg.id, icon: sondeAscentIcon}).addTo(sondemap),
|
||||
path: L.polyline([[msg.lat, msg.lon, msg.alt]],{title:msg.id + " Path", color:'blue'}).addTo(sondemap)
|
||||
};
|
||||
} else{
|
||||
// Yep - update the sonde_positions entry.
|
||||
sonde_positions[msg.id].latest_data = msg;
|
||||
sonde_positions[msg.id].path.addLatLng([msg.lat, msg.lon]);
|
||||
sonde_positions[msg.id].marker.setLatLng([msg.lat, msg.lon]).update();
|
||||
sonde_positions[msg.id].path.addLatLng([msg.lat, msg.lon, msg.alt]);
|
||||
sonde_positions[msg.id].marker.setLatLng([msg.lat, msg.lon, msg.alt]).update();
|
||||
|
||||
if (msg.vel_v < 0){
|
||||
sonde_positions[msg.id].marker.setIcon(sondeDescentIcon);
|
||||
|
|
@ -227,7 +250,7 @@
|
|||
|
||||
// Centre the map on the sonde position.
|
||||
sondemap.panTo([msg.lat, msg.lon]);
|
||||
|
||||
// Update the telemetry text display
|
||||
updateTelemetryText();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import json
|
|||
import logging
|
||||
import random
|
||||
import requests
|
||||
import time
|
||||
import traceback
|
||||
import autorx
|
||||
import autorx.config
|
||||
|
|
@ -17,6 +18,12 @@ import autorx.scan
|
|||
from threading import Thread
|
||||
import flask
|
||||
from flask_socketio import SocketIO
|
||||
try:
|
||||
# Python 2
|
||||
from Queue import Queue
|
||||
except ImportError:
|
||||
# Python 3
|
||||
from queue import Queue
|
||||
|
||||
|
||||
# Instantiate our Flask app.
|
||||
|
|
@ -32,7 +39,12 @@ flask_shutdown_key = "temp"
|
|||
# SocketIO instance
|
||||
socketio = SocketIO(app)
|
||||
|
||||
# Global store of telemetry data, which we will add data do and manage.
|
||||
# Global store of telemetry data, which we will add data to and manage.
|
||||
# Under each key (which will be the sonde ID), we will have a dictionary containing:
|
||||
# 'latest_timestamp': timestamp (unix timestamp) of when the last packet was received.
|
||||
# 'latest_telem': telemetry dictionary.
|
||||
# 'path': list of [lat,lon,alt] pairs
|
||||
#
|
||||
flask_telemetry_store = {}
|
||||
|
||||
#
|
||||
|
|
@ -97,6 +109,12 @@ def flask_get_scan_data():
|
|||
return json.dumps(autorx.scan.scan_result)
|
||||
|
||||
|
||||
@app.route("/get_telemetry_archive")
|
||||
def flask_get_telemetry_archive():
|
||||
""" Return a copy of the telemetry archive """
|
||||
return json.dumps(flask_telemetry_store)
|
||||
|
||||
|
||||
@app.route("/shutdown/<shutdown_key>")
|
||||
def shutdown_flask(shutdown_key):
|
||||
""" Shutdown the Flask Server """
|
||||
|
|
@ -176,11 +194,44 @@ class WebExporter(object):
|
|||
# We require the following fields to be present in the incoming telemetry dictionary data
|
||||
REQUIRED_FIELDS = ['frame', 'id', 'datetime', 'lat', 'lon', 'alt', 'temp', 'type', 'freq', 'freq_float', 'datetime_dt']
|
||||
|
||||
def __init__(self):
|
||||
""" """
|
||||
pass
|
||||
def __init__(self,
|
||||
max_age = 120):
|
||||
""" Initialise a WebExporter object.
|
||||
|
||||
Args:
|
||||
max_age: Store telemetry data up to X hours old
|
||||
"""
|
||||
|
||||
self.max_age = max_age*60
|
||||
self.input_queue = Queue()
|
||||
|
||||
# Start the input queue processing thread.
|
||||
self.input_processing_running = True
|
||||
self.input_thread = Thread(target=self.process_queue)
|
||||
self.input_thread.start()
|
||||
|
||||
|
||||
def process_queue(self):
|
||||
""" Process data from the input queue.
|
||||
"""
|
||||
while self.input_processing_running:
|
||||
# Read in all queue items and handle them.
|
||||
while not self.input_queue.empty():
|
||||
self.handle_telemetry(self.input_queue.get())
|
||||
|
||||
# Check the telemetry store for old data.
|
||||
self.clean_telemetry_store()
|
||||
|
||||
# Wait a short time before processing new data
|
||||
time.sleep(0.1)
|
||||
|
||||
logging.debug("WebExporter - Closed Processing thread.")
|
||||
|
||||
|
||||
def handle_telemetry(self,telemetry):
|
||||
""" Send incoming telemetry to clients, and add it to the telemetry store. """
|
||||
global flask_telemetry_store
|
||||
|
||||
def add(self, telemetry):
|
||||
for _field in self.REQUIRED_FIELDS:
|
||||
if _field not in telemetry:
|
||||
self.log_error("JSON object missing required field %s" % _field)
|
||||
|
|
@ -190,10 +241,41 @@ class WebExporter(object):
|
|||
_telem.pop('datetime_dt')
|
||||
socketio.emit('telemetry_event', _telem, namespace='/update_status')
|
||||
|
||||
# Add the telemetry information to the global telemetry store
|
||||
if _telem['id'] not in flask_telemetry_store:
|
||||
flask_telemetry_store[_telem['id']] = {'timestamp':time.time(), 'latest_telem':_telem, 'path':[]}
|
||||
|
||||
flask_telemetry_store[_telem['id']]['path'].append([_telem['lat'],_telem['lon'],_telem['alt']])
|
||||
flask_telemetry_store[_telem['id']]['latest_telem'] = _telem
|
||||
flask_telemetry_store[_telem['id']]['timestamp'] = time.time()
|
||||
|
||||
|
||||
def clean_telemetry_store(self):
|
||||
""" Remove any old data from the telemetry store """
|
||||
global flask_telemetry_store
|
||||
|
||||
_now = time.time()
|
||||
_telem_ids = list(flask_telemetry_store.keys())
|
||||
for _id in _telem_ids:
|
||||
# If the most recently telemetry is older than self.max_age, remove all data for
|
||||
# that sonde from the archive.
|
||||
if (_now - flask_telemetry_store[_id]['timestamp']) > self.max_age:
|
||||
flask_telemetry_store.pop(_id)
|
||||
logging.debug("WebExporter - Removed Sonde #%s from archive." % _id)
|
||||
|
||||
|
||||
|
||||
def add(self, telemetry):
|
||||
# Add it to the queue if we are running.
|
||||
if self.input_processing_running:
|
||||
self.input_queue.put(telemetry)
|
||||
else:
|
||||
logging.error("WebExporter - Processing not running, discarding.")
|
||||
|
||||
|
||||
def close(self):
|
||||
""" Dummy close function """
|
||||
pass
|
||||
""" Shutdown """
|
||||
self.input_processing_running = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -164,6 +164,16 @@ max_altitude = 50000
|
|||
# Discard positions more than 1000 km from the observation station location (if set)
|
||||
max_radius_km = 1000
|
||||
|
||||
|
||||
# Web Interface Settings
|
||||
[web]
|
||||
# Server Port
|
||||
web_port = 5000
|
||||
# Archive Age - How long to keep a sonde telemetry in memory for the web client to access, in minutes
|
||||
# Note: The higher this number, the more data the client will need to load in on startup
|
||||
archive_age = 120
|
||||
|
||||
|
||||
# Advanced Settings
|
||||
# These control low-level settings within various modules.
|
||||
# Playing with them may result in odd behaviour.
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue