Add habitat chase-car position upload.

bearings
Mark Jessop 2018-09-01 22:22:00 +09:30
rodzic 3686e84581
commit 5a10945543
6 zmienionych plików z 304 dodań i 4 usunięć

Wyświetl plik

@ -60,6 +60,11 @@ def parse_config_file(filename):
chase_config['car_serial_port'] = config.get('gps_serial', 'gps_port') chase_config['car_serial_port'] = config.get('gps_serial', 'gps_port')
chase_config['car_serial_baud'] = config.getint('gps_serial', 'gps_baud') chase_config['car_serial_baud'] = config.getint('gps_serial', 'gps_baud')
# Habitat Settings
chase_config['habitat_upload_enabled'] = config.getboolean('habitat', 'habitat_upload_enabled')
chase_config['habitat_call'] = config.get('habitat', 'habitat_call')
chase_config['habitat_update_rate'] = config.getint('habitat', 'habitat_update_rate')
# Predictor # Predictor
chase_config['pred_enabled'] = config.getboolean('predictor', 'predictor_enabled') chase_config['pred_enabled'] = config.getboolean('predictor', 'predictor_enabled')
chase_config['pred_burst'] = config.getfloat('predictor', 'default_burst') chase_config['pred_burst'] = config.getfloat('predictor', 'default_burst')

Wyświetl plik

@ -8,6 +8,7 @@
# #
import logging import logging
import re import re
import time
import traceback import traceback
from datetime import datetime from datetime import datetime
from threading import Thread from threading import Thread

Wyświetl plik

@ -0,0 +1,213 @@
#!/usr/bin/env python
#
# Project Horus - Browser-Based Chase Mapper
# Habitat Communication (Chase car position upload)
#
# Copyright (C) 2018 Mark Jessop <vk5qi@rfhead.net>
# Released under GNU GPL v3 or later
#
import datetime
import logging
import requests
import time
import traceback
import json
from base64 import b64encode
from hashlib import sha256
from threading import Thread, Lock
try:
# Python 2
from Queue import Queue
except ImportError:
# Python 3
from queue import Queue
HABITAT_URL = "http://habitat.habhub.org/"
url_habitat_uuids = HABITAT_URL + "_uuids?count=%d"
url_habitat_db = HABITAT_URL + "habitat/"
uuids = []
def ISOStringNow():
return "%sZ" % datetime.datetime.utcnow().isoformat()
def postListenerData(doc, timeout=10):
global uuids, url_habitat_db
# do we have at least one uuid, if not go get more
if len(uuids) < 1:
fetchUuids()
# Attempt to add UUID and time data to document.
try:
doc['_id'] = uuids.pop()
except IndexError:
logging.error("Habitat - Unable to post listener data - no UUIDs available.")
return False
doc['time_uploaded'] = ISOStringNow()
try:
_r = requests.post(url_habitat_db, json=doc, timeout=timeout)
return True
except Exception as e:
logging.error("Habitat - Could not post listener data - %s" % str(e))
return False
def fetchUuids(timeout=10):
global uuids, url_habitat_uuids
_retries = 5
while _retries > 0:
try:
_r = requests.get(url_habitat_uuids % 10, timeout=timeout)
uuids.extend(_r.json()['uuids'])
logging.debug("Habitat - Got UUIDs")
return
except Exception as e:
logging.error("Habitat - Unable to fetch UUIDs, retrying in 10 seconds - %s" % str(e))
time.sleep(10)
_retries = _retries - 1
continue
logging.error("Habitat - Gave up trying to get UUIDs.")
return
def initListenerCallsign(callsign):
doc = {
'type': 'listener_information',
'time_created' : ISOStringNow(),
'data': {
'callsign': callsign
}
}
resp = postListenerData(doc)
if resp is True:
logging.debug("Habitat - Listener Callsign Initialized.")
return True
else:
logging.error("Habitat - Unable to initialize callsign.")
return False
def uploadListenerPosition(callsign, lat, lon):
""" Upload Listener Position """
doc = {
'type': 'listener_telemetry',
'time_created': ISOStringNow(),
'data': {
'callsign': callsign,
'chase': True,
'latitude': lat,
'longitude': lon,
'altitude': 0,
'speed': 0,
}
}
# post position to habitat
resp = postListenerData(doc)
if resp is True:
logging.debug("Habitat - Listener information uploaded.")
return True
else:
logging.error("Habitat - Unable to upload listener information.")
return False
class HabitatChaseUploader(object):
''' Upload supplied chase car positions to Habitat on a regular basis '''
def __init__(self,
update_rate = 30,
callsign = "N0CALL",
upload_enabled = True):
''' Initialise the Habitat Chase uploader, and start the update thread '''
self.update_rate = update_rate
self.callsign = callsign
self.callsign_init = False
self.upload_enabled = upload_enabled
self.car_position = None
self.car_position_lock = Lock()
self.uploader_thread_running = True
self.uploader_thread = Thread(target=self.upload_thread)
self.uploader_thread.start()
logging.info("Habitat - Chase-Car Position Uploader Started")
def update_position(self, position):
''' Update the chase car position state
This function accepts and stores a copy of the same dictionary structure produced by both
Horus UDP broadcasts, and the serial GPS and GPSD modules
'''
with self.car_position_lock:
self.car_position = position.copy()
def upload_thread(self):
''' Uploader thread '''
while self.uploader_thread_running:
# Grab a copy of the most recent car position.
with self.car_position_lock:
if self.car_position != None:
_position = self.car_position.copy()
else:
_position = None
if self.upload_enabled and _position != None:
try:
# If the listener callsign has not been initialized, init it.
# We only need to do this once per callsign.
if self.callsign_init != self.callsign:
_resp = initListenerCallsign(self.callsign)
if _resp:
self.callsign_init = self.callsign
# Upload the listener position.
uploadListenerPosition(self.callsign, _position['latitude'], _position['longitude'])
except Exception as e:
logging.error("Habitat - Error uploading chase-car position - %s" % str(e))
# Wait for next update.
_i = 0
while (_i < self.update_rate) and self.uploader_thread_running:
time.sleep(1)
_i += 1
def set_update_rate(self, rate):
''' Set the update rate '''
self.update_rate = int(rate)
def set_callsign(self, call):
''' Set the callsign '''
self.callsign = call
def close(self):
self.uploader_thread_running = False
try:
self.uploader_thread.join()
except:
pass
logging.info("Habitat - Chase-Car Position Uploader Closed")

Wyświetl plik

@ -124,4 +124,21 @@ tile_server_enabled = False
tile_server_path = /home/pi/Maps/ tile_server_path = /home/pi/Maps/
#
# Habitat Chase-Car Position Upload
# If you want, this application can upload your chase-car position to the Habhub tracker,
# for those follwing along at home.
# The settings below can be modified from the web interface, but they will default to what is set below on startup.
#
[habitat]
# Enable uploading of chase-car position to Habitat (True / False)
habitat_upload_enabled = False
# Callsign to use when uploading. Note that _chase is automatically appended to this callsign
# i.e. N0CALL will show up as N0CALL_chase on tracker.habhub.org
habitat_call = 'N0CALL'
# Attempt to upload position to habitat every x seconds.
habitat_update_rate = 30

Wyświetl plik

@ -24,6 +24,7 @@ from chasemapper.gps import SerialGPS, GPSDGPS
from chasemapper.atmosphere import time_to_landing from chasemapper.atmosphere import time_to_landing
from chasemapper.listeners import OziListener, UDPListener from chasemapper.listeners import OziListener, UDPListener
from chasemapper.predictor import predictor_spawn_download, model_download_running from chasemapper.predictor import predictor_spawn_download, model_download_running
from chasemapper.habitat import HabitatChaseUploader
# Define Flask Application, and allow automatic reloading of templates for dev work # Define Flask Application, and allow automatic reloading of templates for dev work
@ -60,6 +61,9 @@ current_payload_tracks = {} # Store of payload Track objects which are used to c
# Chase car position # Chase car position
car_track = GenericTrack() car_track = GenericTrack()
# Habitat Chase-Car uploader object
habitat_uploader = None
# #
# Flask Routes # Flask Routes
@ -101,7 +105,7 @@ def flask_emit_event(event_name="none", data={}):
@socketio.on('client_settings_update', namespace='/chasemapper') @socketio.on('client_settings_update', namespace='/chasemapper')
def client_settings_update(data): def client_settings_update(data):
global chasemapper_config global chasemapper_config, habitat_uploader
_predictor_change = "none" _predictor_change = "none"
if (chasemapper_config['pred_enabled'] == False) and (data['pred_enabled'] == True): if (chasemapper_config['pred_enabled'] == False) and (data['pred_enabled'] == True):
@ -109,6 +113,13 @@ def client_settings_update(data):
elif (chasemapper_config['pred_enabled'] == True) and (data['pred_enabled'] == False): elif (chasemapper_config['pred_enabled'] == True) and (data['pred_enabled'] == False):
_predictor_change = "stop" _predictor_change = "stop"
_habitat_change = "none"
if (chasemapper_config['habitat_upload_enabled'] == False) and (data['habitat_upload_enabled'] == True):
_habitat_change = "start"
elif (chasemapper_config['habitat_upload_enabled'] == True) and (data['habitat_upload_enabled'] == False):
_habitat_change = "stop"
# Overwrite local config data with data from the client. # Overwrite local config data with data from the client.
chasemapper_config = data chasemapper_config = data
@ -125,6 +136,21 @@ def client_settings_update(data):
predictor = None predictor = None
# Start or Stop the Habitat Chase-Car Uploader.
if _habitat_change == "start":
if habitat_uploader == None:
habitat_uploader = HabitatChaseUploader(update_rate = chasemapper_config['habitat_update_rate'],
callsign=chasemapper_config['habitat_call'])
elif _habitat_change == "stop":
habitat_uploader.close()
habitat_uploader = None
# Update the habitat uploader with a new update rate, if one has changed.
if habitat_uploader != None:
habitat_uploader.set_update_rate(chasemapper_config['habitat_update_rate'])
habitat_uploader.set_callsign(chasemapper_config['habitat_call'])
# Push settings back out to all clients. # Push settings back out to all clients.
flask_emit_event('server_settings_update', chasemapper_config) flask_emit_event('server_settings_update', chasemapper_config)
@ -511,7 +537,7 @@ def udp_listener_car_callback(data):
''' Handle car position data ''' ''' Handle car position data '''
# TODO: Make a generic car position function, and have this function pass data into it # TODO: Make a generic car position function, and have this function pass data into it
# so we can add support for other chase car position inputs. # so we can add support for other chase car position inputs.
global car_track global car_track, habitat_uploader
_lat = data['latitude'] _lat = data['latitude']
_lon = data['longitude'] _lon = data['longitude']
_alt = data['altitude'] _alt = data['altitude']
@ -537,6 +563,10 @@ def udp_listener_car_callback(data):
# Push the new car position to the web client # Push the new car position to the web client
flask_emit_event('telemetry_event', {'callsign': 'CAR', 'position':[_lat,_lon,_alt], 'vel_v':0.0, 'heading': _heading, 'speed':_speed}) flask_emit_event('telemetry_event', {'callsign': 'CAR', 'position':[_lat,_lon,_alt], 'vel_v':0.0, 'heading': _heading, 'speed':_speed})
# Update the Habitat Uploader, if one exists.
if habitat_uploader != None:
habitat_uploader.update_position(data)
# Add other listeners here... # Add other listeners here...
@ -727,10 +757,15 @@ if __name__ == "__main__":
# Start listeners using the default profile selection. # Start listeners using the default profile selection.
start_listeners(chasemapper_config['profiles'][chasemapper_config['selected_profile']]) start_listeners(chasemapper_config['profiles'][chasemapper_config['selected_profile']])
# Start up the predictor, if enabled.
if chasemapper_config['pred_enabled']: if chasemapper_config['pred_enabled']:
initPredictor() initPredictor()
# Start up the Habitat Chase-Car Uploader, if enabled
if chasemapper_config['habitat_upload_enabled']:
habitat_uploader = HabitatChaseUploader(update_rate = chasemapper_config['habitat_update_rate'],
callsign=chasemapper_config['habitat_call'])
# Start up the data age monitor thread. # Start up the data age monitor thread.
_data_age_monitor = Thread(target=check_data_age) _data_age_monitor = Thread(target=check_data_age)
_data_age_monitor.start() _data_age_monitor.start()
@ -742,6 +777,9 @@ if __name__ == "__main__":
predictor_thread_running = False predictor_thread_running = False
data_monitor_thread_running = False data_monitor_thread_running = False
if habitat_uploader != None:
habitat_uploader.close()
# Attempt to close the running listeners. # Attempt to close the running listeners.
for _thread in data_listeners: for _thread in data_listeners:
try: try:

Wyświetl plik

@ -108,7 +108,10 @@
$('#burstAlt').val(chase_config.pred_burst.toFixed(0)); $('#burstAlt').val(chase_config.pred_burst.toFixed(0));
$('#descentRate').val(chase_config.pred_desc_rate.toFixed(1)); $('#descentRate').val(chase_config.pred_desc_rate.toFixed(1));
$('#predUpdateRate').val(chase_config.pred_update_rate.toFixed(0)); $('#predUpdateRate').val(chase_config.pred_update_rate.toFixed(0));
$('#habitatUpdateRate').val(chase_config.habitat_update_rate.toFixed(0));
$("#predictorEnabled").prop('checked', chase_config.pred_enabled); $("#predictorEnabled").prop('checked', chase_config.pred_enabled);
$("#habitatUploadEnabled").prop('checked', chase_config.habitat_upload_enabled);
$("#habitatCall").val(chase_config.habitat_call);
$("#abortPredictionEnabled").prop('checked', chase_config.show_abort); $("#abortPredictionEnabled").prop('checked', chase_config.show_abort);
// Clear and populate the profile selection. // Clear and populate the profile selection.
@ -143,6 +146,8 @@
updateSettings = function(){ updateSettings = function(){
chase_config.pred_enabled = document.getElementById("predictorEnabled").checked; chase_config.pred_enabled = document.getElementById("predictorEnabled").checked;
chase_config.show_abort = document.getElementById("abortPredictionEnabled").checked; chase_config.show_abort = document.getElementById("abortPredictionEnabled").checked;
chase_config.habitat_upload_enabled = document.getElementById("habitatUploadEnabled").checked;
chase_config.habitat_call = $('#habitatCall').val()
// Attempt to parse the text field values. // Attempt to parse the text field values.
var _burst_alt = parseFloat($('#burstAlt').val()); var _burst_alt = parseFloat($('#burstAlt').val());
@ -153,12 +158,16 @@
if (isNaN(_desc_rate) == false){ if (isNaN(_desc_rate) == false){
chase_config.pred_desc_rate = _desc_rate chase_config.pred_desc_rate = _desc_rate
} }
var _update_rate = parseInt($('#predUpdateRate').val()); var _update_rate = parseInt($('#predUpdateRate').val());
if (isNaN(_update_rate) == false){ if (isNaN(_update_rate) == false){
chase_config.pred_update_rate = _update_rate chase_config.pred_update_rate = _update_rate
} }
var _habitat_update_rate = parseInt($('#habitatUpdateRate').val());
if (isNaN(_habitat_update_rate) == false){
chase_config.habitat_update_rate = _habitat_update_rate
}
socket.emit('client_settings_update', chase_config); socket.emit('client_settings_update', chase_config);
}; };
@ -176,6 +185,12 @@
$("#predUpdateRate").change(function(){ $("#predUpdateRate").change(function(){
updateSettings(); updateSettings();
}); });
$("#habitatUpdateRate").change(function(){
updateSettings();
});
$("#habitatCall").change(function(){
updateSettings();
});
$("#profileSelect").change(function(){ $("#profileSelect").change(function(){
@ -869,6 +884,17 @@
<b>Update Rate</b><input type="text" class="paramEntry" id="predUpdateRate"><br/> <b>Update Rate</b><input type="text" class="paramEntry" id="predUpdateRate"><br/>
</div> </div>
</hr> </hr>
<h3>Habitat</h3>
<div class="paramRow">
<b>Enable Chase-Car Position Upload</b> <input type="checkbox" class="paramSelector" id="habitatUploadEnabled" onclick='updateSettings();'>
</div>
<div class="paramRow">
<b>Habitat Call:</b><input type="text" class="paramEntry" id="habitatCall"><br/>
</div>
<div class="paramRow">
<b>Update Rate (seconds):</b><input type="text" class="paramEntry" id="habitatUpdateRate"><br/>
</div>
</hr>
<h3>Other</h3> <h3>Other</h3>
<div class="paramRow"> <div class="paramRow">
<button type="button" class="paramSelector" id="clearPayloadData">Clear Payload Data</button></br> <button type="button" class="paramSelector" id="clearPayloadData">Clear Payload Data</button></br>