Added cleaning of web telemetry archive data. Add web server settings to config.

pull/71/head
Mark Jessop 2018-06-23 23:43:51 +09:30
rodzic 558fc15f37
commit 7d1d98b6a0
5 zmienionych plików z 150 dodań i 21 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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'] = {}

Wyświetl plik

@ -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();
});

Wyświetl plik

@ -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__":

Wyświetl plik

@ -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.