Added abort prediction support, dynamic updates of predictor settings.

bearings
Mark Jessop 2018-07-17 22:53:59 +09:30
rodzic b2d5f46859
commit ca6180f7bf
2 zmienionych plików z 146 dodań i 54 usunięć

Wyświetl plik

@ -34,7 +34,13 @@ socketio = SocketIO(app)
# Global stores of data.
# Don't expose these settings to the client!
pred_settings = {
'pred_binary': "./pred",
'gfs_path': "./gfs/",
}
# These settings are shared between server and all clients, and are updated dynamically.
chasemapper_config = {
# Start location for the map (until either a chase car position, or balloon position is available.)
'default_lat': -34.9,
@ -43,13 +49,11 @@ chasemapper_config = {
# Predictor settings
'pred_enabled': False, # Enable running and display of predicted flight paths.
# Default prediction settings (actual values will be used once the flight is underway)
'pred_model': "No Data",
'pred_model': "Disabled",
'pred_desc_rate': 6.0,
'pred_burst': 28000,
'show_abort': True, # Show a prediction of an 'abort' paths (i.e. if the balloon bursts *now*)
'pred_binary': "./pred",
'gfs_path': "./gfs/",
'predictor_update_rate': 15 # Update predictor every 15 seconds.
'pred_update_rate': 15 # Update predictor every 15 seconds.
}
# Payload data Stores
@ -80,12 +84,26 @@ def flask_get_config():
return json.dumps(chasemapper_config)
def flask_emit_event(event_name="none", data={}):
""" Emit a socketio event to any clients. """
socketio.emit(event_name, data, namespace='/chasemapper')
@socketio.on('client_settings_update', namespace='/chasemapper')
def client_settings_update(data):
global chasemapper_config
# Overwrite local config data with data from the client.
# TODO: Some sanitization of this data... this could lead to bad things.
chasemapper_config = data
# Updates based on
# Push settings back out to all clients.
flask_emit_event('server_settings_update', chasemapper_config)
def handle_new_payload_position(data):
_lat = data['lat']
@ -162,6 +180,7 @@ def handle_new_payload_position(data):
# Predictor Code
#
predictor = None
predictor_semaphore = False
predictor_thread_running = True
predictor_thread = None
@ -172,7 +191,7 @@ def predictorThread():
while predictor_thread_running:
run_prediction()
for i in range(int(chasemapper_config['predictor_update_rate'])):
for i in range(int(chasemapper_config['pred_update_rate'])):
time.sleep(1)
if predictor_thread_running == False:
return
@ -182,9 +201,11 @@ def run_prediction():
''' Run a Flight Path prediction '''
global chasemapper_config, current_payloads, current_payload_tracks, predictor
if predictor == None:
if (predictor == None) or (chasemapper_config['pred_enabled'] == False):
return
# Set the semaphore so we don't accidentally kill the predictor object while it's running.
predictor_semaphore = True
for _payload in current_payload_tracks:
_current_pos = current_payload_tracks[_payload].get_latest_state()
@ -240,39 +261,52 @@ def run_prediction():
else:
logging.error("Prediction Failed.")
# if _run_abort_prediction and (_current_pos['alt'] < burst_alt) and (_current_pos['is_descending'] == False):
# print("Running Abort Prediction... ")
# _pred_path = _predictor.predict(
# launch_lat=_current_pos['lat'],
# launch_lon=_current_pos['lon'],
# launch_alt=_current_pos['alt'],
# ascent_rate=_current_pos['ascent_rate'],
# descent_rate=_desc_rate,
# burst_alt=_current_pos['alt']+200,
# launch_time=_current_pos['time'])
# Abort predictions
if chasemapper_config['show_abort'] and (_current_pos['alt'] < chasemapper_config['pred_burst']) and (_current_pos['is_descending'] == False):
logging.info("Running Abort Predictor for: %s." % _payload)
# if len(_pred_path) > 1:
# _pred_path.insert(0,_current_pos_list)
# _abort_prediction = _pred_path
# _abort_prediction_valid = True
# print("Abort Prediction Updated, %d points." % len(_pred_path))
# else:
# print("Prediction Failed.")
# else:
# _abort_prediction_valid = False
_abort_pred_path = predictor.predict(
launch_lat=_current_pos['lat'],
launch_lon=_current_pos['lon'],
launch_alt=_current_pos['alt'],
ascent_rate=_current_pos['ascent_rate'],
descent_rate=_desc_rate,
burst_alt=_current_pos['alt']+200,
launch_time=_current_pos['time'],
descent_mode=_current_pos['is_descending'])
# # If have been asked to run an abort prediction, but we are descent, set the is_valid
# # flag to false, so the abort prediction is not plotted.
# if _run_abort_prediction and _current_pos['is_descending']:
# _abort_prediction_valid == False
if len(_pred_path) > 1:
# Valid Prediction!
_abort_pred_path.insert(0,_current_pos_list)
# Convert from predictor output format to a polyline.
_abort_pred_output = []
for _point in _abort_pred_path:
_abort_pred_output.append([_point[1], _point[2], _point[3]])
current_payloads[_payload]['abort_path'] = _abort_pred_output
current_payloads[_payload]['abort_landing'] = _abort_pred_output[-1]
logging.info("Abort Prediction Updated, %d data points." % len(_pred_path))
else:
logging.error("Prediction Failed.")
current_payloads[_payload]['abort_path'] = []
current_payloads[_payload]['abort_landing'] = []
else:
# Zero the abort path and landing
current_payloads[_payload]['abort_path'] = []
current_payloads[_payload]['abort_landing'] = []
predictor_semaphore = False
# Send the web client the updated prediction data.
_client_data = {
'callsign': _payload,
'pred_path': current_payloads[_payload]['pred_path'],
'pred_landing': current_payloads[_payload]['pred_landing'],
'burst': current_payloads[_payload]['burst'],
'abort_path': [],
'abort_landing': []
'abort_path': current_payloads[_payload]['abort_path'],
'abort_landing': current_payloads[_payload]['abort_landing']
}
flask_emit_event('predictor_update', _client_data)
@ -284,7 +318,7 @@ def initPredictor():
from cusfpredict.utils import gfs_model_age
# Check if we have any GFS data
_model_age = gfs_model_age(chasemapper_config['gfs_path'])
_model_age = gfs_model_age(pred_settings['gfs_path'])
if _model_age == "Unknown":
logging.error("No GFS data in directory.")
chasemapper_config['pred_model'] = "No GFS Data."
@ -292,12 +326,16 @@ def initPredictor():
else:
chasemapper_config['pred_model'] = _model_age
flask_emit_event('predictor_model_update',{'model':_model_age})
predictor = Predictor(bin_path=chasemapper_config['pred_binary'], gfs_path=chasemapper_config['gfs_path'])
predictor = Predictor(bin_path=pred_settings['pred_binary'], gfs_path=pred_settings['gfs_path'])
# Start up the predictor thread.
predictor_thread = Thread(target=predictorThread)
predictor_thread.start()
# Set the predictor to enabled, and update the clients.
chasemapper_config['pred_enabled'] = True
flask_emit_event('server_settings_update', chasemapper_config)
except Exception as e:
traceback.print_exc()
logging.error("Loading predictor failed: " + str(e))
@ -310,6 +348,7 @@ def initPredictor():
@socketio.on('download_model', namespace='/chasemapper')
def download_new_model(data):
""" Trigger a download of a new weather model """
logging.info("Web Client Initiated request for new predictor data.")
pass
# TODO

Wyświetl plik

@ -46,6 +46,7 @@
// Default prediction settings (actual values will be used once the flight is underway)
pred_desc_rate: 6.0,
pred_burst: 28000,
pred_update_rate: 15,
pred_model: 'Disabled',
show_abort: true, // Show a prediction of an 'abort' paths (i.e. if the balloon bursts *now*)
};
@ -80,6 +81,7 @@
// Other markers which may be added. (TBD, probably other chase car positions via the LoRa payload?)
var misc_markers = {};
var updateSettings;
$(document).ready(function() {
// Use the 'chasemapper' namespace for all of our traffic
@ -90,6 +92,19 @@
// http[s]://<domain>:<port>[/<namespace>]
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
function serverSettingsUpdate(data){
// Accept a json blob of settings data from the client, and update our local store.
chase_config = data;
// Update a few fields based on this data.
$("#predictorModel").html("<b>Current Model: </b>" + chase_config.pred_model);
$('#burstAlt').val(chase_config.pred_burst.toFixed(0));
$('#descentRate').val(chase_config.pred_desc_rate.toFixed(1));
$('#predUpdateRate').val(chase_config.pred_update_rate.toFixed(0));
$("#predictorEnabled").prop('checked', chase_config.pred_enabled);
$("#abortPredictionEnabled").prop('checked', chase_config.show_abort);
}
// Grab the System config on startup.
// Refer to config.py for the contents of the configuration blob.
$.ajax({
@ -97,13 +112,49 @@
dataType: 'json',
async: false, // Yes, this is deprecated...
success: function(data) {
chase_config = data;
// Update a few fields based on this data.
$("#predictorModel").html("<b>Current Model: </b>" + chase_config.pred_model);
$('#burstAlt').val(chase_config.pred_burst.toFixed(0));
$('#descentRate').val(chase_config.pred_desc_rate.toFixed(1));
serverSettingsUpdate(data);
}
});
// Handler for further settings updates.
socket.on('server_settings_update', function(data){
serverSettingsUpdate(data);
});
// Settings updates.
updateSettings = function(){
chase_config.pred_enabled = document.getElementById("predictorEnabled").checked;
chase_config.show_abort = document.getElementById("abortPredictionEnabled").checked;
// Attempt to parse the text field values.
var _burst_alt = parseFloat($('#burstAlt').val());
if (isNaN(_burst_alt) == false){
chase_config.pred_burst = _burst_alt;
}
var _desc_rate = parseFloat($('#descentRate').val());
if (isNaN(_desc_rate) == false){
chase_config.pred_desc_rate = _desc_rate
}
var _update_rate = parseInt($('#predUpdateRate').val());
if (isNaN(_update_rate) == false){
chase_config.pred_update_rate = _update_rate
}
socket.emit('client_settings_update', chase_config);
};
// Use the jquery on-changed call for text entry fields,
// so they only fire after they lose focus.
$("#burstAlt").change(function(){
updateSettings();
});
$("#descentRate").change(function(){
updateSettings();
});
$("#predUpdateRate").change(function(){
updateSettings();
});
// Event handler for Log data.
socket.on('log_event', function(msg) {
@ -525,21 +576,19 @@
// Update the predicted path.
balloon_positions[_callsign].pred_path.setLatLngs(data.pred_path);
if (data.hasOwnProperty("abort_landing") == true){
if (data.abort_landing.length == 3){
// Only update the abort data if there is actually abort data to show.
if(data.abort_landing.length == 3){
if (balloon_positions[_callsign].abort_marker == null){
balloon_positions[callsign].abort_marker = L.marker(data.abort_landing,{title:callsign + " Abort", icon: abortIcon})
.bindTooltip(callsign + " Abort Landing",{permanent:false,direction:'right'});
if(chase_config.show_abort == true){
balloon_positions[callsign].abort_marker.addTo(map);
}
}else{
balloon_positions[callsign].abort_marker.setLatLng(data.abort_landing);
if (balloon_positions[_callsign].abort_marker == null){
balloon_positions[callsign].abort_marker = L.marker(data.abort_landing,{title:callsign + " Abort", icon: abortIcon})
.bindTooltip(callsign + " Abort Landing",{permanent:false,direction:'right'});
if(chase_config.show_abort == true){
balloon_positions[callsign].abort_marker.addTo(map);
}
balloon_positions[_callsign].abort_path.setLatLngs(data.abort_path);
}else{
balloon_positions[callsign].abort_marker.setLatLng(data.abort_landing);
}
balloon_positions[_callsign].abort_path.setLatLngs(data.abort_path);
}else{
// Clear out the abort and abort marker data.
balloon_positions[_callsign].abort_path.setLatLngs([]);
@ -593,8 +642,6 @@
}
}
}, age_update_rate);
});
</script>
</head>
@ -645,7 +692,10 @@
<b>Download Model</b> <button type="button" class="paramSelector" id="downloadModel">Download</button>
</div>
<div class="paramRow">
<b>Enable Predictions</b> <input type="checkbox" class="paramSelector" id="predictorEnabled">
<b>Enable Predictions</b> <input type="checkbox" class="paramSelector" id="predictorEnabled" onclick='updateSettings();'>
</div>
<div class="paramRow">
<b>Show 'Abort' Predictions</b> <input type="checkbox" class="paramSelector" id="abortPredictionEnabled" onclick='updateSettings();'>
</div>
<div class="paramRow">
<b>Burst Altitude</b><input type="text" class="paramEntry" id="burstAlt"><br/>
@ -653,6 +703,9 @@
<div class="paramRow">
<b>Descent Rate</b><input type="text" class="paramEntry" id="descentRate"><br/>
</div>
<div class="paramRow">
<b>Update Rate</b><input type="text" class="paramEntry" id="predUpdateRate"><br/>
</div>
</div>
</div>
</div>