Fleet Manager
Co-Authored-By: mike8643 <98910897+mike8643@users.noreply.github.com>
This commit is contained in:
5
selfdrive/frogpilot/fleetmanager/README.md
Normal file
5
selfdrive/frogpilot/fleetmanager/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Fleet Manager
|
||||||
|
|
||||||
|
Fleet Manger on openpilot allows viewing dashcam footage, screen recordings, and error logs by connecting to the comma device via the same network, with your mobile device or PC. Big thanks to [actuallylemoncurd](https://github.com/actuallylemoncurd), [AlexandreSato](https://github.com/alexandreSato), [ntegan1](https://github.com/ntegan1), and [royjr](https://github.com/royjr).
|
||||||
|
|
||||||
|
The network can be set up by Wi-Fi, mobile hotspot, or tethering on the comma device. Navigate to http://tici:8082/ OR http://ipAddress:8082 to access.
|
||||||
243
selfdrive/frogpilot/fleetmanager/fleet_manager.py
Normal file
243
selfdrive/frogpilot/fleetmanager/fleet_manager.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import secrets
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from flask import Flask, render_template, Response, request, send_from_directory, session, redirect, url_for
|
||||||
|
import requests
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
from openpilot.common.realtime import set_core_affinity
|
||||||
|
import openpilot.selfdrive.frogpilot.fleetmanager.helpers as fleet
|
||||||
|
from openpilot.system.hardware.hw import Paths
|
||||||
|
from openpilot.common.swaglog import cloudlog
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def home_page():
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def internal_error(exception):
|
||||||
|
print('500 error caught')
|
||||||
|
tberror = traceback.format_exc()
|
||||||
|
return render_template("error.html", error=tberror)
|
||||||
|
|
||||||
|
@app.route("/footage/full/<cameratype>/<route>")
|
||||||
|
def full(cameratype, route):
|
||||||
|
chunk_size = 1024 * 512 # 5KiB
|
||||||
|
file_name = cameratype + (".ts" if cameratype == "qcamera" else ".hevc")
|
||||||
|
vidlist = "|".join(Paths.log_root() + "/" + segment + "/" + file_name for segment in fleet.segments_in_route(route))
|
||||||
|
|
||||||
|
def generate_buffered_stream():
|
||||||
|
with fleet.ffmpeg_mp4_concat_wrap_process_builder(vidlist, cameratype, chunk_size) as process:
|
||||||
|
for chunk in iter(lambda: process.stdout.read(chunk_size), b""):
|
||||||
|
yield bytes(chunk)
|
||||||
|
return Response(generate_buffered_stream(), status=200, mimetype='video/mp4')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/footage/<cameratype>/<segment>")
|
||||||
|
def fcamera(cameratype, segment):
|
||||||
|
if not fleet.is_valid_segment(segment):
|
||||||
|
return render_template("error.html", error="invalid segment")
|
||||||
|
file_name = Paths.log_root() + "/" + segment + "/" + cameratype + (".ts" if cameratype == "qcamera" else ".hevc")
|
||||||
|
return Response(fleet.ffmpeg_mp4_wrap_process_builder(file_name).stdout.read(), status=200, mimetype='video/mp4')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/footage/<route>")
|
||||||
|
def route(route):
|
||||||
|
if len(route) != 20:
|
||||||
|
return render_template("error.html", error="route not found")
|
||||||
|
|
||||||
|
if str(request.query_string) == "b''":
|
||||||
|
query_segment = str("0")
|
||||||
|
query_type = "qcamera"
|
||||||
|
else:
|
||||||
|
query_segment = (str(request.query_string).split(","))[0][2:]
|
||||||
|
query_type = (str(request.query_string).split(","))[1][:-1]
|
||||||
|
|
||||||
|
links = ""
|
||||||
|
segments = ""
|
||||||
|
for segment in fleet.segments_in_route(route):
|
||||||
|
links += "<a href='"+route+"?"+segment.split("--")[2]+","+query_type+"'>"+segment+"</a><br>"
|
||||||
|
segments += "'"+segment+"',"
|
||||||
|
return render_template("route.html", route=route, query_type=query_type, links=links, segments=segments, query_segment=query_segment)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/footage/")
|
||||||
|
@app.route("/footage")
|
||||||
|
def footage():
|
||||||
|
return render_template("footage.html", rows=fleet.all_routes())
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/screenrecords/")
|
||||||
|
@app.route("/screenrecords")
|
||||||
|
def screenrecords():
|
||||||
|
rows = fleet.list_files(fleet.SCREENRECORD_PATH)
|
||||||
|
if not rows:
|
||||||
|
return render_template("error.html", error="no screenrecords found at:<br><br>" + fleet.SCREENRECORD_PATH)
|
||||||
|
return render_template("screenrecords.html", rows=rows, clip=rows[0])
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/screenrecords/<clip>")
|
||||||
|
def screenrecord(clip):
|
||||||
|
return render_template("screenrecords.html", rows=fleet.list_files(fleet.SCREENRECORD_PATH), clip=clip)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/screenrecords/play/pipe/<file>")
|
||||||
|
def videoscreenrecord(file):
|
||||||
|
file_name = fleet.SCREENRECORD_PATH + file
|
||||||
|
return Response(fleet.ffplay_mp4_wrap_process_builder(file_name).stdout.read(), status=200, mimetype='video/mp4')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/screenrecords/download/<clip>")
|
||||||
|
def download_file(clip):
|
||||||
|
return send_from_directory(fleet.SCREENRECORD_PATH, clip, as_attachment=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/about")
|
||||||
|
def about():
|
||||||
|
return render_template("about.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/error_logs")
|
||||||
|
def error_logs():
|
||||||
|
return render_template("error_logs.html", rows=fleet.list_files(fleet.ERROR_LOGS_PATH))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/error_logs/<file_name>")
|
||||||
|
def open_error_log(file_name):
|
||||||
|
f = open(fleet.ERROR_LOGS_PATH + file_name)
|
||||||
|
error = f.read()
|
||||||
|
return render_template("error_log.html", file_name=file_name, file_content=error)
|
||||||
|
|
||||||
|
@app.route("/addr_input", methods=['GET', 'POST'])
|
||||||
|
def addr_input():
|
||||||
|
SearchInput = fleet.get_SearchInput()
|
||||||
|
token = fleet.get_public_token()
|
||||||
|
s_token = fleet.get_app_token()
|
||||||
|
gmap_key = fleet.get_gmap_key()
|
||||||
|
PrimeType = fleet.get_PrimeType()
|
||||||
|
lon = float(0.0)
|
||||||
|
lat = float(0.0)
|
||||||
|
if request.method == 'POST':
|
||||||
|
valid_addr = False
|
||||||
|
postvars = request.form.to_dict()
|
||||||
|
addr, lon, lat, valid_addr, token = fleet.parse_addr(postvars, lon, lat, valid_addr, token)
|
||||||
|
if not valid_addr:
|
||||||
|
# If address is not found, try searching
|
||||||
|
postvars = request.form.to_dict()
|
||||||
|
addr = request.form.get('addr_val')
|
||||||
|
addr, lon, lat, valid_addr, token = fleet.search_addr(postvars, lon, lat, valid_addr, token)
|
||||||
|
if valid_addr:
|
||||||
|
# If a valid address is found, redirect to nav_confirmation
|
||||||
|
return redirect(url_for('nav_confirmation', addr=addr, lon=lon, lat=lat))
|
||||||
|
else:
|
||||||
|
return render_template("error.html")
|
||||||
|
elif PrimeType != 0:
|
||||||
|
return render_template("prime.html")
|
||||||
|
elif fleet.get_nav_active():
|
||||||
|
if SearchInput == 2:
|
||||||
|
return render_template("nonprime.html", gmap_key=gmap_key, lon=lon, lat=lat)
|
||||||
|
else:
|
||||||
|
return render_template("nonprime.html", gmap_key=None, lon=None, lat=None)
|
||||||
|
elif token == "" or token is None:
|
||||||
|
return redirect(url_for('public_token_input'))
|
||||||
|
elif s_token == "" or s_token is None:
|
||||||
|
return redirect(url_for('app_token_input'))
|
||||||
|
elif SearchInput == 2:
|
||||||
|
lon, lat = fleet.get_last_lon_lat()
|
||||||
|
if gmap_key == "" or gmap_key is None:
|
||||||
|
return redirect(url_for('gmap_key_input'))
|
||||||
|
else:
|
||||||
|
return render_template("addr.html", gmap_key=gmap_key, lon=lon, lat=lat)
|
||||||
|
else:
|
||||||
|
return render_template("addr.html", gmap_key=None, lon=None, lat=None)
|
||||||
|
|
||||||
|
@app.route("/nav_confirmation", methods=['GET', 'POST'])
|
||||||
|
def nav_confirmation():
|
||||||
|
token = fleet.get_public_token()
|
||||||
|
lon = request.args.get('lon')
|
||||||
|
lat = request.args.get('lat')
|
||||||
|
addr = request.args.get('addr')
|
||||||
|
if request.method == 'POST':
|
||||||
|
postvars = request.form.to_dict()
|
||||||
|
fleet.nav_confirmed(postvars)
|
||||||
|
return redirect(url_for('addr_input'))
|
||||||
|
else:
|
||||||
|
return render_template("nav_confirmation.html", addr=addr, lon=lon, lat=lat, token=token)
|
||||||
|
|
||||||
|
@app.route("/public_token_input", methods=['GET', 'POST'])
|
||||||
|
def public_token_input():
|
||||||
|
if request.method == 'POST':
|
||||||
|
postvars = request.form.to_dict()
|
||||||
|
fleet.public_token_input(postvars)
|
||||||
|
return redirect(url_for('addr_input'))
|
||||||
|
else:
|
||||||
|
return render_template("public_token_input.html")
|
||||||
|
|
||||||
|
@app.route("/app_token_input", methods=['GET', 'POST'])
|
||||||
|
def app_token_input():
|
||||||
|
if request.method == 'POST':
|
||||||
|
postvars = request.form.to_dict()
|
||||||
|
fleet.app_token_input(postvars)
|
||||||
|
return redirect(url_for('addr_input'))
|
||||||
|
else:
|
||||||
|
return render_template("app_token_input.html")
|
||||||
|
|
||||||
|
@app.route("/gmap_key_input", methods=['GET', 'POST'])
|
||||||
|
def gmap_key_input():
|
||||||
|
if request.method == 'POST':
|
||||||
|
postvars = request.form.to_dict()
|
||||||
|
fleet.gmap_key_input(postvars)
|
||||||
|
return redirect(url_for('addr_input'))
|
||||||
|
else:
|
||||||
|
return render_template("gmap_key_input.html")
|
||||||
|
|
||||||
|
@app.route("/CurrentStep.json", methods=['GET'])
|
||||||
|
def find_CurrentStep():
|
||||||
|
directory = "/data/openpilot/selfdrive/manager/"
|
||||||
|
filename = "CurrentStep.json"
|
||||||
|
return send_from_directory(directory, filename, as_attachment=True)
|
||||||
|
|
||||||
|
@app.route("/navdirections.json", methods=['GET'])
|
||||||
|
def find_nav_directions():
|
||||||
|
directory = "/data/openpilot/selfdrive/manager/"
|
||||||
|
filename = "navdirections.json"
|
||||||
|
return send_from_directory(directory, filename, as_attachment=True)
|
||||||
|
|
||||||
|
@app.route("/locations", methods=['GET'])
|
||||||
|
def get_locations():
|
||||||
|
data = fleet.get_locations()
|
||||||
|
return Response(data, content_type="application/json")
|
||||||
|
|
||||||
|
@app.route("/set_destination", methods=['POST'])
|
||||||
|
def set_destination():
|
||||||
|
valid_addr = False
|
||||||
|
postvars = request.get_json()
|
||||||
|
data, valid_addr = fleet.set_destination(postvars, valid_addr)
|
||||||
|
if valid_addr:
|
||||||
|
return Response('{"success": true}', content_type='application/json')
|
||||||
|
else:
|
||||||
|
return Response('{"success": false}', content_type='application/json')
|
||||||
|
|
||||||
|
@app.route("/navigation/<file_name>", methods=['GET'])
|
||||||
|
def find_navicon(file_name):
|
||||||
|
directory = "/data/openpilot/selfdrive/assets/navigation/"
|
||||||
|
return send_from_directory(directory, file_name, as_attachment=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
set_core_affinity([0, 1, 2, 3])
|
||||||
|
except Exception:
|
||||||
|
cloudlog.exception("fleet_manager: failed to set core affinity")
|
||||||
|
app.secret_key = secrets.token_hex(32)
|
||||||
|
app.run(host="0.0.0.0", port=8082)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
285
selfdrive/frogpilot/fleetmanager/helpers.py
Normal file
285
selfdrive/frogpilot/fleetmanager/helpers.py
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from flask import render_template, request, session
|
||||||
|
from functools import wraps
|
||||||
|
from pathlib import Path
|
||||||
|
from openpilot.system.hardware import PC
|
||||||
|
from openpilot.system.hardware.hw import Paths
|
||||||
|
from openpilot.system.loggerd.uploader import listdir_by_creation
|
||||||
|
from tools.lib.route import SegmentName
|
||||||
|
|
||||||
|
# otisserv conversion
|
||||||
|
from common.params import Params
|
||||||
|
from urllib.parse import parse_qs, quote
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
params = Params()
|
||||||
|
|
||||||
|
# path to openpilot screen recordings and error logs
|
||||||
|
if PC:
|
||||||
|
SCREENRECORD_PATH = os.path.join(str(Path.home()), ".comma", "media", "0", "videos", "")
|
||||||
|
ERROR_LOGS_PATH = os.path.join(str(Path.home()), ".comma", "community", "crashes", "")
|
||||||
|
else:
|
||||||
|
SCREENRECORD_PATH = "/data/media/0/videos/"
|
||||||
|
ERROR_LOGS_PATH = "/data/community/crashes/"
|
||||||
|
|
||||||
|
|
||||||
|
def list_files(path):
|
||||||
|
return sorted(listdir_by_creation(path), reverse=True)
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_segment(segment):
|
||||||
|
try:
|
||||||
|
segment_to_segment_name(Paths.log_root(), segment)
|
||||||
|
return True
|
||||||
|
except AssertionError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def segment_to_segment_name(data_dir, segment):
|
||||||
|
fake_dongle = "ffffffffffffffff"
|
||||||
|
return SegmentName(str(os.path.join(data_dir, fake_dongle + "|" + segment)))
|
||||||
|
|
||||||
|
|
||||||
|
def all_segment_names():
|
||||||
|
segments = []
|
||||||
|
for segment in listdir_by_creation(Paths.log_root()):
|
||||||
|
try:
|
||||||
|
segments.append(segment_to_segment_name(Paths.log_root(), segment))
|
||||||
|
except AssertionError:
|
||||||
|
pass
|
||||||
|
return segments
|
||||||
|
|
||||||
|
|
||||||
|
def all_routes():
|
||||||
|
segment_names = all_segment_names()
|
||||||
|
route_names = [segment_name.route_name for segment_name in segment_names]
|
||||||
|
route_times = [route_name.time_str for route_name in route_names]
|
||||||
|
unique_routes = list(dict.fromkeys(route_times))
|
||||||
|
return sorted(unique_routes, reverse=True)
|
||||||
|
|
||||||
|
|
||||||
|
def segments_in_route(route):
|
||||||
|
segment_names = [segment_name for segment_name in all_segment_names() if segment_name.time_str == route]
|
||||||
|
segments = [segment_name.time_str + "--" + str(segment_name.segment_num) for segment_name in segment_names]
|
||||||
|
return segments
|
||||||
|
|
||||||
|
|
||||||
|
def ffmpeg_mp4_concat_wrap_process_builder(file_list, cameratype, chunk_size=1024*512):
|
||||||
|
command_line = ["ffmpeg"]
|
||||||
|
if not cameratype == "qcamera":
|
||||||
|
command_line += ["-f", "hevc"]
|
||||||
|
command_line += ["-r", "20"]
|
||||||
|
command_line += ["-i", "concat:" + file_list]
|
||||||
|
command_line += ["-c", "copy"]
|
||||||
|
command_line += ["-map", "0"]
|
||||||
|
if not cameratype == "qcamera":
|
||||||
|
command_line += ["-vtag", "hvc1"]
|
||||||
|
command_line += ["-f", "mp4"]
|
||||||
|
command_line += ["-movflags", "empty_moov"]
|
||||||
|
command_line += ["-"]
|
||||||
|
return subprocess.Popen(
|
||||||
|
command_line, stdout=subprocess.PIPE,
|
||||||
|
bufsize=chunk_size
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ffmpeg_mp4_wrap_process_builder(filename):
|
||||||
|
"""Returns a process that will wrap the given filename
|
||||||
|
inside a mp4 container, for easier playback by browsers
|
||||||
|
and other devices. Primary use case is streaming segment videos
|
||||||
|
to the vidserver tool.
|
||||||
|
filename is expected to be a pathname to one of the following
|
||||||
|
/path/to/a/qcamera.ts
|
||||||
|
/path/to/a/dcamera.hevc
|
||||||
|
/path/to/a/ecamera.hevc
|
||||||
|
/path/to/a/fcamera.hevc
|
||||||
|
"""
|
||||||
|
basename = filename.rsplit("/")[-1]
|
||||||
|
extension = basename.rsplit(".")[-1]
|
||||||
|
command_line = ["ffmpeg"]
|
||||||
|
if extension == "hevc":
|
||||||
|
command_line += ["-f", "hevc"]
|
||||||
|
command_line += ["-r", "20"]
|
||||||
|
command_line += ["-i", filename]
|
||||||
|
command_line += ["-c", "copy"]
|
||||||
|
command_line += ["-map", "0"]
|
||||||
|
if extension == "hevc":
|
||||||
|
command_line += ["-vtag", "hvc1"]
|
||||||
|
command_line += ["-f", "mp4"]
|
||||||
|
command_line += ["-movflags", "empty_moov"]
|
||||||
|
command_line += ["-"]
|
||||||
|
return subprocess.Popen(
|
||||||
|
command_line, stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ffplay_mp4_wrap_process_builder(file_name):
|
||||||
|
command_line = ["ffmpeg"]
|
||||||
|
command_line += ["-i", file_name]
|
||||||
|
command_line += ["-c", "copy"]
|
||||||
|
command_line += ["-map", "0"]
|
||||||
|
command_line += ["-f", "mp4"]
|
||||||
|
command_line += ["-movflags", "empty_moov"]
|
||||||
|
command_line += ["-"]
|
||||||
|
return subprocess.Popen(
|
||||||
|
command_line, stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_nav_active():
|
||||||
|
if params.get("NavDestination", encoding='utf8') is not None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_public_token():
|
||||||
|
token = params.get("MapboxPublicKey", encoding='utf8')
|
||||||
|
return token.strip() if token is not None else None
|
||||||
|
|
||||||
|
def get_app_token():
|
||||||
|
token = params.get("MapboxSecretKey", encoding='utf8')
|
||||||
|
return token.strip() if token is not None else None
|
||||||
|
|
||||||
|
def get_gmap_key():
|
||||||
|
token = params.get("GMapKey", encoding='utf8')
|
||||||
|
return token.strip() if token is not None else None
|
||||||
|
|
||||||
|
def get_SearchInput():
|
||||||
|
SearchInput = params.get_int("SearchInput")
|
||||||
|
return SearchInput
|
||||||
|
|
||||||
|
def get_PrimeType():
|
||||||
|
PrimeType = params.get_int("PrimeType")
|
||||||
|
return PrimeType
|
||||||
|
|
||||||
|
def get_last_lon_lat():
|
||||||
|
last_pos = params.get("LastGPSPosition")
|
||||||
|
l = json.loads(last_pos)
|
||||||
|
return l["longitude"], l["latitude"]
|
||||||
|
|
||||||
|
def get_locations():
|
||||||
|
data = params.get("ApiCache_NavDestinations", encoding='utf-8')
|
||||||
|
return data
|
||||||
|
|
||||||
|
def parse_addr(postvars, lon, lat, valid_addr, token):
|
||||||
|
addr = postvars.get("fav_val", [""])
|
||||||
|
real_addr = None
|
||||||
|
if addr != "favorites":
|
||||||
|
try:
|
||||||
|
dests = json.loads(params.get("ApiCache_NavDestinations", encoding='utf8').rstrip('\x00'))
|
||||||
|
except TypeError:
|
||||||
|
dests = json.loads("[]")
|
||||||
|
for item in dests:
|
||||||
|
if "label" in item and item["label"] == addr:
|
||||||
|
lat, lon, real_addr = item["latitude"], item["longitude"], item["place_name"]
|
||||||
|
break
|
||||||
|
return (real_addr, lon, lat, real_addr is not None, token)
|
||||||
|
|
||||||
|
def search_addr(postvars, lon, lat, valid_addr, token):
|
||||||
|
if "addr_val" in postvars:
|
||||||
|
addr = postvars.get("addr_val")
|
||||||
|
if addr != "":
|
||||||
|
# Properly encode the address to handle spaces
|
||||||
|
addr_encoded = quote(addr)
|
||||||
|
query = f"https://api.mapbox.com/geocoding/v5/mapbox.places/{addr_encoded}.json?access_token={token}&limit=1"
|
||||||
|
# focus on place around last gps position
|
||||||
|
lngi, lati = get_last_lon_lat()
|
||||||
|
query += "&proximity=%s,%s" % (lngi, lati)
|
||||||
|
r = requests.get(query)
|
||||||
|
if r.status_code != 200:
|
||||||
|
return (addr, lon, lat, valid_addr, token)
|
||||||
|
j = json.loads(r.text)
|
||||||
|
if not j["features"]:
|
||||||
|
return (addr, lon, lat, valid_addr, token)
|
||||||
|
lon, lat = j["features"][0]["geometry"]["coordinates"]
|
||||||
|
valid_addr = True
|
||||||
|
return (addr, lon, lat, valid_addr, token)
|
||||||
|
|
||||||
|
def set_destination(postvars, valid_addr):
|
||||||
|
if postvars.get("latitude") is not None and postvars.get("longitude") is not None:
|
||||||
|
postvars["lat"] = postvars.get("latitude")
|
||||||
|
postvars["lon"] = postvars.get("longitude")
|
||||||
|
postvars["save_type"] = "recent"
|
||||||
|
nav_confirmed(postvars)
|
||||||
|
valid_addr = True
|
||||||
|
else:
|
||||||
|
addr = postvars.get("place_name")
|
||||||
|
token = get_public_token()
|
||||||
|
data, lon, lat, valid_addr, token = search_addr(addr, lon, lat, valid_addr, token)
|
||||||
|
postvars["lat"] = lat
|
||||||
|
postvars["lon"] = lon
|
||||||
|
postvars["save_type"] = "recent"
|
||||||
|
nav_confirmed(postvars)
|
||||||
|
valid_addr= True
|
||||||
|
return postvars, valid_addr
|
||||||
|
|
||||||
|
def nav_confirmed(postvars):
|
||||||
|
if postvars is not None:
|
||||||
|
lat = float(postvars.get("lat"))
|
||||||
|
lng = float(postvars.get("lon"))
|
||||||
|
save_type = postvars.get("save_type")
|
||||||
|
name = postvars.get("name") if postvars.get("name") is not None else ""
|
||||||
|
params.put("NavDestination", "{\"latitude\": %f, \"longitude\": %f, \"place_name\": \"%s\"}" % (lat, lng, name))
|
||||||
|
if name == "":
|
||||||
|
name = str(lat) + "," + str(lng)
|
||||||
|
new_dest = {"latitude": float(lat), "longitude": float(lng), "place_name": name}
|
||||||
|
if save_type == "recent":
|
||||||
|
new_dest["save_type"] = "recent"
|
||||||
|
else:
|
||||||
|
new_dest["save_type"] = "favorite"
|
||||||
|
new_dest["label"] = save_type
|
||||||
|
val = params.get("ApiCache_NavDestinations", encoding='utf8')
|
||||||
|
if val is not None:
|
||||||
|
val = val.rstrip('\x00')
|
||||||
|
dests = [] if val is None else json.loads(val)
|
||||||
|
# type idx
|
||||||
|
type_label_ids = {"home": None, "work": None, "fav1": None, "fav2": None, "fav3": None, "recent": []}
|
||||||
|
idx = 0
|
||||||
|
for d in dests:
|
||||||
|
if d["save_type"] == "favorite":
|
||||||
|
type_label_ids[d["label"]] = idx
|
||||||
|
else:
|
||||||
|
type_label_ids["recent"].append(idx)
|
||||||
|
idx += 1
|
||||||
|
if save_type == "recent":
|
||||||
|
id = None
|
||||||
|
if len(type_label_ids["recent"]) > 10:
|
||||||
|
dests.pop(type_label_ids["recent"][-1])
|
||||||
|
else:
|
||||||
|
id = type_label_ids[save_type]
|
||||||
|
if id is None:
|
||||||
|
dests.insert(0, new_dest)
|
||||||
|
else:
|
||||||
|
dests[id] = new_dest
|
||||||
|
params.put("ApiCache_NavDestinations", json.dumps(dests).rstrip("\n\r"))
|
||||||
|
|
||||||
|
def public_token_input(postvars):
|
||||||
|
if postvars is None or "pk_token_val" not in postvars or postvars.get("pk_token_val")[0] == "":
|
||||||
|
return postvars
|
||||||
|
else:
|
||||||
|
token = postvars.get("pk_token_val").strip()
|
||||||
|
if "pk." not in token:
|
||||||
|
return postvars
|
||||||
|
else:
|
||||||
|
params.put("MapboxPublicKey", token)
|
||||||
|
return token
|
||||||
|
|
||||||
|
def app_token_input(postvars):
|
||||||
|
if postvars is None or "sk_token_val" not in postvars or postvars.get("sk_token_val")[0] == "":
|
||||||
|
return postvars
|
||||||
|
else:
|
||||||
|
token = postvars.get("sk_token_val").strip()
|
||||||
|
if "sk." not in token:
|
||||||
|
return postvars
|
||||||
|
else:
|
||||||
|
params.put("MapboxSecretKey", token)
|
||||||
|
return token
|
||||||
|
|
||||||
|
def gmap_key_input(postvars):
|
||||||
|
if postvars is None or "gmap_key_val" not in postvars or postvars.get("gmap_key_val")[0] == "":
|
||||||
|
return postvars
|
||||||
|
else:
|
||||||
|
token = postvars.get("gmap_key_val").strip()
|
||||||
|
params.put("GMapKey", token)
|
||||||
|
return token
|
||||||
1
selfdrive/frogpilot/fleetmanager/static/favicon.ico
Normal file
1
selfdrive/frogpilot/fleetmanager/static/favicon.ico
Normal file
@@ -0,0 +1 @@
|
|||||||
|
../../../selfdrive/assets/img_spinner_comma.png
|
||||||
BIN
selfdrive/frogpilot/fleetmanager/static/frog.png
Normal file
BIN
selfdrive/frogpilot/fleetmanager/static/frog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
18
selfdrive/frogpilot/fleetmanager/templates/about.html
Normal file
18
selfdrive/frogpilot/fleetmanager/templates/about.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
About
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<br>
|
||||||
|
<h1>About</h1>
|
||||||
|
<br>
|
||||||
|
<footer class="small text-center text-muted" style="word-wrap: break-word;">
|
||||||
|
Special thanks to:<br><br>
|
||||||
|
ntegan1<br>
|
||||||
|
royjr<br>
|
||||||
|
AlexandreSato<br>
|
||||||
|
actuallylemoncurd
|
||||||
|
</footer>
|
||||||
|
{% endblock %}
|
||||||
11
selfdrive/frogpilot/fleetmanager/templates/addr.html
Normal file
11
selfdrive/frogpilot/fleetmanager/templates/addr.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
FrogPilot Navigation
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% with gmap_key=gmap_key, lon=lon, lat=lat %}
|
||||||
|
{% include "addr_input.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock %}
|
||||||
62
selfdrive/frogpilot/fleetmanager/templates/addr_input.html
Normal file
62
selfdrive/frogpilot/fleetmanager/templates/addr_input.html
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{% block main %}
|
||||||
|
<!-- Your form markup -->
|
||||||
|
<form name="searchForm" method="post">
|
||||||
|
<fieldset class="uk-fieldset">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<select class="uk-select" name="fav_val">
|
||||||
|
<option value="favorites">Select Saved Destinations</option>
|
||||||
|
<option value="home">Home</option>
|
||||||
|
<option value="work">Work</option>
|
||||||
|
<option value="fav1">Favorite 1</option>
|
||||||
|
<option value="fav2">Favorite 2</option>
|
||||||
|
<option value="fav3">Favorite 3</option>
|
||||||
|
</select>
|
||||||
|
<input class="uk-input" type="text" name="addr_val" id="pac-input" placeholder="Search a place">
|
||||||
|
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Search">
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<!-- Include the Google Maps Places API script conditionally with JavaScript -->
|
||||||
|
<script>
|
||||||
|
// attach gmap_key to variable
|
||||||
|
let gmap = "{{gmap_key}}";
|
||||||
|
|
||||||
|
// Check if gmap_key is defined
|
||||||
|
if (gmap && gmap !== "None") {
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.src = 'https://maps.googleapis.com/maps/api/js?key={{gmap_key}}&libraries=places&callback=initAutocomplete';
|
||||||
|
script.async = true;
|
||||||
|
script.defer = true;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
// Define the callback function for place_changed
|
||||||
|
function onPlaceChanged() {
|
||||||
|
var place = autocomplete.getPlace();
|
||||||
|
|
||||||
|
// Check if the place has a formatted address
|
||||||
|
if (place.formatted_address) {
|
||||||
|
// Set the value of the input field to the formatted address
|
||||||
|
document.getElementById('pac-input').value = place.formatted_address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the autocomplete variable
|
||||||
|
var autocomplete;
|
||||||
|
|
||||||
|
// Define the initAutocomplete function with initial bounds
|
||||||
|
function initAutocomplete() {
|
||||||
|
var center = new google.maps.LatLng({{lat}}, {{lon}});
|
||||||
|
var bounds = new google.maps.Circle({ center: center, radius: 5000 }).getBounds();
|
||||||
|
|
||||||
|
autocomplete = new google.maps.places.Autocomplete(
|
||||||
|
document.getElementById('pac-input'),
|
||||||
|
{
|
||||||
|
bounds: bounds // Set initial bounds here
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
autocomplete.addListener('place_changed', onPlaceChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
addr_input
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<form name="setSkTokenForm" method="post">
|
||||||
|
<fieldset class="uk-fieldset">
|
||||||
|
<legend class="uk-legend">Set your Mapbox <b>Secret Token</b></legend>
|
||||||
|
<div style="padding: 5px; color: red; font-weight: bold;">{{msg}}</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<input class="uk-input" type="text" name="sk_token_val" placeholder="sk.xxxxxxx...">
|
||||||
|
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Set">
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
11
selfdrive/frogpilot/fleetmanager/templates/error.html
Normal file
11
selfdrive/frogpilot/fleetmanager/templates/error.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
About
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<br> Oops
|
||||||
|
<br><br><br><br>
|
||||||
|
{{ error | safe }}
|
||||||
|
{% endblock %}
|
||||||
16
selfdrive/frogpilot/fleetmanager/templates/error_log.html
Normal file
16
selfdrive/frogpilot/fleetmanager/templates/error_log.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Error Log
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<br>
|
||||||
|
<h1>Error Log of<br>{{ file_name }}</h1>
|
||||||
|
<br>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block unformated %}
|
||||||
|
<pre style="font-size: x-small; margin: 20px;">{{ file_content }}</pre>
|
||||||
|
<br><br>
|
||||||
|
{% endblock %}
|
||||||
14
selfdrive/frogpilot/fleetmanager/templates/error_logs.html
Normal file
14
selfdrive/frogpilot/fleetmanager/templates/error_logs.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Error Logs
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<br>
|
||||||
|
<h1>Error Logs</h1>
|
||||||
|
<br>
|
||||||
|
{% for row in rows %}
|
||||||
|
<a href="/error_logs/{{ row }}">{{ row }}</a><br>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
15
selfdrive/frogpilot/fleetmanager/templates/footage.html
Normal file
15
selfdrive/frogpilot/fleetmanager/templates/footage.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Dashcam Routes
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<br>
|
||||||
|
<h1>Dashcam Routes</h1>
|
||||||
|
<br>
|
||||||
|
{% for row in rows %}
|
||||||
|
<a href="/footage/{{ row }}">{{ row }}</a><br>
|
||||||
|
{% endfor %}
|
||||||
|
<br><br>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
addr_input
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<form name="setGmapTokenForm" method="post">
|
||||||
|
<fieldset class="uk-fieldset">
|
||||||
|
<legend class="uk-legend">Set your Google Map API Key</legend>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<input class="uk-input" type="text" name="gmap_key_val" placeholder="Google Map API KEY">
|
||||||
|
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Submit">
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
14
selfdrive/frogpilot/fleetmanager/templates/index.html
Normal file
14
selfdrive/frogpilot/fleetmanager/templates/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Home
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<br>
|
||||||
|
<h1>Fleet Manager</h1>
|
||||||
|
<br>
|
||||||
|
<a href='/footage'>View Dashcam Footage</a><br>
|
||||||
|
<br><a href='/screenrecords'>View Screen Recordings</a><br>
|
||||||
|
<br><a href='/error_logs'>Access Error Logs</a><br>
|
||||||
|
{% endblock %}
|
||||||
100
selfdrive/frogpilot/fleetmanager/templates/layout.html
Normal file
100
selfdrive/frogpilot/fleetmanager/templates/layout.html
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" id="htmlElement">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="initial-scale=1, width=device-width">
|
||||||
|
<link href="/static/favicon.ico" rel="icon">
|
||||||
|
<!-- http://getbootstrap.com/docs/5.3/ -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||||
|
<style>
|
||||||
|
.navbar-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.navbar-brand img {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- UIkit CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/css/uikit.min.css" />
|
||||||
|
|
||||||
|
<!-- UIkit JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/js/uikit.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/js/uikit-icons.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Dark mode styles */
|
||||||
|
#htmlElement.dark-mode,
|
||||||
|
#htmlElement.dark-mode input,
|
||||||
|
#htmlElement.dark-mode select,
|
||||||
|
#htmlElement.dark-mode body,
|
||||||
|
#htmlElement.dark-mode h1 {
|
||||||
|
background-color: #121212; /* Dark background color */
|
||||||
|
color: #ffffff; /* Light text color */
|
||||||
|
}
|
||||||
|
.nav-link {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>FrogPilot: {% block title %}{% endblock %}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-fixed-top navbar-expand-sm navbar-dark bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<a href="/" class="navbar-brand mb-0 h1">
|
||||||
|
<img class="d-inline-block align-top mr-2" src="/static/frog.png" /> FrogPilot </a>
|
||||||
|
<button type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" class="navbar-toggler" aria-controls="navbarNav" aria-expanded="false" arial-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse ml-auto" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a href="/footage" class="nav-link">Dashcam Routes</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a href="/screenrecords" class="nav-link">Screen Recordings</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a href="/error_logs" class="nav-link">Error Logs</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a href="/addr_input" class="nav-link">Navigation</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main class="container-fluid p-7 text-center"> {% block main %}{% endblock %} </main>
|
||||||
|
{% block unformated %}{% endblock %}
|
||||||
|
|
||||||
|
<button class="uk-button uk-button-default uk-margin-small-right" onclick="toggleDarkMode()">Toggle Dark Mode</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function setCookie(name, value, days) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/`;
|
||||||
|
}
|
||||||
|
function getCookie(name) {
|
||||||
|
return document.cookie.split('; ')
|
||||||
|
.find(row => row.startsWith(name))
|
||||||
|
?.split('=')[1] || null;
|
||||||
|
}
|
||||||
|
function toggleDarkMode() {
|
||||||
|
console.log('Toggle Dark Mode function called');
|
||||||
|
const htmlElement = document.documentElement;
|
||||||
|
htmlElement.classList.toggle('dark-mode');
|
||||||
|
setCookie('darkMode', htmlElement.classList.contains('dark-mode'), 365);
|
||||||
|
}
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const htmlElement = document.documentElement;
|
||||||
|
htmlElement.classList.toggle('dark-mode', getCookie('darkMode') === 'true');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
addr_input
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<div><img src="https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/pin-s-l+000({{lon}},{{lat}})/{{lon}},{{lat}},14/300x300?access_token={{token}}" /></div>
|
||||||
|
<div style="padding: 5px; font-size: 10px;">{{addr}}</div>
|
||||||
|
<form name="navForm" method="post">
|
||||||
|
<fieldset class="uk-fieldset">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<input type="hidden" name="name" value="{{addr}}">
|
||||||
|
<input type="hidden" name="lat" value="{{lat}}">
|
||||||
|
<input type="hidden" name="lon" value="{{lon}}">
|
||||||
|
<select id="save_type" name="save_type" class="uk-select">
|
||||||
|
<option value="recent">Recent</option>
|
||||||
|
<option value="home">Set Home</option>
|
||||||
|
<option value="work">Set Work</option>
|
||||||
|
<option value="fav1">Set Favorite 1</option>
|
||||||
|
<option value="fav2">Set Favorite 2</option>
|
||||||
|
<option value="fav3">Set Favorite 3</option>
|
||||||
|
</select>
|
||||||
|
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Start Navigation">
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
149
selfdrive/frogpilot/fleetmanager/templates/nav_directions.html
Normal file
149
selfdrive/frogpilot/fleetmanager/templates/nav_directions.html
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
{% block main %}
|
||||||
|
<div id="destinationHeading" style="font-weight: bold;"></div>
|
||||||
|
<div id="jsonOutput" style="text-align: left;"></div>
|
||||||
|
<style>
|
||||||
|
/* Added CSS styles to display images and text on the same line */
|
||||||
|
#jsonOutput span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jsonOutput img {
|
||||||
|
margin-right: 10px; /* Adjust the margin as needed */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
let useMetricUnits = false;
|
||||||
|
let previousNavdirectionsUuid = null;
|
||||||
|
let previousCurrentStepUuid = null;
|
||||||
|
let jsonData = null;
|
||||||
|
let initNav = 0;
|
||||||
|
|
||||||
|
async function loadCurrentStep() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('CurrentStep.json'); // Load CurrentStep.json
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch CurrentStep.json.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
return json;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching or parsing CurrentStep.json:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadNavdirectionsData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('navdirections.json'); // Load navdirections.json
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch JSON file. Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
// Check if the UUIDs match
|
||||||
|
const match = json.uuid === previousCurrentStepUuid;
|
||||||
|
|
||||||
|
previousNavdirectionsUuid = json.uuid;
|
||||||
|
jsonData = json;
|
||||||
|
initNav = 1;
|
||||||
|
return jsonData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching or parsing JSON data:', error);
|
||||||
|
return jsonData; // Return the existing data on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndDisplayData() {
|
||||||
|
const currentStepData = await loadCurrentStep();
|
||||||
|
|
||||||
|
if (currentStepData !== null) {
|
||||||
|
// Set the initial value for `currentStep` based on `CurrentStep.json`
|
||||||
|
previousCurrentStepUuid = currentStepData.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStepData.uuid != previousNavdirectionsUuid) {
|
||||||
|
await loadNavdirectionsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initNav === 0) {
|
||||||
|
await loadNavdirectionsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if jsonData is available and proceed
|
||||||
|
if (jsonData) {
|
||||||
|
// Access the data you need from the loaded JSON
|
||||||
|
const firstRoute = jsonData.routes[0];
|
||||||
|
const firstLeg = firstRoute.legs[0];
|
||||||
|
const steps = firstLeg.steps;
|
||||||
|
const destination = firstRoute.Destination;
|
||||||
|
|
||||||
|
// Determine whether to use metric or imperial units based on the 'Metric' key
|
||||||
|
useMetricUnits = firstRoute.Metric === true; // Removed `const` to update the global useMetricUnits
|
||||||
|
|
||||||
|
// Display the 'destination' value on the webpage
|
||||||
|
const destinationHeading = document.getElementById('destinationHeading');
|
||||||
|
destinationHeading.textContent = `Destination: ${destination}`;
|
||||||
|
|
||||||
|
// Display values from the steps
|
||||||
|
const jsonOutputDiv = document.getElementById('jsonOutput');
|
||||||
|
jsonOutputDiv.innerHTML = '';
|
||||||
|
|
||||||
|
for (let i = currentStepData.CurrentStep; i < steps.length - 1; i++) {
|
||||||
|
const step = steps[i];
|
||||||
|
const instruction0 = steps[i].maneuver.instruction;
|
||||||
|
const instruction = steps[i + 1].maneuver.instruction;
|
||||||
|
const maneuverType = steps[i + 1].maneuver.type;
|
||||||
|
const modifier = steps[i + 1].maneuver.modifier;
|
||||||
|
let distance = step.distance;
|
||||||
|
|
||||||
|
if (!useMetricUnits) {
|
||||||
|
// Convert distance to miles if using imperial units
|
||||||
|
distance = distance * 0.000621371;
|
||||||
|
} else {
|
||||||
|
distance = distance / 1000; // Convert meters to kilometers
|
||||||
|
}
|
||||||
|
const sanitizedManeuverType = maneuverType.replace(/\s+/g, '_');
|
||||||
|
const sanitizedModifier = modifier ? `_${modifier.replace(/\s+/g, '_')}` : '';
|
||||||
|
const filterStyle = !htmlElement.classList.contains('dark-mode') ? 'filter: invert(100%);' : '';
|
||||||
|
// Display the values on the webpage
|
||||||
|
if (i === 0) {
|
||||||
|
jsonOutputDiv.innerHTML += `
|
||||||
|
<hr>
|
||||||
|
<span>
|
||||||
|
<img src="/navigation/direction_depart.png" alt="${maneuverType} icon" width="25" height="25" style="${filterStyle}">
|
||||||
|
<p>${instruction0}</p>
|
||||||
|
</span>
|
||||||
|
<hr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
jsonOutputDiv.innerHTML += `
|
||||||
|
<span>
|
||||||
|
<img src="/navigation/direction_${sanitizedManeuverType}${sanitizedModifier}.png" alt="${maneuverType} icon" width="25" height="25" style="${filterStyle}">
|
||||||
|
<p>In ${distance.toFixed(1)} ${useMetricUnits ? 'km' : 'miles'}: ${instruction}</p>
|
||||||
|
</span>
|
||||||
|
<hr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load `CurrentStep.json` initially
|
||||||
|
loadCurrentStep().then((currentStepData) => {
|
||||||
|
if (currentStepData !== null) {
|
||||||
|
// Set the initial value for `currentStep` based on `CurrentStep.json`
|
||||||
|
previousCurrentStepUuid = currentStepData.uuid;
|
||||||
|
loadNavdirectionsData();
|
||||||
|
// Fetch and display data initially
|
||||||
|
fetchAndDisplayData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Periodically fetch `CurrentStep.json` and display data every 5 seconds
|
||||||
|
setInterval(fetchAndDisplayData, 5000); // Adjust the interval as needed (in milliseconds)
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
13
selfdrive/frogpilot/fleetmanager/templates/nonprime.html
Normal file
13
selfdrive/frogpilot/fleetmanager/templates/nonprime.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
addr_input
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% with gmap_key=gmap_key, lon=lon, lat=lat %}
|
||||||
|
{% include "addr_input.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% include "nav_directions.html" %}
|
||||||
|
{% endblock %}
|
||||||
9
selfdrive/frogpilot/fleetmanager/templates/prime.html
Normal file
9
selfdrive/frogpilot/fleetmanager/templates/prime.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
FrogPilot Navigation
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "nav_directions.html" %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
addr_input
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<form name="setPkTokenForm" method="post">
|
||||||
|
<fieldset class="uk-fieldset">
|
||||||
|
<legend class="uk-legend">Set your Mapbox <b>Public Token</b></legend>
|
||||||
|
<div style="padding: 5px; color: red; font-weight: bold;">{{msg}}</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<input class="uk-input" type="text" name="pk_token_val" placeholder="pk.xxxxxxx...">
|
||||||
|
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Set">
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
57
selfdrive/frogpilot/fleetmanager/templates/route.html
Normal file
57
selfdrive/frogpilot/fleetmanager/templates/route.html
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Dashcam Segments
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% autoescape false %}
|
||||||
|
<br>
|
||||||
|
<h1>Dashcam Segments (one per minute)</h1>
|
||||||
|
<br>
|
||||||
|
<video id="video" width="320" height="240" controls autoplay="autoplay" style="background:black">
|
||||||
|
</video>
|
||||||
|
<br><br>
|
||||||
|
current segment: <span id="currentsegment"></span>
|
||||||
|
<br>
|
||||||
|
current view: <span id="currentview"></span>
|
||||||
|
<br>
|
||||||
|
<a download="{{ route }}-{{ query_type }}.mp4" href="/footage/full/{{ query_type }}/{{ route }}">download full route {{ query_type }}</a>
|
||||||
|
<br><br>
|
||||||
|
<a href="{{ route }}?0,qcamera">qcamera</a> -
|
||||||
|
<a href="{{ route }}?0,fcamera">fcamera</a> -
|
||||||
|
<a href="{{ route }}?0,dcamera">dcamera</a> -
|
||||||
|
<a href="{{ route }}?0,ecamera">ecamera</a>
|
||||||
|
<br><br>
|
||||||
|
{{ links }}
|
||||||
|
<script>
|
||||||
|
var video = document.getElementById('video');
|
||||||
|
var tracks = {
|
||||||
|
list: [{{ segments }}],
|
||||||
|
index: {{ query_segment }},
|
||||||
|
next: function() {
|
||||||
|
if (this.index == this.list.length - 1) this.index = 0;
|
||||||
|
else {
|
||||||
|
this.index += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
play: function() {
|
||||||
|
return ( "{{ query_type }}/" + this.list[this.index] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
video.addEventListener('ended', function(e) {
|
||||||
|
tracks.next();
|
||||||
|
video.src = tracks.play();
|
||||||
|
document.getElementById("currentsegment").textContent=video.src.split("/")[5];
|
||||||
|
document.getElementById("currentview").textContent=video.src.split("/")[4];
|
||||||
|
video.load();
|
||||||
|
video.play();
|
||||||
|
});
|
||||||
|
video.src = tracks.play();
|
||||||
|
document.getElementById("currentsegment").textContent=video.src.split("/")[5];
|
||||||
|
document.getElementById("currentview").textContent=video.src.split("/")[4];
|
||||||
|
</script>
|
||||||
|
{% endautoescape %}
|
||||||
|
<br><br>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Screen Recordings
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<br>
|
||||||
|
<h1>Screen Recordings</h1>
|
||||||
|
<br>
|
||||||
|
<video id="video" width="320" height="240" controls autoplay="autoplay" style="background:black">
|
||||||
|
</video>
|
||||||
|
<br><br>
|
||||||
|
current view: <span id="mycurrentview"></span>
|
||||||
|
<br>
|
||||||
|
<a href="/screenrecords/download/{{ clip }}">download: {{ clip }}</a><br><br>
|
||||||
|
<script>
|
||||||
|
var video = document.getElementById("video");
|
||||||
|
var track = {
|
||||||
|
play: function() {
|
||||||
|
return ( "/screenrecords/play/pipe/" + '{{ clip }}' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
video.src = track.play();
|
||||||
|
document.getElementById("mycurrentview").textContent=video.src.split("/")[6];
|
||||||
|
</script>
|
||||||
|
{% for row in rows %}
|
||||||
|
<a href="/screenrecords/{{ row }}">{{ row }}</a><br>
|
||||||
|
{% endfor %}
|
||||||
|
<br><br>
|
||||||
|
{% endblock %}
|
||||||
@@ -102,6 +102,7 @@ procs = [
|
|||||||
PythonProcess("webjoystick", "tools.bodyteleop.web", notcar),
|
PythonProcess("webjoystick", "tools.bodyteleop.web", notcar),
|
||||||
|
|
||||||
# FrogPilot processes
|
# FrogPilot processes
|
||||||
|
PythonProcess("fleet_manager", "selfdrive.frogpilot.fleetmanager.fleet_manager", always_run),
|
||||||
]
|
]
|
||||||
|
|
||||||
managed_processes = {p.name: p for p in procs}
|
managed_processes = {p.name: p for p in procs}
|
||||||
|
|||||||
BIN
system/fleetmanager/static/frog.png
Normal file
BIN
system/fleetmanager/static/frog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
15
system/fleetmanager/templates/index.html
Normal file
15
system/fleetmanager/templates/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Home
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<br>
|
||||||
|
<h1>Fleet Manager</h1>
|
||||||
|
<br>
|
||||||
|
<a href='/footage'>View Dashcam Footage</a><br>
|
||||||
|
<br><a href='/screenrecords'>View Screen Recordings</a><br>
|
||||||
|
<br><a href='/error_logs'>Access Error Logs</a><br>
|
||||||
|
<br><a href='/addr_input'>Navigation</a><br>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user