forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Release Commit @ 5bc9cd3
This commit is contained in:
186
selfdrive/ui/tests/test_ui/nav_demo_capture.py
Normal file
186
selfdrive/ui/tests/test_ui/nav_demo_capture.py
Normal file
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import importlib
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.test.helpers import with_processes
|
||||
|
||||
TEST_DIR = pathlib.Path(__file__).parent
|
||||
OUTPUT_DIR = TEST_DIR / "nav_demo_report"
|
||||
UI_DELAY = float(os.getenv("IQPILOT_NAV_DEMO_UI_DELAY", "0.75"))
|
||||
VERSION = "0.10.1 / nav-ui-demo / 7864838 / Mar 09"
|
||||
SCENE_SETTLE_S = float(os.getenv("IQPILOT_NAV_DEMO_SCENE_SETTLE_S", "0.04"))
|
||||
SEED_REPEATS = int(os.getenv("IQPILOT_NAV_DEMO_SEED_REPEATS", "2"))
|
||||
SEED_DELAY_S = float(os.getenv("IQPILOT_NAV_DEMO_SEED_DELAY_S", "0.02"))
|
||||
SEED_REFRESH_EVERY = int(os.getenv("IQPILOT_NAV_DEMO_SEED_REFRESH_EVERY", "24"))
|
||||
NAV_REPEATS = int(os.getenv("IQPILOT_NAV_DEMO_NAV_REPEATS", "2"))
|
||||
NAV_DELAY_S = float(os.getenv("IQPILOT_NAV_DEMO_NAV_DELAY_S", "0.02"))
|
||||
SEED_PUBLISH_DURATION_S = SEED_REPEATS * SEED_DELAY_S
|
||||
NAV_SCENE_DURATION_S = NAV_REPEATS * NAV_DELAY_S
|
||||
|
||||
NAV_SCENES = ()
|
||||
NAV_TIMELINE = ()
|
||||
build_ui_pubmaster = None
|
||||
publish_nav_scene = None
|
||||
publish_onroad_seed = None
|
||||
seed_ui_test_params = None
|
||||
|
||||
|
||||
class NavDemoCapture:
|
||||
def __init__(self, output_dir: pathlib.Path):
|
||||
os.environ["SCALE"] = os.getenv("SCALE", "1")
|
||||
os.environ["BIG"] = "1"
|
||||
os.environ["RECORD"] = "1"
|
||||
os.environ["RECORD_OUTPUT"] = str(output_dir / "nav_demo")
|
||||
sys.modules["mouseinfo"] = False
|
||||
self.output_dir = output_dir
|
||||
self.pm = build_ui_pubmaster()
|
||||
self.frames = []
|
||||
self._image_lib = None
|
||||
self.video_path = self.output_dir / "nav_demo.mp4"
|
||||
|
||||
def _load_image_lib(self):
|
||||
if self._image_lib is not None:
|
||||
return self._image_lib
|
||||
try:
|
||||
self._image_lib = importlib.import_module("PIL.Image")
|
||||
except ModuleNotFoundError:
|
||||
self._image_lib = None
|
||||
return self._image_lib
|
||||
|
||||
def setup(self):
|
||||
publish_onroad_seed(self.pm)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
@with_processes(["ui"])
|
||||
def run(self):
|
||||
self.setup()
|
||||
for idx, scene in enumerate(NAV_TIMELINE):
|
||||
if idx % SEED_REFRESH_EVERY == 0:
|
||||
publish_onroad_seed(self.pm, repeats=SEED_REPEATS, delay=SEED_DELAY_S)
|
||||
publish_nav_scene(self.pm, scene, repeats=NAV_REPEATS, delay=NAV_DELAY_S)
|
||||
time.sleep(SCENE_SETTLE_S)
|
||||
|
||||
def extract_video_stills(self) -> list[pathlib.Path]:
|
||||
if not self.video_path.exists():
|
||||
return []
|
||||
|
||||
extracted = []
|
||||
current_ts = UI_DELAY
|
||||
for idx, scene in enumerate(NAV_TIMELINE):
|
||||
if idx % SEED_REFRESH_EVERY == 0:
|
||||
current_ts += SEED_PUBLISH_DURATION_S
|
||||
capture_ts = current_ts + (NAV_SCENE_DURATION_S * 0.6)
|
||||
capture_name = scene.get("capture_name")
|
||||
if capture_name:
|
||||
output_path = self.output_dir / f"{capture_name}.png"
|
||||
subprocess.run(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-loglevel", "error",
|
||||
"-ss", f"{capture_ts:.2f}",
|
||||
"-i", str(self.video_path),
|
||||
"-frames:v", "1",
|
||||
str(output_path),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
extracted.append(output_path)
|
||||
current_ts += NAV_SCENE_DURATION_S + SCENE_SETTLE_S
|
||||
return extracted
|
||||
|
||||
def write_gif(self) -> pathlib.Path | None:
|
||||
if not self.frames:
|
||||
for scene in NAV_SCENES:
|
||||
image_path = self.output_dir / f"{scene['name']}.png"
|
||||
if image_path.exists():
|
||||
self.frames.append(self._load_image_lib().open(image_path).copy())
|
||||
|
||||
if not self.frames:
|
||||
return None
|
||||
if self._load_image_lib() is None:
|
||||
return None
|
||||
gif_path = self.output_dir / "nav_demo.gif"
|
||||
self.frames[0].save(
|
||||
gif_path,
|
||||
save_all=True,
|
||||
append_images=self.frames[1:],
|
||||
duration=900,
|
||||
loop=0,
|
||||
)
|
||||
return gif_path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Run BIG raylib UI with a hard-coded nav route and capture screenshots/GIF.")
|
||||
parser.add_argument("--output-dir", type=pathlib.Path, default=OUTPUT_DIR, help="Directory for screenshots and gif.")
|
||||
parser.add_argument("--no-gif", action="store_true", help="Skip animated GIF creation.")
|
||||
parser.add_argument("--mapbox-token", default="", help="Mapbox token for the demo prefix. Falls back to MAPBOX_TOKEN if omitted.")
|
||||
parser.add_argument("--no-mapbox", action="store_true", help="Disable Mapbox for the demo and use only cached/offline tiles.")
|
||||
parser.add_argument("--force-local-offline", action="store_true", help="Bypass both live and cached Mapbox so only the local offline provider can render.")
|
||||
parser.add_argument("--offline-mbtiles", type=pathlib.Path, default=None, help="Path to a local raster MBTiles file for offline rendering.")
|
||||
parser.add_argument("--offline-tile-root", type=pathlib.Path, default=None, help="Path to a local XYZ raster tile directory for offline rendering.")
|
||||
parser.add_argument("--fixture", type=pathlib.Path, default=None, help="Route fixture JSON to drive the nav demo.")
|
||||
parser.add_argument("--osmaps-mode", choices=("online", "offline", "both"), default=None,
|
||||
help="Set the OnlineOSMaps/OfflineOSMaps params (the production source selection) for the demo prefix.")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.output_dir.exists():
|
||||
shutil.rmtree(args.output_dir)
|
||||
args.output_dir.mkdir(parents=True)
|
||||
|
||||
mapbox_token = args.mapbox_token
|
||||
if not mapbox_token and not args.no_mapbox:
|
||||
existing = Params().get("MapboxToken")
|
||||
if isinstance(existing, bytes):
|
||||
existing = existing.decode("utf-8")
|
||||
mapbox_token = existing or ""
|
||||
|
||||
with OpenpilotPrefix():
|
||||
if args.fixture is not None:
|
||||
os.environ["IQPILOT_NAV_DEMO_FIXTURE"] = str(args.fixture)
|
||||
if args.force_local_offline:
|
||||
os.environ["IQPILOT_DISABLE_MAPBOX_PROVIDER"] = "1"
|
||||
os.environ["IQPILOT_DISABLE_MAPBOX_CACHE"] = "1"
|
||||
elif args.no_mapbox:
|
||||
os.environ.pop("IQPILOT_DISABLE_MAPBOX_PROVIDER", None)
|
||||
os.environ.pop("IQPILOT_DISABLE_MAPBOX_CACHE", None)
|
||||
if args.offline_mbtiles is not None:
|
||||
os.environ["IQPILOT_OFFLINE_MBTILES"] = str(args.offline_mbtiles)
|
||||
if args.offline_tile_root is not None:
|
||||
os.environ["IQPILOT_OFFLINE_TILE_ROOT"] = str(args.offline_tile_root)
|
||||
global NAV_SCENES, NAV_TIMELINE, build_ui_pubmaster, publish_nav_scene, publish_onroad_seed, seed_ui_test_params
|
||||
nav_demo_common = importlib.import_module("openpilot.selfdrive.ui.tests.test_ui.nav_demo_common")
|
||||
NAV_SCENES = nav_demo_common.NAV_SCENES
|
||||
NAV_TIMELINE = nav_demo_common.NAV_TIMELINE
|
||||
build_ui_pubmaster = nav_demo_common.build_ui_pubmaster
|
||||
publish_nav_scene = nav_demo_common.publish_nav_scene
|
||||
publish_onroad_seed = nav_demo_common.publish_onroad_seed
|
||||
seed_ui_test_params = nav_demo_common.seed_ui_test_params
|
||||
seed_ui_test_params(Params(), VERSION, mapbox_token=mapbox_token)
|
||||
if args.osmaps_mode is not None:
|
||||
demo_params = Params()
|
||||
demo_params.put_bool("OnlineOSMaps", args.osmaps_mode in ("online", "both"))
|
||||
demo_params.put_bool("OfflineOSMaps", args.osmaps_mode in ("offline", "both"))
|
||||
demo = NavDemoCapture(args.output_dir)
|
||||
demo.run()
|
||||
demo.extract_video_stills()
|
||||
gif_path = None if args.no_gif else demo.write_gif()
|
||||
|
||||
print(f"Screenshots written to: {args.output_dir}")
|
||||
if demo.video_path.exists():
|
||||
print(f"Recorded preview written to: {demo.video_path}")
|
||||
if gif_path is not None:
|
||||
print(f"Animated preview written to: {gif_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
705
selfdrive/ui/tests/test_ui/nav_demo_common.py
Normal file
705
selfdrive/ui/tests/test_ui/nav_demo_common.py
Normal file
@@ -0,0 +1,705 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from math import atan2, cos, radians, sqrt
|
||||
from pathlib import Path
|
||||
|
||||
from cereal import car, custom, log, messaging
|
||||
from cereal.messaging import PubMaster
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.updated.updated import parse_release_notes
|
||||
from openpilot.system.version import terms_version, training_version
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
DEFAULT_DEMO_FIXTURE_PATH = Path(__file__).with_name("nav_demo_fixture_bolingbrook_mapbox.json")
|
||||
DEMO_ROUTE_STEP_M_MIN = 12.0
|
||||
TARGET_TIMELINE_SCENES = 180
|
||||
|
||||
|
||||
def _load_demo_fixture() -> dict:
|
||||
fixture_path = Path(os.getenv("IQPILOT_NAV_DEMO_FIXTURE", str(DEFAULT_DEMO_FIXTURE_PATH)))
|
||||
return json.loads(fixture_path.read_text())
|
||||
|
||||
|
||||
def _lerp(a: float, b: float, t: float) -> float:
|
||||
return a + (b - a) * t
|
||||
|
||||
|
||||
def _segment_length_m(a: tuple[float, float], b: tuple[float, float]) -> float:
|
||||
lat_scale = 111_320.0
|
||||
lon_scale = 111_320.0 * cos(radians((a[0] + b[0]) * 0.5))
|
||||
dx = (b[1] - a[1]) * lon_scale
|
||||
dy = (b[0] - a[0]) * lat_scale
|
||||
return sqrt(dx * dx + dy * dy)
|
||||
|
||||
|
||||
def _route_length_m(points: list[tuple[float, float]]) -> float:
|
||||
return sum(_segment_length_m(points[idx], points[idx + 1]) for idx in range(len(points) - 1))
|
||||
|
||||
|
||||
def _cumulative_distances(points: list[tuple[float, float]]) -> list[float]:
|
||||
out = [0.0]
|
||||
for idx in range(len(points) - 1):
|
||||
out.append(out[-1] + _segment_length_m(points[idx], points[idx + 1]))
|
||||
return out
|
||||
|
||||
|
||||
def _interpolate_along(points: list[tuple[float, float]], distance_m: float) -> tuple[float, float]:
|
||||
if len(points) < 2:
|
||||
return points[0]
|
||||
|
||||
remaining = max(distance_m, 0.0)
|
||||
for idx in range(len(points) - 1):
|
||||
a, b = points[idx], points[idx + 1]
|
||||
seg_len = _segment_length_m(a, b)
|
||||
if remaining <= seg_len:
|
||||
t = 0.0 if seg_len < 1e-3 else remaining / seg_len
|
||||
return _lerp(a[0], b[0], t), _lerp(a[1], b[1], t)
|
||||
remaining -= seg_len
|
||||
|
||||
return points[-1]
|
||||
|
||||
|
||||
def _bearing_between(a: tuple[float, float], b: tuple[float, float]) -> float:
|
||||
lon_scale = cos(radians((a[0] + b[0]) * 0.5))
|
||||
dx = (b[1] - a[1]) * lon_scale
|
||||
dy = b[0] - a[0]
|
||||
return (90.0 - (180.0 / 3.141592653589793) * atan2(dy, dx)) % 360.0
|
||||
|
||||
|
||||
def _slice_route_ahead(points: list[tuple[float, float]], current_idx: int, lookbehind: int = 1) -> list[tuple[float, float]]:
|
||||
start = max(current_idx - lookbehind, 0)
|
||||
return points[start:]
|
||||
|
||||
|
||||
def _decode_polyline6(polyline: str) -> list[tuple[float, float]]:
|
||||
if not polyline:
|
||||
return []
|
||||
|
||||
points = []
|
||||
index = 0
|
||||
lat = 0
|
||||
lon = 0
|
||||
|
||||
while index < len(polyline):
|
||||
result = 0
|
||||
shift = 0
|
||||
while True:
|
||||
byte = ord(polyline[index]) - 63
|
||||
index += 1
|
||||
result |= (byte & 0x1F) << shift
|
||||
shift += 5
|
||||
if byte < 0x20:
|
||||
break
|
||||
lat += ~(result >> 1) if result & 1 else (result >> 1)
|
||||
|
||||
result = 0
|
||||
shift = 0
|
||||
while True:
|
||||
byte = ord(polyline[index]) - 63
|
||||
index += 1
|
||||
result |= (byte & 0x1F) << shift
|
||||
shift += 5
|
||||
if byte < 0x20:
|
||||
break
|
||||
lon += ~(result >> 1) if result & 1 else (result >> 1)
|
||||
points.append((lat / 1_000_000.0, lon / 1_000_000.0))
|
||||
|
||||
return points
|
||||
|
||||
def _modifier_to_direction(modifier: str | None) -> int:
|
||||
modifier = (modifier or "").lower()
|
||||
if "left" in modifier:
|
||||
return custom.NavDirection.left
|
||||
if "right" in modifier:
|
||||
return custom.NavDirection.right
|
||||
return custom.NavDirection.none
|
||||
|
||||
|
||||
def _modifier_to_turn_direction(modifier: str | None) -> int:
|
||||
modifier = (modifier or "").lower()
|
||||
if "left" in modifier:
|
||||
return custom.IQTurnSignalDirection.turnLeft
|
||||
if "right" in modifier:
|
||||
return custom.IQTurnSignalDirection.turnRight
|
||||
return custom.IQTurnSignalDirection.none
|
||||
|
||||
|
||||
def _valhalla_modifier_from_type(type_code: int) -> str:
|
||||
mapping = {
|
||||
9: "slight_right",
|
||||
10: "right",
|
||||
11: "sharp_right",
|
||||
12: "uturn_right",
|
||||
13: "uturn_left",
|
||||
14: "sharp_left",
|
||||
15: "left",
|
||||
16: "slight_left",
|
||||
17: "straight",
|
||||
18: "right",
|
||||
19: "left",
|
||||
20: "straight",
|
||||
21: "roundabout",
|
||||
22: "roundabout",
|
||||
24: "right",
|
||||
25: "left",
|
||||
26: "straight",
|
||||
27: "straight",
|
||||
31: "straight",
|
||||
32: "right",
|
||||
33: "left",
|
||||
36: "straight",
|
||||
}
|
||||
return mapping.get(type_code, "straight")
|
||||
|
||||
|
||||
def _normalize_mapbox_fixture(data: dict) -> dict:
|
||||
route = data["routes"][0]
|
||||
leg = route["legs"][0]
|
||||
points = [(lat, lon) for lon, lat in route["geometry"]["coordinates"]]
|
||||
waypoint_entries = data.get("waypoints", [{}, {}])
|
||||
start_waypoint = waypoint_entries[0] if waypoint_entries else {}
|
||||
destination_location = data.get("waypoints", [{}, {}])[-1].get("location", route["geometry"]["coordinates"][-1])
|
||||
destination = (float(destination_location[1]), float(destination_location[0]))
|
||||
steps = []
|
||||
previous_name = os.getenv("IQPILOT_NAV_DEMO_START_NAME", "185 Brandon Ct")
|
||||
for step in leg["steps"]:
|
||||
maneuver = step["maneuver"]
|
||||
name = step["name"] or previous_name
|
||||
if step["name"]:
|
||||
previous_name = step["name"]
|
||||
steps.append({
|
||||
"name": name,
|
||||
"banner_name": step["name"],
|
||||
"location": (float(maneuver["location"][1]), float(maneuver["location"][0])),
|
||||
"raw_type": maneuver.get("type", "none"),
|
||||
"modifier": maneuver.get("modifier", "straight") or "straight",
|
||||
"description": maneuver.get("instruction") or name or "Continue",
|
||||
"distance": float(step["distance"]),
|
||||
"duration": float(step["duration"]),
|
||||
})
|
||||
return {
|
||||
"provider": "mapbox",
|
||||
"route_points": points,
|
||||
"duration_s": float(route["duration"]),
|
||||
"distance_m": float(route["distance"]),
|
||||
"steps": steps,
|
||||
"start_name": os.getenv("IQPILOT_NAV_DEMO_START_NAME", start_waypoint.get("name") or "Start"),
|
||||
"destination_name": os.getenv("IQPILOT_NAV_DEMO_DESTINATION_NAME", waypoint_entries[-1].get("name") or "Destination"),
|
||||
"destination": destination,
|
||||
}
|
||||
|
||||
|
||||
def _normalize_valhalla_fixture(data: dict) -> dict:
|
||||
trip = data["trip"]
|
||||
leg = trip["legs"][0]
|
||||
points = _decode_polyline6(leg["shape"])
|
||||
start_loc = trip["locations"][0]
|
||||
destination_loc = trip["locations"][-1]
|
||||
destination = (float(destination_loc["lat"]), float(destination_loc["lon"]))
|
||||
steps = []
|
||||
previous_name = os.getenv("IQPILOT_NAV_DEMO_START_NAME", "185 Brandon Ct")
|
||||
for maneuver in leg["maneuvers"]:
|
||||
type_code = int(maneuver.get("type", 8) or 8)
|
||||
modifier = _valhalla_modifier_from_type(type_code)
|
||||
begin_idx = min(max(int(maneuver.get("begin_shape_index", 0) or 0), 0), len(points) - 1)
|
||||
street_names = maneuver.get("street_names") or []
|
||||
banner_name = street_names[0] if street_names else ""
|
||||
name = banner_name or previous_name
|
||||
if banner_name:
|
||||
previous_name = banner_name
|
||||
steps.append({
|
||||
"name": name,
|
||||
"banner_name": banner_name,
|
||||
"location": points[begin_idx],
|
||||
"raw_type": f"valhalla:{type_code}",
|
||||
"modifier": modifier,
|
||||
"description": maneuver.get("instruction") or name or "Continue",
|
||||
"distance": float(maneuver.get("length", 0.0) or 0.0) * 1000.0,
|
||||
"duration": float(maneuver.get("time", 0.0) or 0.0),
|
||||
"type_code": type_code,
|
||||
})
|
||||
return {
|
||||
"provider": "valhalla",
|
||||
"route_points": points,
|
||||
"duration_s": float(trip["summary"]["time"]),
|
||||
"distance_m": float(trip["summary"]["length"]) * 1000.0,
|
||||
"steps": steps,
|
||||
"start_name": os.getenv("IQPILOT_NAV_DEMO_START_NAME", start_loc.get("name") or "Start"),
|
||||
"destination_name": os.getenv("IQPILOT_NAV_DEMO_DESTINATION_NAME", destination_loc.get("name") or "Destination"),
|
||||
"destination": destination,
|
||||
}
|
||||
|
||||
|
||||
def _normalize_demo_fixture(data: dict) -> dict:
|
||||
if "routes" in data:
|
||||
return _normalize_mapbox_fixture(data)
|
||||
if "trip" in data:
|
||||
return _normalize_valhalla_fixture(data)
|
||||
raise ValueError("Unsupported nav demo fixture format")
|
||||
|
||||
|
||||
def _map_step_type(step_type: str, modifier: str | None, type_code: int | None = None) -> int:
|
||||
if type_code is not None:
|
||||
if type_code in {4, 5, 6}:
|
||||
return custom.IQNavState.ManeuverType.arrive
|
||||
if type_code in {21, 22, 23, 37}:
|
||||
return custom.IQNavState.ManeuverType.roundabout
|
||||
if type_code in {18, 19, 24, 25}:
|
||||
return custom.IQNavState.ManeuverType.exit
|
||||
if type_code in {17, 20, 26, 27}:
|
||||
return custom.IQNavState.ManeuverType.merge
|
||||
if type_code in {1, 2, 3, 7, 8, 31, 36}:
|
||||
return custom.IQNavState.ManeuverType.continueStraight
|
||||
return custom.IQNavState.ManeuverType.turn
|
||||
step_type = step_type or "none"
|
||||
modifier = (modifier or "").lower()
|
||||
if step_type == "arrive":
|
||||
return custom.IQNavState.ManeuverType.arrive
|
||||
if step_type in {"off ramp"}:
|
||||
return custom.IQNavState.ManeuverType.exit
|
||||
if step_type in {"merge", "on ramp"}:
|
||||
return custom.IQNavState.ManeuverType.merge
|
||||
if step_type in {"fork"}:
|
||||
return custom.IQNavState.ManeuverType.fork
|
||||
if step_type in {"roundabout", "rotary", "roundabout turn"}:
|
||||
return custom.IQNavState.ManeuverType.roundabout
|
||||
if step_type in {"continue", "new name", "depart", "notification"}:
|
||||
return custom.IQNavState.ManeuverType.continueStraight
|
||||
if step_type in {"turn", "end of road"}:
|
||||
return custom.IQNavState.ManeuverType.turn
|
||||
if modifier == "straight":
|
||||
return custom.IQNavState.ManeuverType.continueStraight
|
||||
return custom.IQNavState.ManeuverType.turn
|
||||
|
||||
|
||||
def _nearest_route_index(points: list[tuple[float, float]], target: tuple[float, float]) -> int:
|
||||
best_idx = 0
|
||||
best_distance = None
|
||||
for idx, point in enumerate(points):
|
||||
d = _segment_length_m(point, target)
|
||||
if best_distance is None or d < best_distance:
|
||||
best_distance = d
|
||||
best_idx = idx
|
||||
return best_idx
|
||||
|
||||
|
||||
DEMO_FIXTURE = _load_demo_fixture()
|
||||
DEMO_CONTEXT = _normalize_demo_fixture(DEMO_FIXTURE)
|
||||
DEMO_START_NAME = DEMO_CONTEXT["start_name"]
|
||||
DEMO_DESTINATION_NAME = DEMO_CONTEXT["destination_name"]
|
||||
DEMO_DESTINATION = DEMO_CONTEXT["destination"]
|
||||
DEMO_ROUTE_POINTS = DEMO_CONTEXT["route_points"]
|
||||
ROUTE_DISTANCES = _cumulative_distances(DEMO_ROUTE_POINTS)
|
||||
ROUTE_TOTAL_DISTANCE_M = ROUTE_DISTANCES[-1]
|
||||
DEMO_DURATION_S = float(DEMO_CONTEXT["duration_s"])
|
||||
DEMO_ROUTE_STEP_M = max(DEMO_ROUTE_STEP_M_MIN, ROUTE_TOTAL_DISTANCE_M / TARGET_TIMELINE_SCENES)
|
||||
|
||||
DEMO_STEPS = []
|
||||
previous_name = DEMO_START_NAME
|
||||
for idx, step in enumerate(DEMO_CONTEXT["steps"]):
|
||||
maneuver_location = step["location"]
|
||||
route_index = _nearest_route_index(DEMO_ROUTE_POINTS, maneuver_location)
|
||||
route_distance = ROUTE_DISTANCES[route_index]
|
||||
step_name = step["name"] or previous_name
|
||||
if step["name"]:
|
||||
previous_name = step["name"]
|
||||
step_type = _map_step_type(step.get("raw_type", "none"), step.get("modifier"), step.get("type_code"))
|
||||
direction = _modifier_to_direction(step.get("modifier"))
|
||||
step_speed = max(float(step["distance"]) / max(float(step["duration"]), 1.0), 3.5)
|
||||
DEMO_STEPS.append({
|
||||
"index": idx,
|
||||
"name": step_name,
|
||||
"banner_name": step.get("banner_name", ""),
|
||||
"route_index": route_index,
|
||||
"route_distance": route_distance,
|
||||
"type": step_type,
|
||||
"raw_type": step.get("raw_type", "none"),
|
||||
"modifier": step.get("modifier", "straight") or "straight",
|
||||
"direction": direction,
|
||||
"description": step.get("description") or step_name or "Continue",
|
||||
"distance": float(step["distance"]),
|
||||
"duration": float(step["duration"]),
|
||||
"speed": step_speed,
|
||||
"location": maneuver_location,
|
||||
})
|
||||
|
||||
DEMO_NAV_STEPS = tuple(step for step in DEMO_STEPS if step["raw_type"] not in {"depart", "valhalla:1"})
|
||||
DEMO_DESTINATION_ROUTE_POINT = DEMO_STEPS[-1]["location"]
|
||||
|
||||
|
||||
def _find_route_index_for_distance(distance_m: float) -> int:
|
||||
for idx, route_distance in enumerate(ROUTE_DISTANCES):
|
||||
if route_distance >= distance_m:
|
||||
return idx
|
||||
return len(ROUTE_DISTANCES) - 1
|
||||
|
||||
|
||||
def _find_upcoming_steps(distance_m: float) -> tuple[dict | None, dict | None]:
|
||||
upcoming = [step for step in DEMO_NAV_STEPS if step["route_distance"] > distance_m + 1e-3]
|
||||
first = upcoming[0] if upcoming else None
|
||||
second = upcoming[1] if len(upcoming) > 1 else None
|
||||
return first, second
|
||||
|
||||
|
||||
def _current_road_name(distance_m: float) -> str:
|
||||
current = DEMO_START_NAME
|
||||
for step in DEMO_STEPS:
|
||||
if step["route_distance"] <= distance_m and step["banner_name"]:
|
||||
current = step["banner_name"]
|
||||
return current
|
||||
|
||||
|
||||
def _phase_for_step(step: dict | None, distance_to_next: float) -> int:
|
||||
if step is None or step["type"] == custom.IQNavState.ManeuverType.arrive:
|
||||
return custom.IQNavState.ManeuverPhase.none
|
||||
if step["type"] in (custom.IQNavState.ManeuverType.exit, custom.IQNavState.ManeuverType.merge, custom.IQNavState.ManeuverType.fork):
|
||||
if distance_to_next <= 90.0:
|
||||
return custom.IQNavState.ManeuverPhase.highwayCommit
|
||||
if distance_to_next <= 260.0:
|
||||
return custom.IQNavState.ManeuverPhase.highwayPrepare
|
||||
return custom.IQNavState.ManeuverPhase.none
|
||||
if distance_to_next <= 45.0:
|
||||
return custom.IQNavState.ManeuverPhase.turnActive
|
||||
if distance_to_next <= 180.0:
|
||||
return custom.IQNavState.ManeuverPhase.turnPrepare
|
||||
return custom.IQNavState.ManeuverPhase.none
|
||||
|
||||
|
||||
def _zoom_for_distance(distance_to_next: float, maneuver_type: int) -> float:
|
||||
if maneuver_type == custom.IQNavState.ManeuverType.arrive:
|
||||
return 17.2
|
||||
if maneuver_type in (custom.IQNavState.ManeuverType.exit, custom.IQNavState.ManeuverType.merge, custom.IQNavState.ManeuverType.fork):
|
||||
if distance_to_next <= 120.0:
|
||||
return 16.9
|
||||
return 16.3
|
||||
if distance_to_next <= 70.0:
|
||||
return 17.1
|
||||
if distance_to_next <= 160.0:
|
||||
return 16.8
|
||||
return 16.4
|
||||
|
||||
|
||||
def _speed_target_for_step(current_step: dict | None, next_step: dict | None, distance_to_next: float) -> float:
|
||||
current_speed = current_step["speed"] if current_step is not None else 12.0
|
||||
next_speed = next_step["speed"] if next_step is not None else current_speed
|
||||
if next_step is not None and next_step["type"] == custom.IQNavState.ManeuverType.arrive:
|
||||
if distance_to_next <= 25.0:
|
||||
return 3.0
|
||||
if distance_to_next <= 80.0:
|
||||
return 4.5
|
||||
if distance_to_next <= 35.0:
|
||||
return max(next_speed * 0.85, 4.5)
|
||||
if distance_to_next <= 140.0:
|
||||
return max(min(current_speed, next_speed + 1.5), 6.0)
|
||||
return max(current_speed, 7.0)
|
||||
|
||||
|
||||
def _capture_distance_targets() -> dict[str, float]:
|
||||
captures = {}
|
||||
selected_steps = [step for step in DEMO_NAV_STEPS if step["type"] != custom.IQNavState.ManeuverType.continueStraight]
|
||||
for idx, step in enumerate(selected_steps[:4], start=1):
|
||||
captures[f"nav_step_{idx:02d}"] = max(step["route_distance"] - min(120.0, max(step["distance"] * 0.35, 45.0)), 0.0)
|
||||
captures["nav_arrival"] = max(DEMO_NAV_STEPS[-1]["route_distance"] - 35.0, 0.0)
|
||||
return captures
|
||||
|
||||
|
||||
def _timeline_distances() -> list[float]:
|
||||
base = [idx * DEMO_ROUTE_STEP_M for idx in range(int(ROUTE_TOTAL_DISTANCE_M // DEMO_ROUTE_STEP_M) + 1)]
|
||||
points = set(base)
|
||||
for step in DEMO_NAV_STEPS:
|
||||
for offset in (260.0, 180.0, 120.0, 80.0, 50.0, 25.0):
|
||||
if step["type"] == custom.IQNavState.ManeuverType.arrive and offset > 120.0:
|
||||
continue
|
||||
points.add(max(step["route_distance"] - offset, 0.0))
|
||||
points.add(ROUTE_TOTAL_DISTANCE_M - 15.0)
|
||||
points.add(ROUTE_TOTAL_DISTANCE_M - 5.0)
|
||||
return sorted(d for d in points if 0.0 <= d <= ROUTE_TOTAL_DISTANCE_M)
|
||||
|
||||
|
||||
def _make_timeline_scene(distance_m: float) -> dict:
|
||||
current_lat, current_lon = _interpolate_along(DEMO_ROUTE_POINTS, distance_m)
|
||||
next_lat, next_lon = _interpolate_along(DEMO_ROUTE_POINTS, min(distance_m + 18.0, ROUTE_TOTAL_DISTANCE_M))
|
||||
route_idx = _find_route_index_for_distance(distance_m)
|
||||
bearing = _bearing_between((current_lat, current_lon), (next_lat, next_lon))
|
||||
|
||||
current_step_idx = max(0, max((idx for idx, step in enumerate(DEMO_STEPS) if step["route_distance"] <= distance_m), default=0))
|
||||
current_step = DEMO_STEPS[current_step_idx]
|
||||
next_step, second_step = _find_upcoming_steps(distance_m)
|
||||
if next_step is None:
|
||||
next_step = DEMO_NAV_STEPS[-1]
|
||||
|
||||
next_distance = max(next_step["route_distance"] - distance_m, 0.0)
|
||||
remaining_distance = max(ROUTE_TOTAL_DISTANCE_M - distance_m, 0.0)
|
||||
remaining_time = max(DEMO_DURATION_S * (remaining_distance / max(ROUTE_TOTAL_DISTANCE_M, 1.0)), 10.0)
|
||||
|
||||
scene = {
|
||||
"name": f"nav_timeline_{int(distance_m):04d}",
|
||||
"capture_name": "",
|
||||
"road_name": _current_road_name(distance_m),
|
||||
"distance_m": next_distance,
|
||||
"time_remaining": remaining_time,
|
||||
"distance_remaining": remaining_distance,
|
||||
"speed_limit": max(current_step["speed"], 8.0),
|
||||
"speed_limit_ahead": max(next_step["speed"], 6.0),
|
||||
"speed_limit_ahead_distance": max(min(next_distance, 220.0), 0.0),
|
||||
"phase": _phase_for_step(next_step, next_distance),
|
||||
"direction": next_step["direction"],
|
||||
"next_type": next_step["type"],
|
||||
"next_modifier": next_step["modifier"],
|
||||
"next_description": next_step["description"],
|
||||
"second_type": second_step["type"] if second_step is not None else custom.IQNavState.ManeuverType.arrive,
|
||||
"second_direction": second_step["direction"] if second_step is not None else custom.NavDirection.none,
|
||||
"second_modifier": second_step["modifier"] if second_step is not None else "straight",
|
||||
"second_distance": max(second_step["route_distance"] - distance_m, 0.0) if second_step is not None else 0.0,
|
||||
"second_valid": second_step is not None,
|
||||
"provider": custom.IQNavState.LongitudinalProvider.route,
|
||||
"route_speed_target": _speed_target_for_step(current_step, next_step, next_distance),
|
||||
"current_latitude": current_lat,
|
||||
"current_longitude": current_lon,
|
||||
"bearing_deg": bearing,
|
||||
"zoom_hint": _zoom_for_distance(next_distance, next_step["type"]),
|
||||
"destination_latitude": DEMO_DESTINATION[0],
|
||||
"destination_longitude": DEMO_DESTINATION[1],
|
||||
"destination_name": DEMO_DESTINATION_NAME,
|
||||
"route_points": _slice_route_ahead(DEMO_ROUTE_POINTS, route_idx, lookbehind=1),
|
||||
"next_maneuver_latitude": next_step["location"][0],
|
||||
"next_maneuver_longitude": next_step["location"][1],
|
||||
}
|
||||
return scene
|
||||
|
||||
|
||||
CAPTURE_TARGETS = _capture_distance_targets()
|
||||
_timeline_scenes = [_make_timeline_scene(distance_m) for distance_m in _timeline_distances()]
|
||||
for capture_name, target_distance in CAPTURE_TARGETS.items():
|
||||
best_scene = min(
|
||||
_timeline_scenes,
|
||||
key=lambda scene: abs((ROUTE_TOTAL_DISTANCE_M - scene["distance_remaining"]) - target_distance),
|
||||
)
|
||||
best_scene["capture_name"] = capture_name
|
||||
NAV_TIMELINE = tuple(_timeline_scenes)
|
||||
NAV_SCENES = tuple(scene for scene in NAV_TIMELINE if scene.get("capture_name"))
|
||||
|
||||
|
||||
def seed_ui_test_params(params: Params, version: str, mapbox_token: str = "") -> None:
|
||||
params.put("DongleId", "123456789012345")
|
||||
params.put("UpdaterCurrentDescription", version)
|
||||
params.put("UpdaterNewDescription", version)
|
||||
params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR))
|
||||
params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR))
|
||||
params.put("HasAcceptedTerms", terms_version)
|
||||
params.put("CompletedTrainingVersion", training_version)
|
||||
params.put_bool("OnScreenNavigation", True)
|
||||
|
||||
cp = car.CarParams(notCar=True, wheelbase=2.7, steerRatio=15.0)
|
||||
cp.openpilotLongitudinalControl = True
|
||||
cp_bytes = cp.to_bytes()
|
||||
params.put("CarParamsPersistent", cp_bytes)
|
||||
params.put("CarParams", cp_bytes)
|
||||
|
||||
token = mapbox_token or os.getenv("MAPBOX_TOKEN", "")
|
||||
if token:
|
||||
params.put("MapboxToken", token)
|
||||
|
||||
|
||||
def build_ui_pubmaster() -> PubMaster:
|
||||
return PubMaster([
|
||||
"deviceState",
|
||||
"pandaStates",
|
||||
"driverStateV2",
|
||||
"selfdriveState",
|
||||
"carState",
|
||||
"carControl",
|
||||
"controlsState",
|
||||
"iqPlan",
|
||||
"iqLiveData",
|
||||
"iqNavState",
|
||||
"iqNavRenderState",
|
||||
"gpsLocationExternal",
|
||||
])
|
||||
|
||||
|
||||
def publish_onroad_seed(pm: PubMaster, repeats: int = 8, delay: float = 0.05) -> None:
|
||||
device_state = messaging.new_message("deviceState")
|
||||
device_state.deviceState.started = True
|
||||
device_state.deviceState.networkType = log.DeviceState.NetworkType.wifi
|
||||
device_state.deviceState.deviceType = HARDWARE.get_device_type()
|
||||
|
||||
panda_states = messaging.new_message("pandaStates", 1)
|
||||
panda_states.pandaStates[0].pandaType = log.PandaState.PandaType.dos
|
||||
panda_states.pandaStates[0].ignitionLine = True
|
||||
|
||||
driver_state = messaging.new_message("driverStateV2")
|
||||
driver_state.driverStateV2.leftDriverData.faceOrientation = [0.0, 0.0, 0.0]
|
||||
|
||||
selfdrive_state = messaging.new_message("selfdriveState")
|
||||
selfdrive_state.selfdriveState.enabled = True
|
||||
selfdrive_state.selfdriveState.state = log.SelfdriveState.OpenpilotState.enabled
|
||||
|
||||
car_state = messaging.new_message("carState")
|
||||
car_state.carState.vEgo = 22.0
|
||||
car_state.carState.aEgo = -0.2
|
||||
car_state.carState.vCruise = 72.0
|
||||
car_state.carState.vCruiseCluster = 72.0
|
||||
|
||||
car_control = messaging.new_message("carControl")
|
||||
car_control.carControl.enabled = True
|
||||
car_control.carControl.latActive = True
|
||||
car_control.carControl.cruiseControl.override = False
|
||||
|
||||
controls_state = messaging.new_message("controlsState")
|
||||
controls_state.controlsState.vCruiseDEPRECATED = 72.0
|
||||
controls_state.controlsState.vCruiseClusterDEPRECATED = 72.0
|
||||
controls_state.controlsState.curvature = 0.0
|
||||
|
||||
gps = messaging.new_message("gpsLocationExternal")
|
||||
gps.gpsLocationExternal.flags = 1
|
||||
gps.gpsLocationExternal.hasFix = True
|
||||
gps.gpsLocationExternal.verticalAccuracy = 1.0
|
||||
gps.gpsLocationExternal.speedAccuracy = 0.5
|
||||
gps.gpsLocationExternal.bearingAccuracyDeg = 1.0
|
||||
gps.gpsLocationExternal.vNED = [0.0, 0.0, 0.0]
|
||||
gps.gpsLocationExternal.latitude = DEMO_ROUTE_POINTS[0][0]
|
||||
gps.gpsLocationExternal.longitude = DEMO_ROUTE_POINTS[0][1]
|
||||
gps.gpsLocationExternal.altitude = 181.0
|
||||
gps.gpsLocationExternal.speed = 22.0
|
||||
gps.gpsLocationExternal.bearingDeg = _bearing_between(DEMO_ROUTE_POINTS[0], DEMO_ROUTE_POINTS[1])
|
||||
gps.gpsLocationExternal.unixTimestampMillis = int(time.time() * 1000)
|
||||
|
||||
for _ in range(repeats):
|
||||
pm.send("deviceState", device_state)
|
||||
pm.send("pandaStates", panda_states)
|
||||
pm.send("driverStateV2", driver_state)
|
||||
pm.send("selfdriveState", selfdrive_state)
|
||||
pm.send("carState", car_state)
|
||||
pm.send("carControl", car_control)
|
||||
pm.send("controlsState", controls_state)
|
||||
pm.send("gpsLocationExternal", gps)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def publish_nav_scene(pm: PubMaster, scene: dict, repeats: int = 6, delay: float = 0.05) -> None:
|
||||
iq_plan = messaging.new_message("iqPlan")
|
||||
iq_plan.iqPlan.longitudinalPlanSource = custom.IQPlan.LongitudinalPlanSource.nav
|
||||
iq_plan.iqPlan.vTarget = float(scene["route_speed_target"])
|
||||
iq_plan.iqPlan.aTarget = -0.7
|
||||
resolver = iq_plan.iqPlan.speedLimit.resolver
|
||||
resolver.speedLimit = float(scene["speed_limit"])
|
||||
resolver.speedLimitLast = float(scene["speed_limit"])
|
||||
resolver.speedLimitFinal = float(scene["speed_limit"])
|
||||
resolver.speedLimitFinalLast = float(scene["speed_limit"])
|
||||
resolver.speedLimitValid = True
|
||||
resolver.speedLimitLastValid = True
|
||||
resolver.speedLimitOffset = 0.0
|
||||
resolver.distToSpeedLimit = 0.0
|
||||
resolver.source = custom.IQPlan.SpeedLimit.Source.map
|
||||
assist = iq_plan.iqPlan.speedLimit.assist
|
||||
assist.enabled = False
|
||||
assist.active = False
|
||||
assist.state = custom.IQPlan.SpeedLimit.AssistState.disabled
|
||||
assist.vTarget = 255.0
|
||||
assist.aTarget = 0.0
|
||||
nav_summary = iq_plan.iqPlan.iqNavState.nav
|
||||
nav_summary.engaged = True
|
||||
nav_summary.provider = scene["provider"]
|
||||
nav_summary.state = custom.IQNavState.LongitudinalState.active
|
||||
nav_summary.speedTarget = float(scene["route_speed_target"])
|
||||
nav_summary.accelTarget = -0.7
|
||||
nav_summary.valid = True
|
||||
|
||||
iq_live_data = messaging.new_message("iqLiveData")
|
||||
iq_live_data.iqLiveData.speedLimitValid = True
|
||||
iq_live_data.iqLiveData.speedLimit = float(scene["speed_limit"])
|
||||
iq_live_data.iqLiveData.speedLimitAheadValid = True
|
||||
iq_live_data.iqLiveData.speedLimitAhead = float(scene["speed_limit_ahead"])
|
||||
iq_live_data.iqLiveData.speedLimitAheadDistance = float(scene["speed_limit_ahead_distance"])
|
||||
iq_live_data.iqLiveData.roadName = scene["road_name"]
|
||||
|
||||
iq_nav_state = messaging.new_message("iqNavState")
|
||||
nav_state = iq_nav_state.iqNavState
|
||||
nav_state.active = True
|
||||
nav_state.destinationValid = True
|
||||
nav_state.destinationLatitude = float(scene["destination_latitude"])
|
||||
nav_state.destinationLongitude = float(scene["destination_longitude"])
|
||||
nav_state.destinationName = scene.get("destination_name", "Navigation destination")
|
||||
nav_state.distanceRemaining = float(scene["distance_remaining"])
|
||||
nav_state.timeRemaining = float(scene["time_remaining"])
|
||||
nav_state.nextManeuverValid = True
|
||||
nav_state.nextManeuverDistance = float(scene["distance_m"])
|
||||
nav_state.nextManeuverType = scene["next_type"]
|
||||
nav_state.nextManeuverDirection = scene["direction"]
|
||||
nav_state.nextManeuverModifier = scene["next_modifier"]
|
||||
nav_state.nextManeuverDescription = scene["next_description"]
|
||||
nav_state.secondNextManeuverValid = scene.get("second_valid", True)
|
||||
nav_state.secondNextManeuverType = scene["second_type"]
|
||||
nav_state.secondNextManeuverDirection = scene["second_direction"]
|
||||
nav_state.secondNextManeuverDistance = float(scene["second_distance"])
|
||||
nav_state.secondNextManeuverModifier = scene.get("second_modifier", "")
|
||||
nav_state.longitudinalProvider = scene["provider"]
|
||||
nav_state.longitudinalState = custom.IQNavState.LongitudinalState.active
|
||||
nav_state.longitudinalEngaged = True
|
||||
nav_state.speedTarget = float(scene["route_speed_target"])
|
||||
nav_state.accelTarget = -0.7
|
||||
nav_state.valid = True
|
||||
nav_state.targetSpeed = float(scene["route_speed_target"])
|
||||
nav_state.targetSpeedValid = True
|
||||
nav_state.maneuverPhase = scene["phase"]
|
||||
nav_state.maneuverDirection = scene["direction"]
|
||||
nav_state.navSpeedTargetActive = True
|
||||
|
||||
if scene["phase"] in (custom.IQNavState.ManeuverPhase.highwayPrepare, custom.IQNavState.ManeuverPhase.highwayCommit):
|
||||
nav_state.shouldSendLanePositioning = True
|
||||
nav_state.lanePositioningDirection = custom.IQTurnSignalDirection.turnRight if scene["direction"] == custom.NavDirection.right else custom.IQTurnSignalDirection.turnLeft
|
||||
nav_state.command = custom.IQNavState.Command.laneChange
|
||||
nav_state.commandDirection = scene["direction"]
|
||||
nav_state.commandIndex = 1
|
||||
elif scene["phase"] in (custom.IQNavState.ManeuverPhase.turnPrepare, custom.IQNavState.ManeuverPhase.turnActive):
|
||||
nav_state.shouldSendTurnDesire = True
|
||||
nav_state.turnDesireDirection = custom.IQTurnSignalDirection.turnLeft if scene["direction"] == custom.NavDirection.left else custom.IQTurnSignalDirection.turnRight
|
||||
|
||||
iq_nav_render = messaging.new_message("iqNavRenderState")
|
||||
render_state = iq_nav_render.iqNavRenderState
|
||||
render_state.active = True
|
||||
render_state.currentLatitude = float(scene["current_latitude"])
|
||||
render_state.currentLongitude = float(scene["current_longitude"])
|
||||
render_state.bearingDeg = float(scene["bearing_deg"])
|
||||
render_state.zoomHint = float(scene["zoom_hint"])
|
||||
route_points = scene["route_points"]
|
||||
render_state.init("routePolyline", len(route_points))
|
||||
render_state.init("routePolylineSimplified", len(route_points))
|
||||
for idx, (lat, lon) in enumerate(route_points):
|
||||
render_state.routePolyline[idx].latitude = lat
|
||||
render_state.routePolyline[idx].longitude = lon
|
||||
render_state.routePolylineSimplified[idx].latitude = lat
|
||||
render_state.routePolylineSimplified[idx].longitude = lon
|
||||
render_state.nextManeuverLatitude = float(scene["next_maneuver_latitude"])
|
||||
render_state.nextManeuverLongitude = float(scene["next_maneuver_longitude"])
|
||||
render_state.nextManeuverType = scene["next_type"]
|
||||
render_state.nextManeuverDirection = scene["direction"]
|
||||
render_state.nextManeuverDistance = float(scene["distance_m"])
|
||||
render_state.destinationLatitude = float(scene["destination_latitude"])
|
||||
render_state.destinationLongitude = float(scene["destination_longitude"])
|
||||
|
||||
gps = messaging.new_message("gpsLocationExternal")
|
||||
gps.gpsLocationExternal.flags = 1
|
||||
gps.gpsLocationExternal.hasFix = True
|
||||
gps.gpsLocationExternal.verticalAccuracy = 1.0
|
||||
gps.gpsLocationExternal.speedAccuracy = 0.5
|
||||
gps.gpsLocationExternal.bearingAccuracyDeg = 1.0
|
||||
gps.gpsLocationExternal.vNED = [0.0, 0.0, 0.0]
|
||||
gps.gpsLocationExternal.latitude = float(scene["current_latitude"])
|
||||
gps.gpsLocationExternal.longitude = float(scene["current_longitude"])
|
||||
gps.gpsLocationExternal.altitude = 181.0
|
||||
gps.gpsLocationExternal.speed = max(float(scene["route_speed_target"]), 4.5)
|
||||
gps.gpsLocationExternal.bearingDeg = float(scene["bearing_deg"])
|
||||
gps.gpsLocationExternal.unixTimestampMillis = int(time.time() * 1000)
|
||||
|
||||
for _ in range(repeats):
|
||||
pm.send("iqPlan", iq_plan)
|
||||
pm.send("iqLiveData", iq_live_data)
|
||||
pm.send("iqNavState", iq_nav_state)
|
||||
pm.send("iqNavRenderState", iq_nav_render)
|
||||
pm.send("gpsLocationExternal", gps)
|
||||
time.sleep(delay)
|
||||
3637
selfdrive/ui/tests/test_ui/nav_demo_fixture_bolingbrook_mapbox.json
Normal file
3637
selfdrive/ui/tests/test_ui/nav_demo_fixture_bolingbrook_mapbox.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,404 @@
|
||||
{
|
||||
"trip": {
|
||||
"locations": [
|
||||
{
|
||||
"type": "break",
|
||||
"lat": 41.690091,
|
||||
"lon": -88.078806,
|
||||
"name": "185 Brandon Ct, Bolingbrook, IL 60440",
|
||||
"side_of_street": "left",
|
||||
"original_index": 0
|
||||
},
|
||||
{
|
||||
"type": "break",
|
||||
"lat": 41.908129,
|
||||
"lon": -88.115063,
|
||||
"name": "505 E North Ave, Carol Stream, IL 60188",
|
||||
"original_index": 1
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"type": 3,
|
||||
"instruction": "Drive east on Cinnamon Court.",
|
||||
"verbal_succinct_transition_instruction": "Drive east. Then, in 300 feet, Turn left to stay on Cinnamon Court.",
|
||||
"verbal_pre_transition_instruction": "Drive east on Cinnamon Court. Then, in 300 feet, Turn left to stay on Cinnamon Court.",
|
||||
"verbal_post_transition_instruction": "Continue for 300 feet.",
|
||||
"street_names": [
|
||||
"Cinnamon Court"
|
||||
],
|
||||
"bearing_after": 86,
|
||||
"time": 9.98,
|
||||
"length": 0.0515,
|
||||
"cost": 15.202,
|
||||
"begin_shape_index": 0,
|
||||
"end_shape_index": 2,
|
||||
"verbal_multi_cue": true,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left to stay on Cinnamon Court.",
|
||||
"verbal_transition_alert_instruction": "Turn left to stay on Cinnamon Court.",
|
||||
"verbal_succinct_transition_instruction": "Turn left.",
|
||||
"verbal_pre_transition_instruction": "Turn left to stay on Cinnamon Court.",
|
||||
"verbal_post_transition_instruction": "Continue for 400 feet.",
|
||||
"street_names": [
|
||||
"Cinnamon Court"
|
||||
],
|
||||
"bearing_before": 89,
|
||||
"bearing_after": 354,
|
||||
"time": 17.532,
|
||||
"length": 0.0764,
|
||||
"cost": 24.474,
|
||||
"begin_shape_index": 2,
|
||||
"end_shape_index": 5,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 10,
|
||||
"instruction": "Turn right onto Lily Cache Lane.",
|
||||
"verbal_transition_alert_instruction": "Turn right onto Lily Cache Lane.",
|
||||
"verbal_succinct_transition_instruction": "Turn right.",
|
||||
"verbal_pre_transition_instruction": "Turn right onto Lily Cache Lane.",
|
||||
"verbal_post_transition_instruction": "Continue for a half mile.",
|
||||
"street_names": [
|
||||
"Lily Cache Lane"
|
||||
],
|
||||
"bearing_before": 356,
|
||||
"bearing_after": 88,
|
||||
"time": 62.436,
|
||||
"length": 0.5014,
|
||||
"cost": 151.84,
|
||||
"begin_shape_index": 5,
|
||||
"end_shape_index": 18,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 10,
|
||||
"instruction": "Turn right onto South Bolingbrook Drive/IL 53.",
|
||||
"verbal_transition_alert_instruction": "Turn right onto South Bolingbrook Drive.",
|
||||
"verbal_succinct_transition_instruction": "Turn right.",
|
||||
"verbal_pre_transition_instruction": "Turn right onto South Bolingbrook Drive, IL 53.",
|
||||
"verbal_post_transition_instruction": "Continue for a half mile.",
|
||||
"street_names": [
|
||||
"South Bolingbrook Drive",
|
||||
"IL 53"
|
||||
],
|
||||
"bearing_before": 89,
|
||||
"bearing_after": 179,
|
||||
"time": 52.006,
|
||||
"length": 0.4473,
|
||||
"cost": 129.587,
|
||||
"begin_shape_index": 18,
|
||||
"end_shape_index": 34,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 19,
|
||||
"instruction": "Turn left to take the I 55 North ramp toward Chicago.",
|
||||
"verbal_transition_alert_instruction": "Turn left to take the I 55 North ramp.",
|
||||
"verbal_pre_transition_instruction": "Turn left to take the I 55 North ramp toward Chicago.",
|
||||
"verbal_post_transition_instruction": "Continue for 1.5 miles.",
|
||||
"street_names": [
|
||||
"I 55 North",
|
||||
"Adlai Stevenson Expressway"
|
||||
],
|
||||
"bearing_before": 177,
|
||||
"bearing_after": 66,
|
||||
"time": 99.799,
|
||||
"length": 1.4676,
|
||||
"cost": 180.0,
|
||||
"begin_shape_index": 34,
|
||||
"end_shape_index": 55,
|
||||
"highway": true,
|
||||
"sign": {
|
||||
"exit_branch_elements": [
|
||||
{
|
||||
"text": "I 55 North",
|
||||
"consecutive_count": 1
|
||||
}
|
||||
],
|
||||
"exit_toward_elements": [
|
||||
{
|
||||
"text": "Chicago"
|
||||
}
|
||||
]
|
||||
},
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 20,
|
||||
"instruction": "Take exit 269 on the right onto I 355 Toll toward Northwest Suburbs.",
|
||||
"verbal_transition_alert_instruction": "Take exit 269 on the right.",
|
||||
"verbal_pre_transition_instruction": "Take exit 269 on the right onto I 355 Toll toward Northwest Suburbs.",
|
||||
"bearing_before": 55,
|
||||
"bearing_after": 60,
|
||||
"time": 56.476,
|
||||
"length": 0.5294,
|
||||
"cost": 60.572,
|
||||
"begin_shape_index": 55,
|
||||
"end_shape_index": 63,
|
||||
"toll": true,
|
||||
"sign": {
|
||||
"exit_number_elements": [
|
||||
{
|
||||
"text": "269"
|
||||
}
|
||||
],
|
||||
"exit_branch_elements": [
|
||||
{
|
||||
"text": "I 355 Toll"
|
||||
}
|
||||
],
|
||||
"exit_toward_elements": [
|
||||
{
|
||||
"text": "Northwest Suburbs",
|
||||
"consecutive_count": 1
|
||||
},
|
||||
{
|
||||
"text": "Southwest Suburbs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 24,
|
||||
"instruction": "Keep left to take I 355 North toward Northwest Suburbs.",
|
||||
"verbal_transition_alert_instruction": "Keep left to take I 355 North.",
|
||||
"verbal_pre_transition_instruction": "Keep left to take I 355 North toward Northwest Suburbs.",
|
||||
"verbal_post_transition_instruction": "Continue for 16 miles.",
|
||||
"street_names": [
|
||||
"I 355 North",
|
||||
"Veterans Memorial Tollway"
|
||||
],
|
||||
"bearing_before": 58,
|
||||
"bearing_after": 58,
|
||||
"time": 940.064,
|
||||
"length": 15.5504,
|
||||
"cost": 1049.088,
|
||||
"begin_shape_index": 63,
|
||||
"end_shape_index": 353,
|
||||
"toll": true,
|
||||
"highway": true,
|
||||
"sign": {
|
||||
"exit_branch_elements": [
|
||||
{
|
||||
"text": "I 355 North",
|
||||
"consecutive_count": 1
|
||||
}
|
||||
],
|
||||
"exit_toward_elements": [
|
||||
{
|
||||
"text": "Northwest Suburbs",
|
||||
"consecutive_count": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 20,
|
||||
"instruction": "Take exit 27 on the right onto IL 64 toward North Avenue.",
|
||||
"verbal_transition_alert_instruction": "Take exit 27 on the right.",
|
||||
"verbal_pre_transition_instruction": "Take exit 27 on the right onto IL 64 toward North Avenue.",
|
||||
"bearing_before": 358,
|
||||
"bearing_after": 3,
|
||||
"time": 27.824,
|
||||
"length": 0.4268,
|
||||
"cost": 29.178,
|
||||
"begin_shape_index": 353,
|
||||
"end_shape_index": 365,
|
||||
"toll": true,
|
||||
"sign": {
|
||||
"exit_number_elements": [
|
||||
{
|
||||
"text": "27"
|
||||
}
|
||||
],
|
||||
"exit_branch_elements": [
|
||||
{
|
||||
"text": "IL 64",
|
||||
"consecutive_count": 2
|
||||
}
|
||||
],
|
||||
"exit_toward_elements": [
|
||||
{
|
||||
"text": "North Avenue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 24,
|
||||
"instruction": "Keep left to take IL 64 toward Glendale Heights/Carol Stream.",
|
||||
"verbal_transition_alert_instruction": "Keep left to take IL 64.",
|
||||
"verbal_pre_transition_instruction": "Keep left to take IL 64 toward Glendale Heights, Carol Stream. Then Turn left onto North Avenue.",
|
||||
"bearing_before": 9,
|
||||
"bearing_after": 1,
|
||||
"time": 5.008,
|
||||
"length": 0.0813,
|
||||
"cost": 24.782,
|
||||
"begin_shape_index": 365,
|
||||
"end_shape_index": 369,
|
||||
"toll": true,
|
||||
"sign": {
|
||||
"exit_branch_elements": [
|
||||
{
|
||||
"text": "IL 64",
|
||||
"consecutive_count": 2
|
||||
}
|
||||
],
|
||||
"exit_toward_elements": [
|
||||
{
|
||||
"text": "Glendale Heights"
|
||||
},
|
||||
{
|
||||
"text": "Carol Stream"
|
||||
}
|
||||
]
|
||||
},
|
||||
"verbal_multi_cue": true,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left onto North Avenue/IL 64.",
|
||||
"verbal_transition_alert_instruction": "Turn left onto North Avenue.",
|
||||
"verbal_succinct_transition_instruction": "Turn left.",
|
||||
"verbal_pre_transition_instruction": "Turn left onto North Avenue, IL 64.",
|
||||
"verbal_post_transition_instruction": "Continue for 3 miles.",
|
||||
"street_names": [
|
||||
"North Avenue",
|
||||
"IL 64"
|
||||
],
|
||||
"bearing_before": 13,
|
||||
"bearing_after": 266,
|
||||
"time": 287.201,
|
||||
"length": 3.3088,
|
||||
"cost": 330.452,
|
||||
"begin_shape_index": 369,
|
||||
"end_shape_index": 476,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 10,
|
||||
"instruction": "Turn right onto North Schmale Road.",
|
||||
"verbal_transition_alert_instruction": "Turn right onto North Schmale Road.",
|
||||
"verbal_succinct_transition_instruction": "Turn right.",
|
||||
"verbal_pre_transition_instruction": "Turn right onto North Schmale Road.",
|
||||
"verbal_post_transition_instruction": "Continue for a half mile.",
|
||||
"street_names": [
|
||||
"North Schmale Road"
|
||||
],
|
||||
"bearing_before": 268,
|
||||
"bearing_after": 2,
|
||||
"time": 46.619,
|
||||
"length": 0.5163,
|
||||
"cost": 104.948,
|
||||
"begin_shape_index": 476,
|
||||
"end_shape_index": 507,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left onto Kehoe Boulevard.",
|
||||
"verbal_transition_alert_instruction": "Turn left onto Kehoe Boulevard.",
|
||||
"verbal_succinct_transition_instruction": "Turn left.",
|
||||
"verbal_pre_transition_instruction": "Turn left onto Kehoe Boulevard.",
|
||||
"verbal_post_transition_instruction": "Continue for a half mile.",
|
||||
"street_names": [
|
||||
"Kehoe Boulevard"
|
||||
],
|
||||
"bearing_before": 0,
|
||||
"bearing_after": 270,
|
||||
"time": 146.003,
|
||||
"length": 0.7431,
|
||||
"cost": 260.047,
|
||||
"begin_shape_index": 507,
|
||||
"end_shape_index": 533,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left.",
|
||||
"verbal_transition_alert_instruction": "Turn left.",
|
||||
"verbal_succinct_transition_instruction": "Turn left.",
|
||||
"verbal_pre_transition_instruction": "Turn left.",
|
||||
"verbal_post_transition_instruction": "Continue for 400 feet.",
|
||||
"bearing_before": 269,
|
||||
"bearing_after": 180,
|
||||
"time": 21.653,
|
||||
"length": 0.0677,
|
||||
"cost": 113.812,
|
||||
"begin_shape_index": 533,
|
||||
"end_shape_index": 538,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 4,
|
||||
"instruction": "You have arrived at 505 E North Ave, Carol Stream, IL 60188.",
|
||||
"verbal_transition_alert_instruction": "You will arrive at 505 E North Ave, Carol Stream, IL 60188.",
|
||||
"verbal_pre_transition_instruction": "You have arrived at 505 E North Ave, Carol Stream, IL 60188.",
|
||||
"bearing_before": 150,
|
||||
"time": 0.0,
|
||||
"length": 0.0,
|
||||
"cost": 0.0,
|
||||
"begin_shape_index": 538,
|
||||
"end_shape_index": 538,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"has_time_restrictions": false,
|
||||
"has_toll": true,
|
||||
"has_highway": true,
|
||||
"has_ferry": false,
|
||||
"min_lat": 41.684661,
|
||||
"min_lon": -88.115648,
|
||||
"max_lat": 41.909558,
|
||||
"max_lon": -88.027976,
|
||||
"time": 1772.608,
|
||||
"length": 23.7686,
|
||||
"cost": 2473.988
|
||||
},
|
||||
"shape": "mpponAb{{~fDe@}TWog@aRnAwh@jBwFT]ie@c@cj@]k^c@ae@]qa@q@u|@Yql@Sud@OcZnA_L_@efA[u{@o@ueBde@c@dKKjJQpq@mAjS_@nS_@vl@o@rEGpOO~SaAja@cArf@YjPK`GC`IQtr@aBv@mE[gIuBsIiD_PoIiWgJcSsGuJmMwPaPuP_WyWkYoh@_RiXwVil@{p@g~Agj@orAeZis@wqAo`DagCqdGsn@{zA{dBiiEmqBowE}z@{gCal@{cBm[c}@oq@}lBaO}b@wJwX_Reh@qOwb@imAugDcv@gtBkk@qgBqTue@sFcJcE{FgE{EgJkIwGaFaHqDgGiCkHqBuFiAcG_A}Ge@_FS_HDiFN}JhAoHdBwJfCiJbEkIhFoKpIs`@r^ex@x{@w^f^cTpP{GbE}OxIkLlEaRbEs]xEyn@jJuiBpUct@bNa}B~WogCbYqeCjXojAzMe~BbPofBbHm\\rAqiHnGkzCd@ymDzIimDzAafBbC}m@f@kdH~G_dCtAccHzIyrAzAiaAvA_iBlAqjAZ}fAdCmpAlKefA|ZmmAre@asA`{@spDpiDiS~Rii@~h@saCv|BgxCdtCk}@`{@yrA~nAkRrRwY|YoaA`~@mrCzlCa^n[qb@hZke@rZ_^zQ}IxFwQ|Io^hOil@`T{gAx\\mz@~SyfBp]kdA~Mwq@hImp@hEazAjIwr@dBkdBdCcJJ_\\j@o`AbAeiA|AkXb@uRFmOJ{i@Ci_ApEem@}FytBmReqBod@cSqFcRyF_\\oKk\\_M}\\yM{ZsO_\\yQmZoSsYcUcY_XmR}Q}QkSsL{NmLqPmLoPeL{PuKyQqK_TuIsRmH_R_JmWeImYsGiZaC}MkBaN_ByLqAyLmAwLeAcNq@yKk@iNe@_MUaNMkMBaM@yLPmM\\sL^gNhAqZ~LyrCrUgnFzCk}@y@ob@kEcjAyUwpBkQ}x@eZe}@wo@itA_q@y~@qv@{p@ks@y`@geAoVcS_D{RoBsTu@uTC}}@zB_dAb@eMP_yAdCal@fAg}AjC{DFuSh@wq@Yeo@xEq`B~AinAlB}|@~@oxAhCc{@`BwgAvAmrAxAodAvBusAnBu~@bBmfA`Bgm@zAwi@lAqqAbC_c@l@ms@k@knBtFgQ~Au[nDgZnGgSdFgUlIgSnJiYrNcNxHyPdMsP~MeQzP{U|VwR`XkY`e@gSva@iMrXsMd\\oY|t@a\\nv@kQv]gOlW}NzUaSrXqSvXoL~MqMlPcG`GiJpJcHrGwMrLqXjUsPxLsZpRsU~MyH`EoV|KmUvJoR|G{RhGoPbEmUxF}j@rJaKjAyE\\cLnAiXdBwT~@qY`@qf@a@eUiAu[sBiWcDqZyEce@eJcs@yPo_AmVch@eOyb@{N_Cu@wZeKyi@kQqZwLmk@gUkn@yWqaAgd@{a@oTwbBm~@ilA{u@wjA}v@oa@{Wo]yQkn@{WuXeJe_@gKgc@mI_a@mFmYiC}_@yAwU_@kZNqWj@m^`Bug@jGya@bIeQdEse@lNi]zMsf@fUij@jZ}bBj_A_o@l]ou@za@kZ|Mcl@jSog@bKsf@nF}]vAu}@hAah@n@u^l@oi@p@gk@`@_b@n@crBxB}_@h@sg@{Aaj@Ric@d@qq@Cik@o@a[Y}^_C_^}DoHyAy[{EqTgDaQ{Byd@c@sMZoDHkN{C\\zPTjLj@pz@Rb[`@|]~C~lA^r]J|KJbIb@jd@l@rp@F~JDjLB|Fd@leAZxs@FzMd@|c@N~Zr@hyAF`FHxGtA~jAJ`Fp@x^VvMPvJBfCb@|i@r@z|@RjVJ`OFzIJdP|@reA@tF~@zh@vAdiATdQJzHRbPRrORp\\h@ps@FlMB|IxA~qBVxUDzENvLv@p}@HjIFpHLvIjA~|@ThSn@vk@fB~bB@d@t@fs@n@zl@@p@vA|tARfRNrRP`UFjH^ld@xAbdB`@jZBzAxAdeAf@pk@n@`v@xA~cBjAluAP|MJlKJvLPjP@lBj@bj@j@vi@HzHZxUPtO\\tXx@rr@v@~o@z@pcAVnYhAzrA|@hdAvBziBj@xd@^zb@d@nl@RhTjBh{AhEfsD`@`a@jAplAFnF^~\\^l\\f@fe@p@br@aDpKk@hBaArB}@hAkFhF{R_@}n@uAgErCqJ]cEKu[{@_LWy^}@gTg@y@A{LUwEOuL]oESgJo@_PyBmLsCga@qJqLkCiG}@aEa@eIa@yGSmYG_L@qi@K?hI?l@?r]?lo@?dQ?~fA?h`@?pX?rgA?pe@?rE?|e@Brg@BlPEjdA?js@?vR@bM@dlBG`b@@ti@Bxa@?fSAjO?hV@pHdMBpCvA~E|BxRBzKyH"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"has_time_restrictions": false,
|
||||
"has_toll": true,
|
||||
"has_highway": true,
|
||||
"has_ferry": false,
|
||||
"min_lat": 41.684661,
|
||||
"min_lon": -88.115648,
|
||||
"max_lat": 41.909558,
|
||||
"max_lon": -88.027976,
|
||||
"time": 1772.608,
|
||||
"length": 23.7686,
|
||||
"cost": 2473.988
|
||||
},
|
||||
"status_message": "Found route between points",
|
||||
"status": 0,
|
||||
"units": "miles",
|
||||
"language": "en-US"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
{
|
||||
"trip": {
|
||||
"locations": [
|
||||
{
|
||||
"type": "break",
|
||||
"lat": 41.701966,
|
||||
"lon": -88.086597,
|
||||
"original_index": 0
|
||||
},
|
||||
{
|
||||
"type": "break",
|
||||
"lat": 41.704616,
|
||||
"lon": -88.067058,
|
||||
"original_index": 1
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"type": 1,
|
||||
"instruction": "Drive north on Brandon Court.",
|
||||
"verbal_succinct_transition_instruction": "Drive north. Then Turn left onto Blair Lane.",
|
||||
"verbal_pre_transition_instruction": "Drive north on Brandon Court. Then Turn left onto Blair Lane.",
|
||||
"verbal_post_transition_instruction": "Continue for 80 meters.",
|
||||
"street_names": [
|
||||
"Brandon Court"
|
||||
],
|
||||
"bearing_after": 359,
|
||||
"time": 10.107,
|
||||
"length": 0.084,
|
||||
"cost": 10.846,
|
||||
"begin_shape_index": 0,
|
||||
"end_shape_index": 2,
|
||||
"verbal_multi_cue": true,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left onto Blair Lane.",
|
||||
"verbal_transition_alert_instruction": "Turn left onto Blair Lane.",
|
||||
"verbal_succinct_transition_instruction": "Turn left. Then Turn right onto North Schmidt Road.",
|
||||
"verbal_pre_transition_instruction": "Turn left onto Blair Lane. Then Turn right onto North Schmidt Road.",
|
||||
"verbal_post_transition_instruction": "Continue for 90 meters.",
|
||||
"street_names": [
|
||||
"Blair Lane"
|
||||
],
|
||||
"bearing_before": 359,
|
||||
"bearing_after": 269,
|
||||
"time": 12.751,
|
||||
"length": 0.09,
|
||||
"cost": 20.96,
|
||||
"begin_shape_index": 2,
|
||||
"end_shape_index": 4,
|
||||
"verbal_multi_cue": true,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 10,
|
||||
"instruction": "Turn right onto North Schmidt Road.",
|
||||
"verbal_transition_alert_instruction": "Turn right onto North Schmidt Road.",
|
||||
"verbal_succinct_transition_instruction": "Turn right.",
|
||||
"verbal_pre_transition_instruction": "Turn right onto North Schmidt Road.",
|
||||
"verbal_post_transition_instruction": "Continue for 500 meters.",
|
||||
"street_names": [
|
||||
"North Schmidt Road"
|
||||
],
|
||||
"bearing_before": 269,
|
||||
"bearing_after": 358,
|
||||
"time": 43.917,
|
||||
"length": 0.533,
|
||||
"cost": 77.944,
|
||||
"begin_shape_index": 4,
|
||||
"end_shape_index": 17,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 10,
|
||||
"instruction": "Turn right onto West Boughton Road/CH 67.",
|
||||
"verbal_transition_alert_instruction": "Turn right onto West Boughton Road.",
|
||||
"verbal_succinct_transition_instruction": "Turn right.",
|
||||
"verbal_pre_transition_instruction": "Turn right onto West Boughton Road, CH 67.",
|
||||
"verbal_post_transition_instruction": "Continue for 1.5 kilometers.",
|
||||
"street_names": [
|
||||
"West Boughton Road",
|
||||
"CH 67"
|
||||
],
|
||||
"bearing_before": 359,
|
||||
"bearing_after": 80,
|
||||
"time": 104.058,
|
||||
"length": 1.628,
|
||||
"cost": 143.853,
|
||||
"begin_shape_index": 17,
|
||||
"end_shape_index": 48,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 10,
|
||||
"instruction": "Turn right onto North Bolingbrook Drive/IL 53.",
|
||||
"verbal_transition_alert_instruction": "Turn right onto North Bolingbrook Drive.",
|
||||
"verbal_succinct_transition_instruction": "Turn right.",
|
||||
"verbal_pre_transition_instruction": "Turn right onto North Bolingbrook Drive, IL 53.",
|
||||
"verbal_post_transition_instruction": "Continue for 700 meters.",
|
||||
"street_names": [
|
||||
"North Bolingbrook Drive",
|
||||
"IL 53"
|
||||
],
|
||||
"bearing_before": 67,
|
||||
"bearing_after": 178,
|
||||
"time": 51.743,
|
||||
"length": 0.723,
|
||||
"cost": 70.552,
|
||||
"begin_shape_index": 48,
|
||||
"end_shape_index": 62,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left.",
|
||||
"verbal_transition_alert_instruction": "Turn left.",
|
||||
"verbal_succinct_transition_instruction": "Turn left.",
|
||||
"verbal_pre_transition_instruction": "Turn left.",
|
||||
"verbal_post_transition_instruction": "Continue for 100 meters.",
|
||||
"bearing_before": 178,
|
||||
"bearing_after": 89,
|
||||
"time": 24.024,
|
||||
"length": 0.126,
|
||||
"cost": 146.206,
|
||||
"begin_shape_index": 62,
|
||||
"end_shape_index": 66,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left.",
|
||||
"verbal_transition_alert_instruction": "Turn left.",
|
||||
"verbal_succinct_transition_instruction": "Turn left. Then You will arrive at your destination.",
|
||||
"verbal_pre_transition_instruction": "Turn left. Then You will arrive at your destination.",
|
||||
"verbal_post_transition_instruction": "Continue for 30 meters.",
|
||||
"bearing_before": 88,
|
||||
"bearing_after": 359,
|
||||
"time": 7.643,
|
||||
"length": 0.031,
|
||||
"cost": 14.839,
|
||||
"begin_shape_index": 66,
|
||||
"end_shape_index": 68,
|
||||
"verbal_multi_cue": true,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 4,
|
||||
"instruction": "You have arrived at your destination.",
|
||||
"verbal_transition_alert_instruction": "You will arrive at your destination.",
|
||||
"verbal_pre_transition_instruction": "You have arrived at your destination.",
|
||||
"bearing_before": 359,
|
||||
"time": 0.0,
|
||||
"length": 0.0,
|
||||
"cost": 0.0,
|
||||
"begin_shape_index": 68,
|
||||
"end_shape_index": 68,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"has_time_restrictions": false,
|
||||
"has_toll": false,
|
||||
"has_highway": false,
|
||||
"has_ferry": false,
|
||||
"min_lat": 41.701966,
|
||||
"min_lon": -88.087852,
|
||||
"max_lat": 41.710795,
|
||||
"max_lon": -88.06705,
|
||||
"time": 254.245,
|
||||
"length": 3.216,
|
||||
"cost": 485.201
|
||||
},
|
||||
"shape": "{chpnAhck_gDki@d@yCBR|z@@rFyc@n@yn@j@yo@`AgCDwDDoo@h@gEDaZTue@`@sLJiDDkIFsEDg@wHoBsYyD_k@uBq[gCo`@iB}XQoCWqDaBaWkBeXkBmZsCy_@{D_i@gFsw@gSe~CcEer@e\\mxEyDy^gGge@wIqf@}Nsz@eGm]cA_GaDwQaBqJy@_F{I}g@sB}KqByKqLwi@qE{S~S_@hQ[jS]dIOlJQpHMvo@o@f^]bmAsApSW~j@s@pY_@jMQvr@{@G}GKyUUo_@W_^{IHcFD"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"has_time_restrictions": false,
|
||||
"has_toll": false,
|
||||
"has_highway": false,
|
||||
"has_ferry": false,
|
||||
"min_lat": 41.701966,
|
||||
"min_lon": -88.087852,
|
||||
"max_lat": 41.710795,
|
||||
"max_lon": -88.06705,
|
||||
"time": 254.245,
|
||||
"length": 3.216,
|
||||
"cost": 485.201
|
||||
},
|
||||
"status_message": "Found route between points",
|
||||
"status": 0,
|
||||
"units": "kilometers",
|
||||
"language": "en-US"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"trip": {
|
||||
"locations": [
|
||||
{
|
||||
"type": "break",
|
||||
"lat": 41.701966,
|
||||
"lon": -88.086597,
|
||||
"original_index": 0
|
||||
},
|
||||
{
|
||||
"type": "break",
|
||||
"lat": 41.699213,
|
||||
"lon": -88.102372,
|
||||
"original_index": 1
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"maneuvers": [
|
||||
{
|
||||
"type": 1,
|
||||
"instruction": "Drive north on Brandon Court.",
|
||||
"verbal_succinct_transition_instruction": "Drive north. Then Turn left onto Blair Lane.",
|
||||
"verbal_pre_transition_instruction": "Drive north on Brandon Court. Then Turn left onto Blair Lane.",
|
||||
"verbal_post_transition_instruction": "Continue for 80 meters.",
|
||||
"street_names": [
|
||||
"Brandon Court"
|
||||
],
|
||||
"bearing_after": 359,
|
||||
"time": 13.766,
|
||||
"length": 0.084,
|
||||
"cost": 14.791,
|
||||
"begin_shape_index": 0,
|
||||
"end_shape_index": 2,
|
||||
"verbal_multi_cue": true,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left onto Blair Lane.",
|
||||
"verbal_transition_alert_instruction": "Turn left onto Blair Lane.",
|
||||
"verbal_succinct_transition_instruction": "Turn left.",
|
||||
"verbal_pre_transition_instruction": "Turn left onto Blair Lane.",
|
||||
"verbal_post_transition_instruction": "Continue for 90 meters.",
|
||||
"street_names": [
|
||||
"Blair Lane"
|
||||
],
|
||||
"bearing_before": 359,
|
||||
"bearing_after": 269,
|
||||
"time": 20.128,
|
||||
"length": 0.09,
|
||||
"cost": 32.463,
|
||||
"begin_shape_index": 2,
|
||||
"end_shape_index": 4,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 15,
|
||||
"instruction": "Turn left onto North Schmidt Road.",
|
||||
"verbal_transition_alert_instruction": "Turn left onto North Schmidt Road.",
|
||||
"verbal_succinct_transition_instruction": "Turn left.",
|
||||
"verbal_pre_transition_instruction": "Turn left onto North Schmidt Road.",
|
||||
"verbal_post_transition_instruction": "Continue for 500 meters.",
|
||||
"street_names": [
|
||||
"North Schmidt Road"
|
||||
],
|
||||
"bearing_before": 269,
|
||||
"bearing_after": 179,
|
||||
"time": 48.162,
|
||||
"length": 0.522,
|
||||
"cost": 72.474,
|
||||
"begin_shape_index": 4,
|
||||
"end_shape_index": 11,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 10,
|
||||
"instruction": "Turn right onto West Briarcliff Road.",
|
||||
"verbal_transition_alert_instruction": "Turn right onto West Briarcliff Road.",
|
||||
"verbal_succinct_transition_instruction": "Turn right.",
|
||||
"verbal_pre_transition_instruction": "Turn right onto West Briarcliff Road.",
|
||||
"verbal_post_transition_instruction": "Continue for 1.5 kilometers.",
|
||||
"street_names": [
|
||||
"West Briarcliff Road"
|
||||
],
|
||||
"bearing_before": 178,
|
||||
"bearing_after": 267,
|
||||
"time": 211.369,
|
||||
"length": 1.278,
|
||||
"cost": 252.521,
|
||||
"begin_shape_index": 11,
|
||||
"end_shape_index": 39,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
},
|
||||
{
|
||||
"type": 4,
|
||||
"instruction": "You have arrived at your destination.",
|
||||
"verbal_transition_alert_instruction": "You will arrive at your destination.",
|
||||
"verbal_pre_transition_instruction": "You have arrived at your destination.",
|
||||
"bearing_before": 274,
|
||||
"time": 0.0,
|
||||
"length": 0.0,
|
||||
"cost": 0.0,
|
||||
"begin_shape_index": 39,
|
||||
"end_shape_index": 39,
|
||||
"travel_mode": "drive",
|
||||
"travel_type": "car"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"has_time_restrictions": false,
|
||||
"has_toll": false,
|
||||
"has_highway": false,
|
||||
"has_ferry": false,
|
||||
"min_lat": 41.697911,
|
||||
"min_lon": -88.102372,
|
||||
"max_lat": 41.702721,
|
||||
"max_lon": -88.086596,
|
||||
"time": 293.426,
|
||||
"length": 1.974,
|
||||
"cost": 372.251
|
||||
},
|
||||
"shape": "{chpnAhck_gDki@d@yCBR|z@@rF~vB_C|DCzGElfAq@r_@i@|PWbc@s@HxGhC~zBjA|aAuKf|@cGrl@K`IxE|jEj@rx@B~LNhm@x@bgALjKCfLiAdQwBbOaD|McDzIiElHiIbLcMtPmD~F_EnJoDlNeE|Vu@bF]nEe@`K@lD"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"has_time_restrictions": false,
|
||||
"has_toll": false,
|
||||
"has_highway": false,
|
||||
"has_ferry": false,
|
||||
"min_lat": 41.697911,
|
||||
"min_lon": -88.102372,
|
||||
"max_lat": 41.702721,
|
||||
"max_lon": -88.086596,
|
||||
"time": 293.426,
|
||||
"length": 1.974,
|
||||
"cost": 372.251
|
||||
},
|
||||
"status_message": "Found route between points",
|
||||
"status": 0,
|
||||
"units": "kilometers",
|
||||
"language": "en-US"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
36
selfdrive/ui/tests/test_ui/print_mouse_coords.py
Executable file
36
selfdrive/ui/tests/test_ui/print_mouse_coords.py
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple script to print mouse coordinates on Ubuntu.
|
||||
Run with: python print_mouse_coords.py
|
||||
Press Ctrl+C to exit.
|
||||
"""
|
||||
|
||||
from pynput import mouse
|
||||
|
||||
print("Mouse coordinate printer - Press Ctrl+C to exit")
|
||||
print("Click to set the top left origin")
|
||||
|
||||
origin: tuple[int, int] | None = None
|
||||
clicks: list[tuple[int, int]] = []
|
||||
|
||||
|
||||
def on_click(x, y, button, pressed):
|
||||
global origin, clicks
|
||||
if pressed: # Only on mouse down, not up
|
||||
if origin is None:
|
||||
origin = (x, y)
|
||||
print(f"Origin set to: {x},{y}")
|
||||
else:
|
||||
rel_x = x - origin[0]
|
||||
rel_y = y - origin[1]
|
||||
clicks.append((rel_x, rel_y))
|
||||
print(f"Clicks: {clicks}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# Start mouse listener
|
||||
with mouse.Listener(on_click=on_click) as listener:
|
||||
listener.join()
|
||||
except KeyboardInterrupt:
|
||||
print("\nExiting...")
|
||||
392
selfdrive/ui/tests/test_ui/raylib_screenshots.py
Executable file
392
selfdrive/ui/tests/test_ui/raylib_screenshots.py
Executable file
@@ -0,0 +1,392 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import time
|
||||
import pathlib
|
||||
from collections import namedtuple
|
||||
|
||||
import pyautogui
|
||||
import pywinctl
|
||||
|
||||
from cereal import car, log
|
||||
from cereal import messaging
|
||||
from cereal.messaging import PubMaster
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.test.helpers import with_processes
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
|
||||
from openpilot.system.updated.updated import parse_release_notes
|
||||
from openpilot.system.version import terms_version, training_version
|
||||
from openpilot.selfdrive.ui.tests.test_ui.nav_demo_common import (
|
||||
NAV_SCENES,
|
||||
build_ui_pubmaster,
|
||||
publish_nav_scene,
|
||||
)
|
||||
|
||||
AlertSize = log.SelfdriveState.AlertSize
|
||||
AlertStatus = log.SelfdriveState.AlertStatus
|
||||
|
||||
TEST_DIR = pathlib.Path(__file__).parent
|
||||
TEST_OUTPUT_DIR = TEST_DIR / "raylib_report"
|
||||
SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots"
|
||||
UI_DELAY = 0.5
|
||||
|
||||
BRANCH_NAME = "this-is-a-really-super-mega-ultra-max-extreme-ultimate-long-branch-name"
|
||||
VERSION = f"0.10.1 / {BRANCH_NAME} / 7864838 / Oct 03"
|
||||
|
||||
# Offroad alerts to test
|
||||
OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot']
|
||||
|
||||
|
||||
def put_update_params(params: Params):
|
||||
params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR))
|
||||
params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR))
|
||||
params.put("UpdaterTargetBranch", BRANCH_NAME)
|
||||
|
||||
|
||||
def setup_homescreen(click, pm: PubMaster, scroll=None):
|
||||
pass
|
||||
|
||||
|
||||
def setup_homescreen_update_available(click, pm: PubMaster, scroll=None):
|
||||
params = Params()
|
||||
params.put_bool("UpdateAvailable", True)
|
||||
put_update_params(params)
|
||||
setup_offroad_alert(click, pm)
|
||||
|
||||
|
||||
def setup_settings(click, pm: PubMaster, scroll=None):
|
||||
click(100, 100)
|
||||
|
||||
|
||||
def close_settings(click, pm: PubMaster, scroll=None):
|
||||
click(140, 120)
|
||||
|
||||
|
||||
def setup_settings_network(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
click(278, 450)
|
||||
|
||||
|
||||
def setup_settings_network_advanced(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_network(click, pm, scroll=scroll)
|
||||
click(1880, 100)
|
||||
|
||||
|
||||
def setup_settings_toggles(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
click(278, 620)
|
||||
|
||||
|
||||
def setup_settings_software(click, pm: PubMaster, scroll=None):
|
||||
put_update_params(Params())
|
||||
setup_settings(click, pm)
|
||||
click(278, 730)
|
||||
|
||||
|
||||
def setup_settings_software_download(click, pm: PubMaster, scroll=None):
|
||||
params = Params()
|
||||
# setup_settings_software but with "DOWNLOAD" button to test long text
|
||||
params.put("UpdaterState", "idle")
|
||||
params.put_bool("UpdaterFetchAvailable", True)
|
||||
setup_settings_software(click, pm)
|
||||
|
||||
|
||||
def setup_settings_software_release_notes(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_software(click, pm, scroll=scroll)
|
||||
click(588, 110) # expand description for current version
|
||||
|
||||
|
||||
def setup_settings_software_branch_switcher(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_software(click, pm, scroll=scroll)
|
||||
params = Params()
|
||||
params.put("UpdaterAvailableBranches", f"master,nightly,release,{BRANCH_NAME}")
|
||||
params.put("GitBranch", BRANCH_NAME) # should be on top
|
||||
params.put("UpdaterTargetBranch", "nightly") # should be selected
|
||||
click(1984, 449)
|
||||
|
||||
|
||||
def setup_settings_developer(click, pm: PubMaster, scroll=None):
|
||||
CP = car.CarParams()
|
||||
CP.alphaLongitudinalAvailable = True # show alpha long control toggle
|
||||
Params().put("CarParamsPersistent", CP.to_bytes())
|
||||
|
||||
setup_settings(click, pm)
|
||||
scroll(-20, 278, 950)
|
||||
click(278, 950)
|
||||
|
||||
|
||||
def setup_keyboard(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_developer(click, pm, scroll=scroll)
|
||||
click(1930, 470)
|
||||
|
||||
|
||||
def setup_pair_device(click, pm: PubMaster, scroll=None):
|
||||
click(1950, 800)
|
||||
|
||||
|
||||
def setup_offroad_alert(click, pm: PubMaster, scroll=None):
|
||||
put_update_params(Params())
|
||||
set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C')
|
||||
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal')
|
||||
for alert in OFFROAD_ALERTS:
|
||||
set_offroad_alert(alert, True)
|
||||
|
||||
setup_settings(click, pm)
|
||||
close_settings(click, pm)
|
||||
|
||||
|
||||
def setup_confirmation_dialog(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
click(1985, 791) # reset calibration
|
||||
|
||||
|
||||
def setup_experimental_mode_description(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_toggles(click, pm)
|
||||
click(1200, 280) # expand description for experimental mode
|
||||
|
||||
|
||||
def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_developer(click, pm, scroll=scroll)
|
||||
click(650, 960) # toggle IQ.Pilot longitudinal control
|
||||
|
||||
|
||||
def setup_settings_models(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
click(278, 840)
|
||||
|
||||
|
||||
def setup_settings_steering(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
click(278, 950)
|
||||
|
||||
|
||||
def setup_settings_cruise(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
scroll(-4, 278, 950)
|
||||
click(278, 860)
|
||||
|
||||
|
||||
def setup_settings_visuals(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
scroll(-20, 278, 950)
|
||||
click(278, 330)
|
||||
|
||||
|
||||
def setup_settings_display(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
scroll(-20, 278, 950)
|
||||
click(278, 420)
|
||||
|
||||
|
||||
def setup_settings_osm(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
scroll(-20, 278, 950)
|
||||
click(278, 520)
|
||||
|
||||
|
||||
def setup_settings_trips(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
scroll(-20, 278, 950)
|
||||
click(278, 630)
|
||||
|
||||
|
||||
def setup_settings_vehicle(click, pm: PubMaster, scroll=None):
|
||||
setup_settings(click, pm)
|
||||
scroll(-20, 278, 950)
|
||||
click(278, 750)
|
||||
|
||||
|
||||
def setup_onroad(click, pm: PubMaster, scroll=None):
|
||||
ds = messaging.new_message('deviceState')
|
||||
ds.deviceState.started = True
|
||||
|
||||
ps = messaging.new_message('pandaStates', 1)
|
||||
ps.pandaStates[0].pandaType = log.PandaState.PandaType.dos
|
||||
ps.pandaStates[0].ignitionLine = True
|
||||
|
||||
driverState = messaging.new_message('driverStateV2')
|
||||
driverState.driverStateV2.leftDriverData.faceOrientation = [0, 0, 0]
|
||||
|
||||
for _ in range(5):
|
||||
pm.send('deviceState', ds)
|
||||
pm.send('pandaStates', ps)
|
||||
pm.send('driverStateV2', driverState)
|
||||
ds.clear_write_flag()
|
||||
ps.clear_write_flag()
|
||||
driverState.clear_write_flag()
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
def setup_onroad_nav_demo(click, pm: PubMaster, scroll=None):
|
||||
setup_onroad(click, pm)
|
||||
publish_nav_scene(pm, NAV_SCENES[1])
|
||||
|
||||
|
||||
def setup_onroad_sidebar(click, pm: PubMaster, scroll=None):
|
||||
setup_onroad(click, pm)
|
||||
click(100, 100) # open sidebar
|
||||
|
||||
|
||||
def setup_onroad_alert(click, pm: PubMaster, size: log.SelfdriveState.AlertSize, text1: str, text2: str, status: log.SelfdriveState.AlertStatus):
|
||||
setup_onroad(click, pm)
|
||||
alert = messaging.new_message('selfdriveState')
|
||||
ss = alert.selfdriveState
|
||||
ss.alertSize = size
|
||||
ss.alertText1 = text1
|
||||
ss.alertText2 = text2
|
||||
ss.alertStatus = status
|
||||
for _ in range(5):
|
||||
pm.send('selfdriveState', alert)
|
||||
alert.clear_write_flag()
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
def setup_onroad_small_alert(click, pm: PubMaster, scroll=None):
|
||||
setup_onroad_alert(click, pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)
|
||||
|
||||
|
||||
def setup_onroad_medium_alert(click, pm: PubMaster, scroll=None):
|
||||
setup_onroad_alert(click, pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)
|
||||
|
||||
|
||||
def setup_onroad_full_alert(click, pm: PubMaster, scroll=None):
|
||||
setup_onroad_alert(click, pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)
|
||||
|
||||
|
||||
def setup_onroad_full_alert_multiline(click, pm: PubMaster, scroll=None):
|
||||
setup_onroad_alert(click, pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)
|
||||
|
||||
|
||||
def setup_onroad_full_alert_long_text(click, pm: PubMaster, scroll=None):
|
||||
setup_onroad_alert(click, pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)
|
||||
|
||||
|
||||
CASES = {
|
||||
"homescreen": setup_homescreen,
|
||||
"homescreen_paired": setup_homescreen,
|
||||
"homescreen_prime": setup_homescreen,
|
||||
"homescreen_update_available": setup_homescreen_update_available,
|
||||
"homescreen_unifont": setup_homescreen,
|
||||
"settings_device": setup_settings,
|
||||
"settings_network": setup_settings_network,
|
||||
"settings_network_advanced": setup_settings_network_advanced,
|
||||
"settings_toggles": setup_settings_toggles,
|
||||
"settings_software": setup_settings_software,
|
||||
"settings_software_download": setup_settings_software_download,
|
||||
"settings_software_release_notes": setup_settings_software_release_notes,
|
||||
"settings_software_branch_switcher": setup_settings_software_branch_switcher,
|
||||
"settings_developer": setup_settings_developer,
|
||||
"keyboard": setup_keyboard,
|
||||
"pair_device": setup_pair_device,
|
||||
"offroad_alert": setup_offroad_alert,
|
||||
"confirmation_dialog": setup_confirmation_dialog,
|
||||
"experimental_mode_description": setup_experimental_mode_description,
|
||||
"openpilot_long_confirmation_dialog": setup_openpilot_long_confirmation_dialog,
|
||||
"onroad": setup_onroad,
|
||||
"onroad_nav_demo": setup_onroad_nav_demo,
|
||||
"onroad_sidebar": setup_onroad_sidebar,
|
||||
"onroad_small_alert": setup_onroad_small_alert,
|
||||
"onroad_medium_alert": setup_onroad_medium_alert,
|
||||
"onroad_full_alert": setup_onroad_full_alert,
|
||||
"onroad_full_alert_multiline": setup_onroad_full_alert_multiline,
|
||||
"onroad_full_alert_long_text": setup_onroad_full_alert_long_text,
|
||||
}
|
||||
|
||||
# IQ.Pilot cases
|
||||
CASES.update({
|
||||
"settings_models": setup_settings_models,
|
||||
"settings_steering": setup_settings_steering,
|
||||
"settings_cruise": setup_settings_cruise,
|
||||
"settings_visuals": setup_settings_visuals,
|
||||
"settings_display": setup_settings_display,
|
||||
"settings_osm": setup_settings_osm,
|
||||
"settings_trips": setup_settings_trips,
|
||||
"settings_vehicle": setup_settings_vehicle,
|
||||
})
|
||||
|
||||
|
||||
class TestUI:
|
||||
def __init__(self):
|
||||
os.environ["SCALE"] = os.getenv("SCALE", "1")
|
||||
os.environ["BIG"] = "1"
|
||||
sys.modules["mouseinfo"] = False
|
||||
|
||||
def setup(self):
|
||||
# Seed minimal offroad state
|
||||
self.pm = build_ui_pubmaster()
|
||||
ds = messaging.new_message('deviceState')
|
||||
ds.deviceState.networkType = log.DeviceState.NetworkType.wifi
|
||||
for _ in range(5):
|
||||
self.pm.send('deviceState', ds)
|
||||
ds.clear_write_flag()
|
||||
time.sleep(0.05)
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
self.ui = pywinctl.getWindowsWithTitle("UI")[0]
|
||||
except Exception as e:
|
||||
print(f"failed to find ui window, assuming that it's in the top left (for Xvfb) {e}")
|
||||
self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0, 0, 2160, 1080)
|
||||
|
||||
def screenshot(self, name: str):
|
||||
full_screenshot = pyautogui.screenshot()
|
||||
cropped = full_screenshot.crop((self.ui.left, self.ui.top, self.ui.left + self.ui.width, self.ui.top + self.ui.height))
|
||||
cropped.save(SCREENSHOTS_DIR / f"{name}.png")
|
||||
|
||||
def click(self, x: int, y: int, *args, **kwargs):
|
||||
pyautogui.mouseDown(self.ui.left + x, self.ui.top + y, *args, **kwargs)
|
||||
time.sleep(0.01)
|
||||
pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs)
|
||||
|
||||
def scroll(self, clicks: int, x, y, *args, **kwargs):
|
||||
if clicks == 0:
|
||||
return
|
||||
click = -1 if clicks < 0 else 1 # -1 = down, 1 = up
|
||||
for _ in range(abs(clicks)):
|
||||
pyautogui.scroll(click, self.ui.left + x, self.ui.top + y, *args, **kwargs) # scroll for individual clicks since we need to delay between clicks
|
||||
time.sleep(0.01) # small delay between scroll clicks to work properly
|
||||
time.sleep(2) # wait for scroll to fully settle
|
||||
|
||||
@with_processes(["ui"])
|
||||
def test_ui(self, name, setup_case):
|
||||
self.setup()
|
||||
time.sleep(UI_DELAY) # wait for UI to start
|
||||
setup_case(self.click, self.pm, self.scroll)
|
||||
self.screenshot(name)
|
||||
|
||||
|
||||
def create_screenshots():
|
||||
if TEST_OUTPUT_DIR.exists():
|
||||
shutil.rmtree(TEST_OUTPUT_DIR)
|
||||
SCREENSHOTS_DIR.mkdir(parents=True)
|
||||
|
||||
t = TestUI()
|
||||
for name, setup in CASES.items():
|
||||
with OpenpilotPrefix():
|
||||
params = Params()
|
||||
params.put("DongleId", "123456789012345")
|
||||
|
||||
# Set branch name
|
||||
params.put("UpdaterCurrentDescription", VERSION)
|
||||
params.put("UpdaterNewDescription", VERSION)
|
||||
|
||||
# Set terms and training version (to skip onboarding)
|
||||
params.put("HasAcceptedTerms", terms_version)
|
||||
params.put("CompletedTrainingVersion", training_version)
|
||||
|
||||
# PrimeState uses PRIME_TYPE env var (not Params('PrimeType')) to avoid clobbering stock/Connect state.
|
||||
os.environ.pop("PRIME_TYPE", None)
|
||||
if name == "homescreen_paired":
|
||||
os.environ["PRIME_TYPE"] = "0" # NONE
|
||||
elif name == "homescreen_prime":
|
||||
os.environ["PRIME_TYPE"] = "2" # LITE
|
||||
elif name == "homescreen_unifont":
|
||||
params.put("LanguageSetting", "zh-CHT") # Traditional Chinese
|
||||
|
||||
t.test_ui(name, setup)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_screenshots()
|
||||
34
selfdrive/ui/tests/test_ui/template.html
Normal file
34
selfdrive/ui/tests/test_ui/template.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<html>
|
||||
|
||||
<style>
|
||||
.column {
|
||||
float: left;
|
||||
width: 50%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.row::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
{% for name, (image, ref_image) in cases.items() %}
|
||||
|
||||
<h1>{{name}}</h1>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<img class="image" src="{{ image }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
{% endfor %}
|
||||
</html>
|
||||
20
selfdrive/ui/tests/test_ui/test_scroll_panel2.py
Normal file
20
selfdrive/ui/tests/test_ui/test_scroll_panel2.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from collections import deque
|
||||
|
||||
from openpilot.system.ui.lib.scroll_panel2 import weighted_velocity
|
||||
|
||||
|
||||
def test_weighted_velocity_empty():
|
||||
assert weighted_velocity(deque()) == 0.0
|
||||
|
||||
|
||||
def test_weighted_velocity_single():
|
||||
assert weighted_velocity(deque([120.0])) == 120.0
|
||||
|
||||
|
||||
def test_weighted_velocity_two_samples():
|
||||
assert weighted_velocity(deque([100.0, 200.0])) == 130.0
|
||||
|
||||
|
||||
def test_weighted_velocity_three_samples_biases_older():
|
||||
velocity = weighted_velocity(deque([300.0, 180.0, 20.0]))
|
||||
assert velocity == 244.0
|
||||
Reference in New Issue
Block a user