FrogPilot 0.9.7

This commit is contained in:
FrogAi
2024-06-27 10:22:08 -07:00
parent da00915ac8
commit b705b02e70
682 changed files with 181798 additions and 1348 deletions
@@ -0,0 +1,5 @@
# Fleet Manager
Fleet Manger on openpilot allows viewing dashcam footage, screen recordings, error logs and on-device navigation 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), [royjr](https://github.com/royjr), [sunnyhaibin] (https://github.com/sunnypilot), [dragonpilot](https://github.com/dragonpilot-community) and [chatgpt](https://chat.openai.com/).
The network can be set up by Wi-Fi, mobile hotspot, or tethering on the comma device. Navigate to http://ipAddress:8082 to access.
@@ -0,0 +1,379 @@
#!/usr/bin/env python3
# otisserv - Copyright (c) 2019-, Rick Lan, dragonpilot community, and a number of other of contributors.
# Fleet Manager - [actuallylemoncurd](https://github.com/actuallylemoncurd), [AlexandreSato](https://github.com/alexandreSato), [ntegan1](https://github.com/ntegan1), [royjr](https://github.com/royjr), and [sunnyhaibin] (https://github.com/sunnypilot)
# Almost everything else - ChatGPT
# dirty PR pusher - mike8643
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import os
import requests
import secrets
import traceback
import openpilot.selfdrive.frogpilot.fleetmanager.helpers as fleet
from flask import Flask, Response, jsonify, redirect, render_template, request, send_from_directory, session, url_for
from pathlib import Path
from requests.exceptions import ConnectionError
from openpilot.common.realtime import set_core_affinity
from openpilot.common.swaglog import cloudlog
from openpilot.system.hardware.hw import Paths
from openpilot.selfdrive.frogpilot.frogpilot_variables import has_prime
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():
route_paths = fleet.all_routes()
gifs = []
for route_path in route_paths:
input_path = Paths.log_root() + route_path + "--0/qcamera.ts"
output_path = Paths.log_root() + route_path + "--0/preview.gif"
fleet.video_to_img(input_path, output_path)
gif_path = route_path + "--0/preview.gif"
gifs.append(gif_path)
zipped = zip(route_paths, gifs)
return render_template("footage.html", zipped=zipped)
@app.route("/preserved/")
@app.route("/preserved")
def preserved():
query_type = "qcamera"
route_paths = []
gifs = []
segments = fleet.preserved_routes()
for segment in segments:
input_path = Paths.log_root() + segment + "/qcamera.ts"
output_path = Paths.log_root() + segment + "/preview.gif"
fleet.video_to_img(input_path, output_path)
split_segment = segment.split("--")
route_paths.append(f"{split_segment[0]}--{split_segment[1]}?{split_segment[2]},{query_type}")
gif_path = segment + "/preview.gif"
gifs.append(gif_path)
zipped = zip(route_paths, gifs, segments)
return render_template("preserved.html", zipped=zipped)
@app.route("/screenrecords/")
@app.route("/screenrecords")
def screenrecords():
rows = fleet.list_file(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():
rows = fleet.list_file(fleet.ERROR_LOGS_PATH)
if not rows:
return render_template("error.html", error="no error logs found at:<br><br>" + fleet.ERROR_LOGS_PATH)
return render_template("error_logs.html", rows=rows)
@app.route("/error_logs/<file_name>")
def open_error_log(file_name):
f = open(Path(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():
preload = fleet.preload_favs()
search_input = fleet.get_search_input()
token = fleet.get_public_token()
lon = 0.0
lat = 0.0
if request.method == 'POST':
postvars = request.form.to_dict()
valid_addr = False
addr, lon, lat, valid_addr, token = fleet.parse_addr(postvars, lon, lat, valid_addr, token)
if not valid_addr:
addr = request.form.get('addr_val')
addr, lon, lat, valid_addr, token = fleet.search_addr(postvars, lon, lat, valid_addr, token)
if valid_addr:
return redirect(url_for('nav_confirmation', addr=addr, lon=lon, lat=lat))
return render_template("error.html")
if has_prime():
return render_template("prime.html")
if search_input == 0:
if fleet.get_public_token() is None:
return redirect(url_for('public_token_input'))
if fleet.get_secret_token() is None:
return redirect(url_for('app_token_input'))
if search_input == 1:
amap_key, amap_key_2 = fleet.get_amap_key()
if not amap_key or not amap_key_2:
return redirect(url_for('amap_key_input'))
return redirect(url_for('amap_addr_input'))
if search_input == 2:
gmap_key = fleet.get_gmap_key()
lon, lat = fleet.get_last_lon_lat()
if not gmap_key:
return redirect(url_for('gmap_key_input'))
return render_template("addr.html", gmap_key=gmap_key, lon=lon, lat=lat, home=preload[0], work=preload[1], fav1=preload[2], fav2=preload[3], fav3=preload[4])
if fleet.get_nav_active():
return render_template("nonprime.html", gmap_key=None, lon=None, lat=None, home=preload[0], work=preload[1], fav1=preload[2], fav2=preload[3], fav3=preload[4])
return render_template("addr.html", gmap_key=None, lon=None, lat=None, home=preload[0], work=preload[1], fav1=preload[2], fav2=preload[3], fav3=preload[4])
@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("/amap_key_input", methods=['GET', 'POST'])
def amap_key_input():
if request.method == 'POST':
postvars = request.form.to_dict()
fleet.amap_key_input(postvars)
return redirect(url_for('amap_addr_input'))
else:
return render_template("amap_key_input.html")
@app.route("/amap_addr_input", methods=['GET', 'POST'])
def amap_addr_input():
if request.method == 'POST':
postvars = request.form.to_dict()
fleet.nav_confirmed(postvars)
return redirect(url_for('amap_addr_input'))
else:
lon, lat = fleet.get_last_lon_lat()
amap_key, amap_key_2 = fleet.get_amap_key()
return render_template("amap_addr_input.html", lon=lon, lat=lat, amap_key=amap_key, amap_key_2=amap_key_2)
@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)
@app.route("/previewgif/<path:file_path>", methods=['GET'])
def find_previewgif(file_path):
directory = "/data/media/0/realdata/"
return send_from_directory(directory, file_path, as_attachment=True)
@app.route("/tools", methods=['GET'])
def tools_route():
return render_template("tools.html")
@app.route("/get_toggle_values", methods=['GET'])
def get_toggle_values_route():
toggle_values = fleet.get_all_toggle_values()
return jsonify(toggle_values)
@app.route("/reset_toggle_values", methods=['POST'])
def reset_toggle_values_route():
try:
fleet.reset_toggle_values()
return jsonify({"message": "Toggles reset successfully! Rebooting..."}), 200
except Exception as error:
return jsonify({"error": "Failed to reset toggles...", "details": str(error)}), 400
@app.route("/store_toggle_values", methods=['POST'])
def store_toggle_values_route():
try:
updated_values = request.get_json()
fleet.store_toggle_values(updated_values)
return jsonify({"message": "Values updated successfully"}), 200
except Exception as error:
return jsonify({"error": "Failed to update values", "details": str(error)}), 400
@app.route("/lock_doors", methods=['POST'])
def lock_doors_route():
try:
fleet.lock_doors()
return jsonify({"message": "Doors locked successfully!"}), 200
except Exception as error:
return jsonify({"error": "Failed to lock doors...", "details": str(error)}), 400
@app.route("/unlock_doors", methods=['POST'])
def unlock_doors_route():
try:
fleet.unlock_doors()
return jsonify({"message": "Doors unlocked successfully!"}), 200
except Exception as error:
return jsonify({"error": "Failed to unlock doors...", "details": str(error)}), 400
@app.route("/reboot_device", methods=['POST'])
def reboot_device_route():
try:
fleet.reboot_device()
return jsonify({"message": "Successfully rebooted!"}), 200
except Exception as error:
return jsonify({"error": "Failed to reboot...", "details": str(error)}), 400
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()
+505
View File
@@ -0,0 +1,505 @@
# otisserv - Copyright (c) 2019-, Rick Lan, dragonpilot community, and a number of other of contributors.
# Fleet Manager - [actuallylemoncurd](https://github.com/actuallylemoncurd), [AlexandreSato](https://github.com/alexandreSato), [ntegan1](https://github.com/ntegan1), [royjr](https://github.com/royjr), and [sunnyhaibin] (https://github.com/sunnypilot)
# Almost everything else - ChatGPT
# dirty PR pusher - mike8643
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import base64
import json
import math
import os
import requests
import subprocess
import openpilot.system.sentry as sentry
from pathlib import Path
from typing import List
from urllib.parse import quote
from openpilot.common.params import ParamKeyType
from openpilot.selfdrive.car.toyota.carcontroller import LOCK_CMD, UNLOCK_CMD
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
from openpilot.system.loggerd.uploader import listdir_by_creation
from openpilot.system.loggerd.xattr_cache import getxattr
from panda import Panda
from tools.lib.route import SegmentName
from openpilot.selfdrive.frogpilot.frogpilot_variables import params, update_frogpilot_toggles
XOR_KEY = "s8#pL3*Xj!aZ@dWq"
pi = 3.1415926535897932384626
x_pi = 3.14159265358979324 * 3000.0 / 180.0
a = 6378245.0
ee = 0.00669342162296594323
PRESERVE_ATTR_NAME = 'user.preserve'
PRESERVE_ATTR_VALUE = b'1'
PRESERVE_COUNT = 5
# path to openpilot screen recordings and error logs
if PC:
SCREENRECORD_PATH = os.path.join(str(Path.home()), ".comma", "media", "screen_recordings", "")
ERROR_LOGS_PATH = os.path.join(str(Path.home()), ".comma", "community", "crashes", "")
else:
SCREENRECORD_PATH = "/data/media/screen_recordings/"
ERROR_LOGS_PATH = sentry.CRASHES_DIR
def list_files(path): # still used for footage
return sorted(listdir_by_creation(path), reverse=True)
def list_file(path): # new function for screenrecords/error-logs
if os.path.exists(path):
files = os.listdir(path)
sorted_files = sorted(files, reverse=True)
else:
return [] # Return an empty list if there are no files or directory
return sorted_files
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 preserved_routes():
dirs = listdir_by_creation(Paths.log_root())
preserved_segments = get_preserved_segments(dirs)
return sorted(preserved_segments, reverse=True)
def has_preserve_xattr(d: str) -> bool:
return getxattr(os.path.join(Paths.log_root(), d), PRESERVE_ATTR_NAME) == PRESERVE_ATTR_VALUE
def get_preserved_segments(dirs_by_creation: List[str]) -> List[str]:
preserved = []
for n, d in enumerate(filter(has_preserve_xattr, reversed(dirs_by_creation))):
if n == PRESERVE_COUNT:
break
date_str, _, seg_str = d.rpartition("--")
# ignore non-segment directories
if not date_str:
continue
try:
seg_num = int(seg_str)
except ValueError:
continue
# preserve segment and its prior
preserved.append(d)
return preserved
def video_to_gif(input_path, output_path, fps=1, duration=6): # not used right now but can if want longer animated gif
if os.path.exists(output_path):
return
command = [
'ffmpeg', '-y', '-i', input_path,
'-filter_complex',
f'fps={fps},scale=240:-1:flags=lanczos,setpts=0.1*PTS,split[s0][s1];[s0]palettegen=max_colors=32[p];[s1][p]paletteuse=dither=bayer',
'-t', str(duration), output_path
]
subprocess.run(command)
print(f"GIF file created: {output_path}")
def video_to_img(input_path, output_path, fps=1, duration=6):
if os.path.exists(output_path):
return
subprocess.run(['ffmpeg', '-y', '-i', input_path, '-ss', '5', '-vframes', '1', output_path])
print(f"GIF file created: {output_path}")
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_amap_key():
token = params.get("AMapKey1", encoding='utf8')
token2 = params.get("AMapKey2", encoding='utf8')
return (token.strip() if token else None, token2.strip() if token2 else None)
def get_gmap_key():
token = params.get("GMapKey", encoding='utf8')
return token.strip() if token else None
def get_public_token():
token = params.get("MapboxPublicKey", encoding='utf8')
return token.strip() if token and token.startswith("pk") else None
def get_secret_token():
token = params.get("MapboxSecretKey", encoding='utf8')
return token.strip() if token and token.startswith("sk") else None
def get_search_input():
return params.get_int("SearchInput")
def get_last_lon_lat():
last_pos = params.get("LastGPSPosition")
if last_pos:
l = json.loads(last_pos)
else:
return 0.0, 0.0
return l["longitude"], l["latitude"]
def get_locations():
data = params.get("ApiCache_NavDestinations", encoding='utf-8')
return data
def preload_favs():
try:
nav_destinations = json.loads(params.get("ApiCache_NavDestinations", encoding='utf8') or "{}")
except TypeError:
return (None, None, None, None, None)
locations = {"home": None, "work": None, "fav1": None, "fav2": None, "fav3": None}
for item in nav_destinations:
label = item.get("label")
if label in locations and locations[label] is None:
locations[label] = item.get("place_name")
return tuple(locations.values())
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') or "{}")
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 ""
if params.get_int("SearchInput") == 1:
lng, lat = gcj02towgs84(lng, lat)
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
def amap_key_input(postvars):
if postvars is None or "amap_key_val" not in postvars or postvars.get("amap_key_val")[0] == "":
return postvars
else:
token = postvars.get("amap_key_val").strip()
token2 = postvars.get("amap_key_val_2").strip()
params.put("AMapKey1", token)
params.put("AMapKey2", token2)
return token
def gcj02towgs84(lng, lat):
dlat = transform_lat(lng - 105.0, lat - 35.0)
dlng = transform_lng(lng - 105.0, lat - 35.0)
radlat = lat / 180.0 * pi
magic = math.sin(radlat)
magic = 1 - ee * magic * magic
sqrtmagic = math.sqrt(magic)
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
mglat = lat + dlat
mglng = lng + dlng
return [lng * 2 - mglng, lat * 2 - mglat]
def transform_lat(lng, lat):
ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(abs(lng))
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0
ret += (20.0 * math.sin(lat * pi) + 40.0 * math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 * math.sin(lat * pi / 30.0)) * 2.0 / 3.0
return ret
def transform_lng(lng, lat):
ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(abs(lng))
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0
ret += (20.0 * math.sin(lng * pi) + 40.0 * math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
return ret
def xor_encrypt_decrypt(data: str, key: str) -> str:
return ''.join(chr(ord(c) ^ ord(key[i % len(key)])) for i, c in enumerate(data))
def encode_parameters(params_dict):
serialized_data = json.dumps(params_dict)
obfuscated_data = xor_encrypt_decrypt(serialized_data, XOR_KEY)
encoded_data = base64.b64encode(obfuscated_data.encode('utf-8')).decode('utf-8')
return encoded_data
def decode_parameters(encoded_string):
obfuscated_data = base64.b64decode(encoded_string.encode('utf-8')).decode('utf-8')
decrypted_data = xor_encrypt_decrypt(obfuscated_data, XOR_KEY)
return json.loads(decrypted_data)
def get_all_toggle_values():
excluded_keys = [
"ApiCache_NavDestinations", "CalibrationParams", "CarParamsPersistent",
"CarParamsPrevRoute", "GitDiff", "LastGPSPosition", "LiveParameters",
"LiveTorqueParameters", "NavDestination", "NavPastDestinations"
]
toggle_values = {}
for key in params.all_keys():
if key.decode('utf-8') in excluded_keys:
continue
if params.get_key_type(key) & ParamKeyType.PERSISTENT:
if isinstance(params.get(key), bytes):
value = params.get(key, encoding='utf-8')
else:
value = params.get(key) or "0"
toggle_values[key.decode('utf-8')] = value
return encode_parameters(toggle_values)
def reset_toggle_values():
params.put_bool("DoToggleReset", True)
HARDWARE.reboot()
def store_toggle_values(request_data):
excluded_keys = [
"ApiCache_NavDestinations", "CalibrationParams", "CarParamsPersistent",
"CarParamsPrevRoute", "GitDiff", "LastGPSPosition", "LiveParameters",
"LiveTorqueParameters", "NavDestination", "NavPastDestinations"
]
toggle_values = decode_parameters(request_data['data'])
for key, value in toggle_values.items():
if key in excluded_keys:
continue
params.put(key, value)
update_frogpilot_toggles()
def lock_doors():
panda = Panda()
panda.set_safety_mode(panda.SAFETY_ALLOUTPUT)
panda.can_send(0x750, LOCK_CMD, 0)
panda.set_safety_mode(panda.SAFETY_TOYOTA)
panda.send_heartbeat()
def unlock_doors():
panda = Panda()
panda.set_safety_mode(panda.SAFETY_ALLOUTPUT)
panda.can_send(0x750, UNLOCK_CMD, 0)
panda.set_safety_mode(panda.SAFETY_TOYOTA)
panda.send_heartbeat()
def reboot_device():
HARDWARE.reboot()
@@ -0,0 +1 @@
../../../selfdrive/assets/img_spinner_comma.png
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

@@ -0,0 +1,21 @@
{% 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<br>
sunnyhaibin<br>
dragonpilot<br>
chatgpt<br>
</footer>
{% endblock %}
@@ -0,0 +1,11 @@
{% extends "layout.html" %}
{% block title %}
Navigation
{% endblock %}
{% block main %}
{% with gmap_key=gmap_key, lon=lon, lat=lat, home=home, work=work, fav1=fav1, fav2=fav2, fav3=fav3 %}
{% include "addr_input.html" %}
{% endwith %}
{% endblock %}
@@ -0,0 +1,64 @@
{% block main %}
<!-- Your form markup -->
<form name="searchForm" method="post">
<fieldset class="uk-fieldset">
<div class="uk-margin">
{% if home or work or fav1 or fav2 or fav3 %}
<select class="uk-select" name="fav_val">
<option value="favorites">Select Saved Destinations</option>
{% if home %} <option value="home">Home: {{home}}</option> {% endif %}
{% if work %} <option value="work">Work: {{work}}</option> {% endif %}
{% if fav1 %} <option value="fav1">Favorite 1: {{fav1}}</option> {% endif %}
{% if fav2 %} <option value="fav2">Favorite 2: {{fav2}}</option> {% endif %}
{% if fav3 %} <option value="fav3">Favorite 3: {{fav3}}</option> {% endif %}
</select>
{% endif %}
<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,215 @@
{% extends "layout.html" %}
{% block title %}
amap_addr_input
{% endblock %}
{% block main %}
<!-- Head section moved into body -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>输入提示后查询</title>
<!-- 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>
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode:'{{amap_key_2}}',
}
</script>
<style type="text/css">
body {
margin: 0;
height: 100%;
width: 100%;
position: absolute;
}
#mapContainer {
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 400px;
}
#amap-container {
height: 400px; /* Adjust height as needed */
}
.button-group {
position: absolute;
bottom: 20px;
right: 20px;
font-size: 12px;
padding: 10px;
}
.button-group .button {
height: 28px;
line-height: 28px;
background-color: #0D9BF2;
color: #FFF;
border: 0;
outline: none;
padding-left: 5px;
padding-right: 5px;
border-radius: 3px;
margin-bottom: 4px;
cursor: pointer;
}
.amap-info-content {
font-size: 12px;
}
</style>
<script type="text/javascript"
src="https://webapi.amap.com/maps?v=1.4.2&key={{amap_key}}"></script>
<!-- Rest of the HTML body content -->
<div class="uk-grid-match uk-grid-small uk-text-center" uk-grid>
<div class="uk-width-1-3@m">
<select id="save_type" class="uk-select">
<option value="recent">最近</option>
<option value="home">住家</option>
<option value="work">工作</option>
</select>
</div>
<div class="uk-width-expand@m">
<input class="uk-input" type="text" id="keyword" name="keyword"
placeholder="请输入关键字:(选定后搜索)" onfocus='this.value=""' />
</div>
</div>
<input type="hidden" id="longitude" />
<input type="hidden" id="latitude" />
<div style="height: 600px" id="container"></div>
<script type="text/javascript">
var windowsArr = [];
var markers = [];
var map = new AMap.Map("container", {
resizeEnable: true,
center: [{{lat}}, {{lon}}], //地图中心点
zoom: 13, //地图显示的缩放级别
keyboardEnable: false,
});
var infoWindow;
function openInfo(name, addr, lng, lat) {
//构建信息窗体中显示的内容
var info = [];
info.push('<div class="uk-card uk-card-default uk-card-body">');
info.push('<a class="uk-card-badge uk-label" onClick="javascript:infoWindow.close()" uk-close></a>');
info.push("<h3 style=\"padding-top: 10px;\" class=\"uk-card-title\">" + name + "</h3>");
info.push("<p>" + addr + "</p>");
info.push('<div class="uk-card-footer">');
info.push('<form name="navForm" method="post">');
info.push(' <input type="hidden" name="lat" value="' + lat + '">');
info.push(' <input type="hidden" name="lon" value="' + lng + '">');
info.push(' <input type="hidden" name="save_type" value="' + document.getElementById("save_type").value + '">');
info.push(' <input type="hidden" name="name" value="' + name + '">');
info.push(' <input class="uk-button uk-button-primary" type="submit" value="导航" >');
info.push('</form>');
info.push('</div>');
info.push("</div>");
var pos = new AMap.LngLat(lng, lat)
infoWindow = new AMap.InfoWindow({
position: pos,
isCustom: true,
offset: new AMap.Pixel(0, -30),
content: info.join(""), //使用默认信息窗体框样式,显示信息内容
});
infoWindow.open(map, pos);
}
AMap.plugin(["AMap.Autocomplete", "AMap.PlaceSearch"], function () {
var autoOptions = {
city: "全国", //城市,默认全国
input: "keyword", //使用联想输入的input的id
};
autocomplete = new AMap.Autocomplete(autoOptions);
var placeSearch = new AMap.PlaceSearch({
map: "",
});
AMap.event.addListener(autocomplete, "select", function (e) {
//TODO 针对选中的poi实现自己的功能
//重寫搜尋點及其提示資訊begin=====
placeSearch.setCity(e.poi.adcode);
if (e.poi && e.poi.location) {
map.setZoom(17);
map.setCenter(e.poi.location);
}
placeSearch.search(e.poi.name, check_dest); //關鍵字查詢查詢
function check_dest(status, result) {
if (status === "complete" && result.info === "OK") {
for (var h = 0; h < result.poiList.pois.length; h++) {
//返回搜尋列表迴圈繫結marker
var jy = result.poiList.pois[h]["location"]; //經緯度
var name = result.poiList.pois[h]["name"]; //地址
marker = new AMap.Marker({
//加點
map: map,
position: jy,
});
marker.extData = {
getLng: jy["lng"],
getLat: jy["lat"],
name: name,
address: result.poiList.pois[h]["address"],
}; //自定義想傳入的引數
marker.on("click", function (e) {
var hs = e.target.extData;
var content = openInfo(
hs["name"],
hs["address"],
hs["getLng"],
hs["getLat"]
);
});
markers.push(marker);
}
}
}
//重寫搜尋點及其提示資訊end=====
});
});
var clickEventListener = map.on('click', function(e) {
map.remove(markers);
document.getElementById('longitude').value = e.lnglat.getLng();
document.getElementById('latitude').value = e.lnglat.getLat();
lnglatXY = [e.lnglat.getLng(), e.lnglat.getLat()];
var marker = new AMap.Marker({
//加點
map: map,
position: lnglatXY,
});
marker.extData = {
getLng: e.lnglat.getLng(),
getLat: e.lnglat.getLat(),
}; //自定義想傳入的引數
marker.on("click", function (e) {
var hs = e.target.extData;
var content = openInfo(
"",
"(" + hs["getLat"] + ", " + hs["getLng"] + ")",
hs["getLng"],
hs["getLat"]
);
});
markers.push(marker);
if (typeof(infoWindow) != "undefined") {
infoWindow.close();
}
});
</script>
{% endblock %}
@@ -0,0 +1,20 @@
{% extends "layout.html" %}
{% block title %}
amap_key_input
{% endblock %}
{% block main %}
<form name="setAmapTokenForm" method="post">
<fieldset class="uk-fieldset">
<legend class="uk-legend">请输入您的高德地图 API KEY</legend>
<div style="color: red">因系统升级,若于 2021/12/02 前申请 key 的人请重新申请新的「<b>key</b>」和「<b>安全密钥</b>」配对。</div>
<div class="uk-margin">
<input class="uk-input" type="text" name="amap_key_val" placeholder="KEY">
<input class="uk-input" type="text" name="amap_key_val_2" placeholder="安全密钥">
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="设置">
</div>
</fieldset>
</form>
{% endblock %}
@@ -0,0 +1,19 @@
{% extends "layout.html" %}
{% block title %}
MapBox key 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 %}
@@ -0,0 +1,11 @@
{% extends "layout.html" %}
{% block title %}
Error
{% endblock %}
{% block main %}
<br> Oops
<br><br><br><br>
{{ error | safe }}
{% endblock %}
@@ -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 %}
@@ -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 %}
@@ -0,0 +1,28 @@
{% extends "layout.html" %}
{% block title %}
Dashcam Routes
{% endblock %}
{% block main %}
<br>
<h1>Dashcam Routes</h1>
<br>
<div class="row">
{% for row, gif in zipped %}
<div class="col-xs-6 col-sm-4 col-md-3">
<div class="card mb-4 shadow-sm" style="background-color: #212529; color: white;">
<img src="/previewgif/{{ gif }}" class="card-img-top" alt="GIF">
<div class="card-body">
<p class="card-text">{{ row }}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<a href="/footage/{{ row }}" class="btn btn-sm btn-outline-secondary">View Footage</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
@@ -0,0 +1,18 @@
{% extends "layout.html" %}
{% block title %}
GMap key 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 %}
@@ -0,0 +1,16 @@
{% 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='/preserved'>Access Preserved 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='/about'>About Fleet Manager</a><br>
{% endblock %}
@@ -0,0 +1,103 @@
<!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>
<li class="nav-item active">
<a href="/tools" class="nav-link">Tools</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 %}
Nav Search Confirmation
{% 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 %}
@@ -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 %}
@@ -0,0 +1,13 @@
{% extends "layout.html" %}
{% block title %}
Nav Driving Directions
{% endblock %}
{% block main %}
{% with gmap_key=gmap_key, lon=lon, lat=lat, home=home, work=work, fav1=fav1, fav2=fav2, fav3=fav3 %}
{% include "addr_input.html" %}
{% endwith %}
{% include "nav_directions.html" %}
{% endblock %}
@@ -0,0 +1,30 @@
{% extends "layout.html" %}
{% block title %}
Preserved Routes
{% endblock %}
{% block main %}
<br>
<h1>Preserved Routes</h1>
<br>
<div class="row">
{% for route_path, gif_path, segment in zipped %}
<div class="col-xs-6 col-sm-4 col-md-3">
<div class="card mb-4 shadow-sm" style="background-color: #212529; color: white;">
<div class="gif-container">
<img src="/previewgif/{{ gif_path }}" class="card-img-top static-gif" alt="GIF">
</div>
<div class="card-body">
<p class="card-text">{{ segment }}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<a href="/footage/{{ route_path }}" class="btn btn-sm btn-outline-secondary">View Footage</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
@@ -0,0 +1,9 @@
{% extends "layout.html" %}
{% block title %}
Driving Directions
{% 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 %}
@@ -0,0 +1,68 @@
{% 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 }}?{{ query_segment }},qcamera">qcamera</a> -
<a href="{{ route }}?{{ query_segment }},fcamera">fcamera</a> -
<a href="{{ route }}?{{ query_segment }},dcamera">dcamera</a> -
<a href="{{ route }}?{{ query_segment }},ecamera">ecamera</a>
<br><br>
{{ links }}
<script>
var video = document.getElementById('video');
var segments = [{{ segments }}];
var q_segment = {{ query_segment }};
var q_index = 0;
for (var i = 0; i < segments.length; i++) {
var segment = segments[i];
var q_val = segment.split('--').slice(2).join('--');
if (parseInt(q_val) === q_segment) {
q_index = i;
break
}
}
var tracks = {
list: segments,
index: q_index,
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 %}
@@ -0,0 +1,226 @@
{% extends "layout.html" %}
{% block title %}
Tools
{% endblock %}
{% block main %}
<style>
button {
margin-top: 10px;
font-size: 1em;
padding: 8px 16px;
}
.toggle_values {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
flex-wrap: wrap;
}
.toggle-values-btn {
}
.textarea-container {
width: 80%;
margin-top: 20px;
}
textarea {
width: 100%;
height: 200px;
}
.separator {
width: 100%;
border-top: 1px solid #ccc;
margin: 10px 0;
}
.door-control {
width: 80%;
text-align: center;
}
.door-indicator {
font-size: 2em;
font-weight: normal;
margin-bottom: 10px;
}
.toggle_values h1 {
font-size: 2em;
font-weight: normal;
margin-bottom: 10px;
}
.door-btn {
}
</style>
<div class="toggle_values">
<div class="door-control">
<div class="door-indicator">
Lock/Unlock Doors (Toyota/Lexus Only)
</div>
<button type="button" id="lockButton" class="door-btn">
Lock Doors
</button>
<button type="button" id="unlockButton" class="door-btn">
Unlock Doors
</button>
</div>
<div class="separator"></div>
<h1>Backup/Restore Toggle Values</h1>
<div class="textarea-container">
<textarea id="toggleValuesBox" placeholder="Retrieve or restore toggle values."></textarea>
<button type="button" id="retrieveButton" class="toggle-values-btn">
Retrieve Toggle Values
</button>
<button type="button" id="restoreButton" class="toggle-values-btn">
Submit Toggle Values
</button>
</div>
<div class="separator"></div>
<h1>Reset Toggle Values</h1>
<div class="textarea-container">
<button type="button" id="resetButton" class="toggle-values-btn">
Reset Toggle Values
</button>
</div>
<div class="separator"></div>
<h1>Reboot Device</h1>
<div class="textarea-container">
<button type="button" id="rebootButton" class="toggle-values-btn">
Reboot Device
</button>
</div>
</div>
<script>
const toggleValuesBox = document.getElementById('toggleValuesBox');
const rebootButton = document.getElementById('rebootButton');
const retrieveButton = document.getElementById('retrieveButton');
const resetButton = document.getElementById('resetButton');
const restoreButton = document.getElementById('restoreButton');
const lockButton = document.getElementById('lockButton');
const unlockButton = document.getElementById('unlockButton');
rebootButton.addEventListener('click', () => {
fetch('/reboot_device', {
method: 'POST'
})
.then(response =>
response.json().then(data => ({ status: response.status, body: data }))
)
.then(result => {
if (result.status === 200) {
alert(result.body.message);
} else {
alert('Error rebooting device: ' + result.body.error);
}
})
.catch(error =>
console.error('Error rebooting device:', error)
);
});
retrieveButton.addEventListener('click', () => {
fetch('/get_toggle_values')
.then(response => response.text())
.then(data => {
toggleValuesBox.value = data.trim();
})
.catch(error =>
console.error('Error fetching toggle values:', error)
);
});
resetButton.addEventListener('click', () => {
fetch('/reset_toggle_values', {
method: 'POST'
})
.then(response =>
response.json().then(data => ({ status: response.status, body: data }))
)
.then(result => {
if (result.status === 200) {
alert(result.body.message);
} else {
alert('Error resetting toggle values: ' + result.body.error);
}
})
.catch(error =>
console.error('Error resetting toggle values:', error)
);
});
restoreButton.addEventListener('click', () => {
const inputData = toggleValuesBox.value;
fetch('/store_toggle_values', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: inputData.trim() })
})
.then(response =>
response.json().then(data => ({ status: response.status, body: data }))
)
.then(result => {
if (result.status === 200) {
alert('Values stored successfully.');
} else {
alert('Error storing values: ' + result.body.error);
}
})
.catch(error =>
console.error('Error storing toggle values:', error)
);
});
lockButton.addEventListener('click', () => {
fetch('/lock_doors', {
method: 'POST'
})
.then(response =>
response.json().then(data => ({ status: response.status, body: data }))
)
.then(result => {
if (result.status === 200) {
alert(result.body.message);
} else {
alert('Error locking doors: ' + result.body.error);
}
})
.catch(error =>
console.error('Error locking doors:', error)
);
});
unlockButton.addEventListener('click', () => {
fetch('/unlock_doors', {
method: 'POST'
})
.then(response =>
response.json().then(data => ({ status: response.status, body: data }))
)
.then(result => {
if (result.status === 200) {
alert(result.body.message);
} else {
alert('Error unlocking doors: ' + result.body.error);
}
})
.catch(error =>
console.error('Error unlocking doors:', error)
);
});
</script>
{% endblock %}