kopia lustrzana https://github.com/projecthorus/chasemapper
Add habitat chase-car position upload.
rodzic
3686e84581
commit
5a10945543
|
@ -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')
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from threading import Thread
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
Ładowanie…
Reference in New Issue