kopia lustrzana https://github.com/projecthorus/chasemapper
Add telemetry source profiles, which can be selected from within the web client.
rodzic
fdefbfedeb
commit
ccc98f6324
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
125
horusmapper.py
125
horusmapper.py
|
@ -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))
|
||||
|
|
|
@ -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>
|
||||
|
|
Ładowanie…
Reference in New Issue