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_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
chase_config['pred_enabled'] = config.getboolean('predictor', 'predictor_enabled')
chase_config['pred_burst'] = config.getfloat('predictor', 'default_burst')

Wyświetl plik

@ -8,6 +8,7 @@
#
import logging
import re
import time
import traceback
from datetime import datetime
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/
#
# 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.listeners import OziListener, UDPListener
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
@ -60,6 +61,9 @@ current_payload_tracks = {} # Store of payload Track objects which are used to c
# Chase car position
car_track = GenericTrack()
# Habitat Chase-Car uploader object
habitat_uploader = None
#
# Flask Routes
@ -101,7 +105,7 @@ def flask_emit_event(event_name="none", data={}):
@socketio.on('client_settings_update', namespace='/chasemapper')
def client_settings_update(data):
global chasemapper_config
global chasemapper_config, habitat_uploader
_predictor_change = "none"
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):
_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.
chasemapper_config = data
@ -125,6 +136,21 @@ def client_settings_update(data):
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.
flask_emit_event('server_settings_update', chasemapper_config)
@ -511,7 +537,7 @@ def udp_listener_car_callback(data):
''' Handle car position data '''
# 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.
global car_track
global car_track, habitat_uploader
_lat = data['latitude']
_lon = data['longitude']
_alt = data['altitude']
@ -537,6 +563,10 @@ def udp_listener_car_callback(data):
# 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})
# Update the Habitat Uploader, if one exists.
if habitat_uploader != None:
habitat_uploader.update_position(data)
# Add other listeners here...
@ -727,10 +757,15 @@ if __name__ == "__main__":
# Start listeners using the default profile selection.
start_listeners(chasemapper_config['profiles'][chasemapper_config['selected_profile']])
# Start up the predictor, if enabled.
if chasemapper_config['pred_enabled']:
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.
_data_age_monitor = Thread(target=check_data_age)
_data_age_monitor.start()
@ -742,6 +777,9 @@ if __name__ == "__main__":
predictor_thread_running = False
data_monitor_thread_running = False
if habitat_uploader != None:
habitat_uploader.close()
# Attempt to close the running listeners.
for _thread in data_listeners:
try:

Wyświetl plik

@ -108,7 +108,10 @@
$('#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));
$('#habitatUpdateRate').val(chase_config.habitat_update_rate.toFixed(0));
$("#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);
// Clear and populate the profile selection.
@ -143,6 +146,8 @@
updateSettings = function(){
chase_config.pred_enabled = document.getElementById("predictorEnabled").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.
var _burst_alt = parseFloat($('#burstAlt').val());
@ -153,12 +158,16 @@
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
}
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);
};
@ -176,6 +185,12 @@
$("#predUpdateRate").change(function(){
updateSettings();
});
$("#habitatUpdateRate").change(function(){
updateSettings();
});
$("#habitatCall").change(function(){
updateSettings();
});
$("#profileSelect").change(function(){
@ -869,6 +884,17 @@
<b>Update Rate</b><input type="text" class="paramEntry" id="predUpdateRate"><br/>
</div>
</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>
<div class="paramRow">
<button type="button" class="paramSelector" id="clearPayloadData">Clear Payload Data</button></br>