A big fuck-off commit with a bunch of bug fixes, bug additions, and a lot of work put into navigationd to the point that it actually does shit
This commit is contained in:
parent
1d403fc838
commit
d973245735
|
@ -0,0 +1,107 @@
|
||||||
|
import gpsd
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
import geopandas as gpd
|
||||||
|
import osmnx as ox
|
||||||
|
import networkx as nx
|
||||||
|
import os
|
||||||
|
|
||||||
|
urlBase = 'http://localhost:5000'
|
||||||
|
urlLocation = urlBase + '/api/navigation/position'
|
||||||
|
urlImage = urlBase + '/api/navigation/image'
|
||||||
|
urlAddCyberware = urlBase + '/api/cyberware/add'
|
||||||
|
urlRemoveCyberware = urlBase + '/api/cyberware/remove'
|
||||||
|
|
||||||
|
uuid = ""
|
||||||
|
|
||||||
|
def connectCyberware():
|
||||||
|
global uuid
|
||||||
|
# Add to Cyberware and get UUID
|
||||||
|
try:
|
||||||
|
uuidRequest = requests.post(urlAddCyberware, json={ 'name': 'GPS', 'hotpluggable': True, 'canSet': [ '/api/navigation/position', '/api/navigation/image' ] })
|
||||||
|
|
||||||
|
uuid = uuidRequest.json()[0]['uuid']
|
||||||
|
except:
|
||||||
|
print("Cannot connect to NightCall. Stopping.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def disconnectCyberware():
|
||||||
|
try:
|
||||||
|
disconnectRequest = requests.post(urlRemoveCyberware, json={ 'uuid': uuid })
|
||||||
|
except:
|
||||||
|
print("Cannot disconnect Cyberware.")
|
||||||
|
|
||||||
|
gpsdData = None
|
||||||
|
|
||||||
|
def getMapImage(distance):
|
||||||
|
ox.settings.use_cache = True
|
||||||
|
|
||||||
|
|
||||||
|
if(gpsdData.lat == 0 and gpsdData.lon == 0):
|
||||||
|
point = (42.352593, -83.2640164)
|
||||||
|
|
||||||
|
try:
|
||||||
|
G = ox.graph_from_point(point, dist=distance, dist_type="bbox", network_type="drive")
|
||||||
|
|
||||||
|
# impute edge (driving) speeds and calculate edge travel times
|
||||||
|
G = ox.routing.add_edge_speeds(G)
|
||||||
|
G = ox.routing.add_edge_travel_times(G)
|
||||||
|
|
||||||
|
# I'm not sure how to handle this short of saving it and then reading it. :(
|
||||||
|
filepath = uuid+".png"
|
||||||
|
ox.plot_graph(G, show=True, node_size=0, edge_linewidth=2, bgcolor="#0B1629", edge_color="#5FE0E9", save=True, filepath=filepath)
|
||||||
|
except ox._errors.InsufficientResponseError:
|
||||||
|
print("Insufficient response. Skipping.")
|
||||||
|
|
||||||
|
#def getMapImage(area):
|
||||||
|
# #lat, lon = gpsdData.position()
|
||||||
|
# lat, lon = 41.7573113, -93.8128377
|
||||||
|
# bbox = lat-(area/2.0), lat+(area/2.0), lon+(area/2.0), lon-(area/2.0)
|
||||||
|
#
|
||||||
|
# G = ox.graph_from_bbox(bbox=bbox, network_type="drive_service")
|
||||||
|
|
||||||
|
def sendLocation():
|
||||||
|
try:
|
||||||
|
#request = requests.post(urlLocation, json={ 'x': gpsdData.lat, 'y': gpsdData.lon, 'z': gpsdData.alt, 'o': gpsdData.track })
|
||||||
|
request = requests.post(urlLocation, json={ 'x': None, 'y': None, 'z': None, 'o': None, 'uuid': uuid })
|
||||||
|
except:
|
||||||
|
print('Could not contact NightCall')
|
||||||
|
|
||||||
|
def sendMapImage():
|
||||||
|
try:
|
||||||
|
files = { 'image': open(uuid+".png", 'rb') }
|
||||||
|
request = requests.post(urlImage, files=files)
|
||||||
|
except:
|
||||||
|
print("Could not contact NightCall")
|
||||||
|
|
||||||
|
print("Connecting to gpsd")
|
||||||
|
gpsd.connect() # Connect to gpsd
|
||||||
|
|
||||||
|
continueFlag = True
|
||||||
|
|
||||||
|
print("Adding Cyberware")
|
||||||
|
connectCyberware()
|
||||||
|
|
||||||
|
while continueFlag:
|
||||||
|
try:
|
||||||
|
time.sleep(1)
|
||||||
|
try:
|
||||||
|
gpsdData = gpsd.get_current()
|
||||||
|
except gpsd.NoFixError:
|
||||||
|
gpsdData = None
|
||||||
|
print("No GPS fix yet! Using default coordinates.")
|
||||||
|
|
||||||
|
getMapImage(1000)
|
||||||
|
sendLocation()
|
||||||
|
sendMapImage()
|
||||||
|
|
||||||
|
#print(packet.position())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Exiting.")
|
||||||
|
continueFlag = False;
|
||||||
|
|
||||||
|
print("Removing Cyberware")
|
||||||
|
disconnectCyberware()
|
||||||
|
|
||||||
|
print("Cleaning up")
|
||||||
|
os.remove(uuid+".png")
|
|
@ -1 +1,7 @@
|
||||||
pygarmin
|
gpsd-py3
|
||||||
|
osmnx
|
||||||
|
gdal==3.6.2
|
||||||
|
geopandas
|
||||||
|
matplotlib
|
||||||
|
networkx
|
||||||
|
numpy
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from flask import Flask, render_template, jsonify, request
|
from flask import Flask, render_template, jsonify, request, send_file
|
||||||
from flask_apscheduler import APScheduler
|
from flask_apscheduler import APScheduler
|
||||||
from copy import copy
|
from copy import copy
|
||||||
import uuid
|
import uuid
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import requests
|
import requests
|
||||||
|
import io
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@ -432,6 +433,103 @@ def setEnvironmentHumidity():
|
||||||
return 'Incorrect usage.\nUsage: { humidity: INT, uuid: STRING }\n', 400
|
return 'Incorrect usage.\nUsage: { humidity: INT, uuid: STRING }\n', 400
|
||||||
return '', 204
|
return '', 204
|
||||||
|
|
||||||
|
navigationX = None
|
||||||
|
navigationY = None
|
||||||
|
navigationZ = None
|
||||||
|
navigationO = None
|
||||||
|
navigationImage = None
|
||||||
|
|
||||||
|
# Navigation
|
||||||
|
@app.route('/api/navigation')
|
||||||
|
def getNavigation():
|
||||||
|
returnArr = [ { 'x': navigationX, 'y': navigationY, 'z': navigationZ, 'o': navigationO, 'img': navigationImage } ]
|
||||||
|
return jsonify(returnArr), 200
|
||||||
|
|
||||||
|
# Navigation//Position
|
||||||
|
# Position consists of: { x: FLOAT, y: FLOAT, z: FLOAT, o: FLOAT }
|
||||||
|
# o stands for ORIENTATION. o may be NoneType to indicate that such data is not available.
|
||||||
|
@app.route('/api/navigation/position')
|
||||||
|
def getNavigationPosition():
|
||||||
|
returnArr = [ { 'x': navigationX, 'y': navigationY, 'z': navigationZ, 'o': navigationO } ]
|
||||||
|
return jsonify(returnArr), 200
|
||||||
|
|
||||||
|
@app.route('/api/navigation/position', methods=['POST'])
|
||||||
|
def setNavigationPosition():
|
||||||
|
global navigationX
|
||||||
|
global navigationY
|
||||||
|
global navigationZ
|
||||||
|
global navigationO
|
||||||
|
|
||||||
|
json = request.get_json()
|
||||||
|
|
||||||
|
try:
|
||||||
|
uuid = json['uuid']
|
||||||
|
if not authenticate(uuid, '/api/navigation/position'):
|
||||||
|
return 'Forbidden.', 403
|
||||||
|
|
||||||
|
tempX = json['x']
|
||||||
|
tempY = json['y']
|
||||||
|
tempZ = json['z']
|
||||||
|
tempO = json['o']
|
||||||
|
|
||||||
|
navigationX = tempX
|
||||||
|
navigationY = tempY
|
||||||
|
navigationZ = tempZ
|
||||||
|
navigationO = tempO
|
||||||
|
except:
|
||||||
|
return 'Incorrect usage.\nUsage: { x: FLOAT, y: FLOAT, z: FLOAT, o: FLOAT }\n', 400
|
||||||
|
return '', 204
|
||||||
|
|
||||||
|
# Navigation//Position//x
|
||||||
|
#@app.route('/api/navigation/position/x')
|
||||||
|
#def getNavigationPositionX():
|
||||||
|
# return jsonify( [ 'x': navigationX ]), 200
|
||||||
|
|
||||||
|
|
||||||
|
#@app.route('/api/navigation/position/x', methods=['POST'])
|
||||||
|
#def setNavigationPositionX():
|
||||||
|
# global navigationX
|
||||||
|
#
|
||||||
|
# json = request.get_json()
|
||||||
|
#
|
||||||
|
# try:
|
||||||
|
# if not authenticate(url,
|
||||||
|
# except:
|
||||||
|
#
|
||||||
|
|
||||||
|
# Navigation//Image
|
||||||
|
@app.route('/api/navigation/image')
|
||||||
|
def getNavigationImage():
|
||||||
|
#returnArr = [ { 'img': navigationImage } ]
|
||||||
|
#return jsonify(returnArr), 200
|
||||||
|
if(navigationImage != None):
|
||||||
|
imageStream = io.BytesIO(navigationImage)
|
||||||
|
return send_file(imageStream, mimetype="image/png", download_name="minimap.png", max_age=0) # We don't want this to cache ever
|
||||||
|
else:
|
||||||
|
return '', 200
|
||||||
|
|
||||||
|
@app.route('/api/navigation/image', methods=['POST'])
|
||||||
|
def setNavigationImage():
|
||||||
|
global navigationImage
|
||||||
|
|
||||||
|
try:
|
||||||
|
filename = request.files['image'].filename
|
||||||
|
uuid = filename.split('.')[0]
|
||||||
|
if not authenticate(uuid, '/api/navigation/image'):
|
||||||
|
return "Forbidden.", 403
|
||||||
|
|
||||||
|
file = request.files['image'].read()
|
||||||
|
|
||||||
|
navigationImage = file
|
||||||
|
except:
|
||||||
|
return 'Auth failed, or no file was sent.\n', 400
|
||||||
|
return '', 204
|
||||||
|
|
||||||
|
def resetNavigationImage():
|
||||||
|
global navigationImage
|
||||||
|
|
||||||
|
navigationImage = None
|
||||||
|
|
||||||
# Authentication method
|
# Authentication method
|
||||||
# This authorizes the given UUID to determine whether the request is
|
# This authorizes the given UUID to determine whether the request is
|
||||||
# allowed to set the requested endpoint.
|
# allowed to set the requested endpoint.
|
||||||
|
@ -441,12 +539,16 @@ def authenticate(uuid, endpoint):
|
||||||
if uuid == internalUUID:
|
if uuid == internalUUID:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
requestedHardware = None
|
||||||
for c in cyberware: # UUID Match
|
for c in cyberware: # UUID Match
|
||||||
if c['uuid'] == uuid:
|
if c['uuid'] == uuid:
|
||||||
requestedHardware = c
|
requestedHardware = c
|
||||||
c['lastContact'] = datetime.now() # Update last contact
|
c['lastContact'] = datetime.now() # Update last contact
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if requestedHardware == None:
|
||||||
|
return False
|
||||||
|
|
||||||
if requestedHardware['canSet'] == None:
|
if requestedHardware['canSet'] == None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -458,14 +560,16 @@ def authenticate(uuid, endpoint):
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def uiindex():
|
def uiindex():
|
||||||
return render_template('index.html')
|
address = request.base_url
|
||||||
|
return render_template('index.html', access_addr=address )
|
||||||
|
|
||||||
|
|
||||||
# Maintenance functions
|
# Maintenance functions
|
||||||
# The jank, my oh my
|
# The jank, my oh my
|
||||||
valuesToValidate = [ '/api/vitals/heartrate', '/api/vitals/oxygen', '/api/vitals/bodytemp',
|
valuesToValidate = [ '/api/vitals/heartrate', '/api/vitals/oxygen', '/api/vitals/bodytemp',
|
||||||
'/api/fitness/steps',
|
'/api/fitness/steps',
|
||||||
'/api/environment/temperature', '/api/environment/humidity']
|
'/api/environment/temperature', '/api/environment/humidity',
|
||||||
|
'/api/navigation/position', '/api/navigation/image']
|
||||||
baseURL = "http://localhost:5000"
|
baseURL = "http://localhost:5000"
|
||||||
# Value invalidation. A value is deemed invalid when there's no hardware attached
|
# Value invalidation. A value is deemed invalid when there's no hardware attached
|
||||||
# that can set it.
|
# that can set it.
|
||||||
|
@ -490,7 +594,14 @@ def valueInvalidation():
|
||||||
|
|
||||||
endpointToReset = baseURL + invalidValue
|
endpointToReset = baseURL + invalidValue
|
||||||
|
|
||||||
requests.post(endpointToReset, json={ key: None, 'uuid': internalUUID })
|
# Don't look at the special cases. STOP LOOKING AT IT
|
||||||
|
match invalidValue:
|
||||||
|
case '/api/navigation/position':
|
||||||
|
requests.post(endpointToReset, json={ 'x': None, 'y': None, 'z': None, 'o': None, 'uuid': internalUUID })
|
||||||
|
case '/api/navigation/image':
|
||||||
|
resetNavigationImage()
|
||||||
|
case _:
|
||||||
|
requests.post(endpointToReset, json={ key: None, 'uuid': internalUUID })
|
||||||
invalidStr = invalidStr + invalidValue + ", "
|
invalidStr = invalidStr + invalidValue + ", "
|
||||||
|
|
||||||
print("Values invalidated: " + invalidStr)
|
print("Values invalidated: " + invalidStr)
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
flask
|
flask
|
||||||
Flask-APScheduler
|
Flask-APScheduler
|
||||||
pygarmin
|
gpsd-py3
|
||||||
|
gdal=3.6.2
|
||||||
|
geopandas
|
||||||
|
osmnx
|
||||||
|
matplotlib
|
||||||
|
numpy
|
||||||
requests
|
requests
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
font-size 24px;
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +115,30 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid #5FE0E9;
|
border: 1px solid #5FE0E9;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
/*display: flex;
|
||||||
|
justify-content: center;*/
|
||||||
|
/*align-items: center;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#mapImage {
|
||||||
|
width: 150%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#mapDot {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: #F00;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Contains the time */
|
/* Contains the time */
|
||||||
|
@ -191,7 +216,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mapProper">
|
<div id="mapProper">
|
||||||
|
<img id="mapImage" alt=" "></img>
|
||||||
|
<div id="mapDot"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mapTime">
|
<div id="mapTime">
|
||||||
<p id="time">00:00:00</p>
|
<p id="time">00:00:00</p>
|
||||||
|
@ -216,12 +242,13 @@
|
||||||
</div>
|
</div>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.2/jquery.min.js" integrity="sha512-tWHlutFnuG0C6nQRlpvrEhE4QpkG1nn2MOUMWmUeRePl4e3Aki0VB6W1v3oLjFtd0hVOtRQ9PHpSfN6u6/QXkQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.2/jquery.min.js" integrity="sha512-tWHlutFnuG0C6nQRlpvrEhE4QpkG1nn2MOUMWmUeRePl4e3Aki0VB6W1v3oLjFtd0hVOtRQ9PHpSfN6u6/QXkQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// TODO: Make baseUrl be set by Flask
|
//var baseUrl = "http://192.168.6.48:5000";
|
||||||
var baseUrl = "http://localhost:5000";
|
var baseUrl = "{{ access_addr }}";
|
||||||
var vitalsUrl = baseUrl + "/api/vitals";
|
var vitalsUrl = baseUrl + "/api/vitals";
|
||||||
var environmentUrl = baseUrl + "/api/environment";
|
var environmentUrl = baseUrl + "/api/environment";
|
||||||
var malfunctionUrl = baseUrl + "/api/cyberware/malfunctions";
|
var malfunctionUrl = baseUrl + "/api/cyberware/malfunctions";
|
||||||
var messageUrl = baseUrl + "/api/cyberware/messages";
|
var messageUrl = baseUrl + "/api/cyberware/messages";
|
||||||
|
var navImageUrl = baseUrl + "/api/navigation/image";
|
||||||
|
|
||||||
// Malfunction Messages (human-friendly)
|
// Malfunction Messages (human-friendly)
|
||||||
malfunctionOrigin = [ "API Malfunction" ];
|
malfunctionOrigin = [ "API Malfunction" ];
|
||||||
|
@ -297,7 +324,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEnvironmentHumidity(humidity) {
|
function setEnvironmentHumidity(humidity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//function handleMapImage() {
|
||||||
|
// document.getElementById("mapProper").innerHTML = "<img src=" + navImageUrl + "></img>";
|
||||||
|
//}
|
||||||
|
|
||||||
|
//function displayMapStatic() {
|
||||||
|
// document.getElementById("mapProper").innerHTML = "";
|
||||||
|
//}
|
||||||
|
|
||||||
|
function updateNavigationImage() {
|
||||||
|
//handleMapImage()
|
||||||
|
var image = document.getElementById("mapImage");
|
||||||
|
image.src = navImageUrl + "?" + new Date().getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Malfunction handling
|
// Malfunction handling
|
||||||
|
@ -347,6 +387,8 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//stopInterval(t)
|
//stopInterval(t)
|
||||||
|
|
||||||
|
updateNavigationImage()
|
||||||
}
|
}
|
||||||
var t=setInterval(updateAll, 1000)
|
var t=setInterval(updateAll, 1000)
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue