Add telemetry source profiles, which can be selected from within the web client.

bearings
Mark Jessop 2018-07-27 21:47:17 +09:30
rodzic fdefbfedeb
commit ccc98f6324
4 zmienionych plików z 198 dodań i 61 usunięć

Wyświetl plik

@ -48,15 +48,10 @@ def parse_config_file(filename):
chase_config['default_lon'] = config.get('map', 'default_lon')
chase_config['payload_max_age'] = config.getint('map', 'payload_max_age')
# Source Selection
chase_config['data_source'] = config.get('source', 'type')
chase_config['ozimux_port'] = config.getint('source', 'ozimux_port')
chase_config['horus_udp_port'] = config.getint('source', 'horus_udp_port')
# Car GPS Data
chase_config['car_gps_source'] = config.get('car_gps','source')
chase_config['car_gpsd_host'] = config.get('car_gps','gpsd_host')
chase_config['car_gpsd_port'] = config.getint('car_gps','gpsd_port')
# GPSD Settings
chase_config['car_gpsd_host'] = config.get('gpsd','gpsd_host')
chase_config['car_gpsd_port'] = config.getint('gpsd','gpsd_port')
# Predictor
chase_config['pred_enabled'] = config.getboolean('predictor', 'predictor_enabled')
@ -66,6 +61,44 @@ def parse_config_file(filename):
chase_config['pred_gfs_directory'] = config.get('predictor', 'gfs_directory')
chase_config['pred_model_download'] = config.get('predictor', 'model_download')
# Telemetry Source Profiles
_profile_count = config.getint('profile_selection', 'profile_count')
_default_profile = config.getint('profile_selection', 'default_profile')
chase_config['selected_profile'] = ""
chase_config['profiles'] = {}
for i in range(1,_profile_count+1):
_profile_section = "profile_%d" % i
try:
_profile_name = config.get(_profile_section, 'profile_name')
_profile_telem_source_type = config.get(_profile_section, 'telemetry_source_type')
_profile_telem_source_port = config.getint(_profile_section, 'telemetry_source_port')
_profile_car_source_type = config.get(_profile_section, 'car_source_type')
_profile_car_source_port = config.getint(_profile_section, 'car_source_port')
chase_config['profiles'][_profile_name] = {
'name': _profile_name,
'telemetry_source_type': _profile_telem_source_type,
'telemetry_source_port': _profile_telem_source_port,
'car_source_type': _profile_car_source_type,
'car_source_port': _profile_car_source_port
}
if _default_profile == i:
chase_config['selected_profile'] = _profile_name
except Exception as e:
logging.error("Error reading profile section %d - %s" % (i, str(e)))
if len(chase_config['profiles'].keys()) == 0:
logging.critical("Could not read any profile data!")
return None
if chase_config['selected_profile'] not in chase_config['profiles']:
logging.critical("Default profile selection does not exist.")
return None
return chase_config

Wyświetl plik

@ -4,28 +4,49 @@
# Copy this file to horusmapper.cfg and modify as required.
#
# Telemetry Data Source
[source]
# Data source type:
#
# Telemetry Source Profiles
# Multiple Telemetry source profiles can be defined, and can be selected from
# the web GUI.
#
[profile_selection]
# How many profiles have been defined
profile_count = 1
# Index of the default profile (indexing from 1)
default_profile = 1
[profile_1]
# Profile name - will be shown in the web client.
profile_name = Default
# Telemetry source type:
# ozimux - Read data in OziMux format
# horus_udp - Read Horus UDP Broadcast 'Payload Summary' messages
type = ozimux
telemetry_source_type = ozimux
# Telemetry source port (UDP)
telemetry_source_port = 8942
# Ozimux Data source UDP port
ozimux_port = 8942
# Car Position Source
# none - No Chase-Car GPS
# horus_udp - Read Horus UDP Broadcast 'Car GPS' messages
# gpsd - Poll GPSD for positions (TO BE IMPLEMENTED)
car_source_type = horus_udp
# Car position source port (UDP) - only used if horus_udp is selected
car_source_port = 55672
# 'Payload Summary' (also car GPS messages) UDP listen port
horus_udp_port = 55672
# Other profiles can be defined in sections like the following:
[profile_2]
# Example source to take telemetry data from an instance of radiosonde_auto_rx
# emitting Horus UDP packets on port 55673, but still accept car positions via the
# 'standard' Horus UDP port 55672
profile_name = auto_rx
telemetry_source_type = horus_udp
telemetry_source_port = 55673
car_source_type = horus_udp
car_source_port = 55672
# Chase-Car Position Settings
[car_gps]
# Chase car Position data Source
# none - No Chase-Car GPS
# horus_udp - Read Horus UDP Broadcast 'Car GPS' messages
# gpsd - Poll GPSD for positions. (TO BE IMPLEMENTED)
source = horus_udp
# GPSD Host/Port
[gpsd]
# GPSD Host/Port - Only used if selected in the telemetry profile
# TO BE IMPLEMENTED
gpsd_host = localhost
gpsd_port = 2947
@ -49,7 +70,7 @@ payload_max_age = 180
# https://github.com/darksidelemm/cusf_predictor_wrapper
# You also need to compile the predictor binary, and copy it into this directory.
[predictor]
# Enable Predictor (True/False)
# Enable Predictor (True/False) - This can also be enabled from the web client.
predictor_enabled = False
# Predictor defaults - these can be modified at runtime in the web interface.

Wyświetl plik

@ -211,14 +211,15 @@ def predictorThread():
def run_prediction():
''' Run a Flight Path prediction '''
global chasemapper_config, current_payloads, current_payload_tracks, predictor
global chasemapper_config, current_payloads, current_payload_tracks, predictor, predictor_semaphore
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:
_payload_list = list(current_payload_tracks.keys())
for _payload in _payload_list:
# Check the age of the data.
# No point re-running the predictor if the data is older than 30 seconds.
@ -322,8 +323,6 @@ def run_prediction():
current_payloads[_payload]['abort_path'] = []
current_payloads[_payload]['abort_landing'] = []
predictor_semaphore = False
# Send the web client the updated prediction data.
if _pred_ok or _abort_pred_ok:
_client_data = {
@ -336,6 +335,9 @@ def run_prediction():
}
flask_emit_event('predictor_update', _client_data)
# Clear the predictor-runnign semaphore
predictor_semaphore = False
def initPredictor():
global predictor, predictor_thread, chasemapper_config, pred_settings
@ -420,7 +422,7 @@ def download_new_model(data):
@socketio.on('payload_data_clear', namespace='/chasemapper')
def clear_payload_data(data):
""" Clear the payload data store """
global predictor_semaphore, current_payloads
global predictor_semaphore, current_payloads, current_payload_tracks
logging.warning("Client requested all payload data be cleared.")
# Wait until any current predictions have finished running.
while predictor_semaphore:
@ -542,6 +544,88 @@ def check_data_age():
time.sleep(2)
def start_listeners(profile):
""" Stop any currently running listeners, and startup a set of data listeners based on the supplied profile
Args:
profile (dict): A dictionary containing:
'name' (str): Profile name
'telemetry_source_type' (str): Data source type (ozimux or horus_udp)
'telemetry_source_port' (int): Data source port
'car_source_type' (str): Car Position source type (none, horus_udp or gpsd)
'car_source_port' (int): Car Position source port
"""
global data_listeners
# Stop any existing listeners.
for _thread in data_listeners:
try:
_thread.close()
except Exception as e:
logging.error("Error closing thread.")
# Reset the listeners array.
data_listeners = []
# Start up a OziMux listener, if we are using one.
if profile['telemetry_source_type'] == "ozimux":
logging.info("Using OziMux data source on UDP Port %d" % profile['telemetry_source_port'])
_ozi_listener = OziListener(telemetry_callback=ozi_listener_callback, port=profile['telemetry_source_port'])
data_listeners.append(_ozi_listener)
# Start up UDP Broadcast Listener (which we use for car positions even if not for the payload)
# Case 1 - Both telemetry and car position sources are set to horus_udp, and have the same port set. Only start a single UDP listener
if (profile['telemetry_source_type'] == "horus_udp") and (profile['car_source_type'] == "horus_udp") and (profile['car_source_port'] == profile['telemetry_source_port']):
# In this case, we start a single Horus UDP listener.
logging.info("Starting single Horus UDP listener on port %d" % profile['telemetry_source_port'])
_telem_horus_udp_listener = UDPListener(summary_callback=udp_listener_summary_callback,
gps_callback=udp_listener_car_callback,
port=profile['telemetry_source_port'])
_telem_horus_udp_listener.start()
data_listeners.append(_telem_horus_udp_listener)
else:
if profile['telemetry_source_type'] == "horus_udp":
# Telemetry via Horus UDP - Start up a listener
logging.info("Starting Telemetry Horus UDP listener on port %d" % profile['telemetry_source_port'])
_telem_horus_udp_listener = UDPListener(summary_callback=udp_listener_summary_callback,
gps_callback=None,
port=profile['telemetry_source_port'])
_telem_horus_udp_listener.start()
data_listeners.append(_telem_horus_udp_listener)
if profile['car_source_type'] == "horus_udp":
# Car Position via Horus UDP - Start up a listener
logging.info("Starting Car Position Horus UDP listener on port %d" % profile['car_source_port'])
_car_horus_udp_listener = UDPListener(summary_callback=None,
gps_callback=udp_listener_car_callback,
port=profile['car_source_port'])
_car_horus_udp_listener.start()
data_listeners.append(_car_horus_udp_listener)
elif profile['car_source_type'] == "gpsd":
# GPSD Car Position Source - TODO
logging.info("Starting GPSD Car Position Listener.")
else:
# No Car position.
logging.info("No car position data source.")
@socketio.on('profile_change', namespace='/chasemapper')
def profile_change(data):
""" Client has requested a profile change """
global chasemapper_config
logging.info("Client requested change to profile: %s" % data)
# Change the profile, and restart the listeners.
chasemapper_config['selected_profile'] = data
start_listeners(chasemapper_config['profiles'][chasemapper_config['selected_profile']])
# Update all clients with the new profile selection
flask_emit_event('server_settings_update', chasemapper_config)
if __name__ == "__main__":
import argparse
@ -568,7 +652,7 @@ if __name__ == "__main__":
chasemapper_config = read_config(args.config)
# Die if we cannot read a valid config file.
if chasemapper_config == None:
logging.critical("Could not read any configuration data. Exiting")
logging.critical("Could not read configuration data. Exiting")
sys.exit(1)
# Copy out the predictor settings to another dictionary.
@ -578,31 +662,8 @@ if __name__ == "__main__":
'pred_model_download': chasemapper_config['pred_model_download']
}
# Start up the primary data source
if chasemapper_config['data_source'] == "ozimux":
logging.info("Using OziMux data source.")
_ozi_listener = OziListener(telemetry_callback=ozi_listener_callback, port=chasemapper_config['ozimux_port'])
data_listeners.append(_ozi_listener)
# Start up UDP Broadcast Listener (which we use for car positions even if not for the payload)
if (chasemapper_config['data_source'] == "horus_udp") or (chasemapper_config['car_gps_source'] == "horus_udp"):
_horus_udp_port = chasemapper_config['horus_udp_port']
if chasemapper_config['data_source'] == "horus_udp":
_summary_callback = udp_listener_summary_callback
else:
_summary_callback = None
if chasemapper_config['car_gps_source'] == "horus_udp":
logging.info("Listening for Chase Car position via Horus UDP.")
_gps_callback = udp_listener_car_callback
else:
_gps_callback = None
logging.info("Starting Horus UDP Listener")
_horus_udp_listener = UDPListener(summary_callback=_summary_callback,
gps_callback=_gps_callback)
_horus_udp_listener.start()
data_listeners.append(_horus_udp_listener)
# Start listeners using the default profile selection.
start_listeners(chasemapper_config['profiles'][chasemapper_config['selected_profile']])
if chasemapper_config['pred_enabled']:
@ -624,4 +685,4 @@ if __name__ == "__main__":
try:
_thread.close()
except Exception as e:
logging.error("Error closing thread.")
logging.error("Error closing thread - %s" % str(e))

Wyświetl plik

@ -106,6 +106,17 @@
$('#predUpdateRate').val(chase_config.pred_update_rate.toFixed(0));
$("#predictorEnabled").prop('checked', chase_config.pred_enabled);
$("#abortPredictionEnabled").prop('checked', chase_config.show_abort);
// Clear and populate the profile selection.
$('#profileSelect').children('option:not(:first)').remove();
$.each(chase_config.profiles, function(key, value) {
$('#profileSelect')
.append($("<option></option>")
.attr("value",key)
.text(key));
});
$("#profileSelect").val(chase_config.selected_profile);
}
// Grab the System config on startup.
@ -143,6 +154,8 @@
if (isNaN(_update_rate) == false){
chase_config.pred_update_rate = _update_rate
}
socket.emit('client_settings_update', chase_config);
};
@ -159,6 +172,11 @@
updateSettings();
});
$("#profileSelect").change(function(){
// On a profile selection change, emit a message to the client.
socket.emit('profile_change', this.value);
});
// Event handler for Log data.
socket.on('log_event', function(msg) {
@ -596,7 +614,7 @@
if(data.burst.length == 3){
// There is burst data!
if (balloon_positions[_callsign].burst_marker == null){
var _burst_txt = _callsign + "Burst (" + data.burst[2].toFixed(0) + "m)";
var _burst_txt = _callsign + " Burst (" + data.burst[2].toFixed(0) + "m)";
balloon_positions[_callsign].burst_marker = L.marker(data.burst,{title:_burst_txt, icon: burstIcon})
.bindTooltip(_burst_txt,{permanent:false,direction:'right'})
.addTo(map);
@ -764,6 +782,11 @@
<div class="sidebar-pane" id="settings">
<h1 class="sidebar-header">Settings<span class="sidebar-close"><i class="fa fa-caret-left"></i></span></h1>
</hr>
<h3>Data Source</h3>
<select id="profileSelect" name="profileSelect">
</select>
<h3>Map</h3>
<h4>Auto-Follow</h4>
<form>
<input type="radio" name="autoFollow" value="payload" checked> Payload<br>
@ -771,13 +794,12 @@
<input type="radio" name="autoFollow" value="none"> None
</form>
</hr>
<h4>Map</h4>
<div class="paramRow">
<b>Show Car Track</b> <input type="checkbox" class="paramSelector" id="chaseCarTrack" checked>
</div>
</hr>
<h4>Predictor</h4>
<h3>Predictor</h3>
<div class="paramRow" id="predictorModel">
<b>Current Model: </b> Predictor Disabled
</div>
@ -800,7 +822,7 @@
<b>Update Rate</b><input type="text" class="paramEntry" id="predUpdateRate"><br/>
</div>
</hr>
<h4>Other</h4>
<h3>Other</h3>
<div class="paramRow">
<button type="button" class="paramSelector" id="clearPayloadData">Clear Payload Data</button></br>
</div>