mirror of
https://github.com/dzid26/sunnypilot.git
synced 2026-06-08 07:44:55 +08:00
# Conflicts: # .github/workflows/auto_pr_review.yaml # .gitignore # opendbc_repo # panda # selfdrive/ui/mici/layouts/home.py # selfdrive/ui/mici/layouts/onboarding.py # selfdrive/ui/mici/layouts/settings/device.py # selfdrive/ui/tests/diff/replay.py # selfdrive/ui/translations/app_fr.po # system/ui/mici_setup.py Sync: `commaai/opendbc:master` → `sunnypilot/opendbc:master` Sync: `commaai/panda:master` → `sunnypilot/panda:master`
152 lines
5.6 KiB
Python
Executable File
152 lines
5.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import os
|
|
import argparse
|
|
import coverage
|
|
import pyray as rl
|
|
|
|
from tqdm import tqdm
|
|
from typing import Literal
|
|
from collections.abc import Callable
|
|
from cereal.messaging import PubMaster
|
|
from openpilot.common.api import Api
|
|
from openpilot.common.basedir import BASEDIR
|
|
from openpilot.common.params import Params
|
|
from openpilot.common.prefix import OpenpilotPrefix
|
|
from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR
|
|
from openpilot.system.updated.updated import parse_release_notes
|
|
from openpilot.system.version import terms_version, training_version, terms_version_sp, sunnylink_consent_version
|
|
|
|
LayoutVariant = Literal["mici", "tizi"]
|
|
|
|
FPS = 60
|
|
HEADLESS = os.getenv("WINDOWED", "0") != "1"
|
|
|
|
|
|
def setup_state():
|
|
params = Params()
|
|
params.put("HasAcceptedTerms", terms_version)
|
|
params.put("CompletedTrainingVersion", training_version)
|
|
params.put("DongleId", "test123456789")
|
|
# Combined description for layouts that still use it (BIG home, settings/software)
|
|
params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30")
|
|
params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR))
|
|
params.put("HasAcceptedTermsSP", terms_version_sp)
|
|
params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version)
|
|
|
|
# Params for mici home
|
|
params.put("Version", "0.10.1")
|
|
params.put("GitBranch", "test-branch")
|
|
params.put("GitCommit", "abc12340ff9131237ba23a1d0fbd8edf9c80e87")
|
|
params.put("GitCommitDate", "'1732924800 2024-11-30 00:00:00 +0000'")
|
|
|
|
# Patch Api.get_token to return a static token so the pairing QR code is deterministic across runs
|
|
Api.get_token = lambda self, payload_extra=None, expiry_hours=0: "test_token"
|
|
|
|
|
|
def run_replay(variant: LayoutVariant) -> None:
|
|
if HEADLESS:
|
|
rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN)
|
|
os.environ["OFFSCREEN"] = "1" # Run UI without FPS limit (set before importing gui_app)
|
|
|
|
setup_state()
|
|
os.makedirs(DIFF_OUT_DIR, exist_ok=True)
|
|
|
|
from openpilot.selfdrive.ui.ui_state import ui_state, device # Import within OpenpilotPrefix context so param values are setup correctly
|
|
from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage
|
|
from openpilot.selfdrive.ui.tests.diff.replay_script import build_script
|
|
|
|
gui_app.init_window("ui diff test", fps=FPS)
|
|
|
|
# Dynamically import main layout based on variant
|
|
if variant == "mici":
|
|
from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout as MainLayout
|
|
else:
|
|
from openpilot.selfdrive.ui.layouts.main import MainLayout
|
|
main_layout = MainLayout()
|
|
|
|
# Disable interactive timeout — replay clicks use left_down=False so they never reset the timer,
|
|
# and after 30s of real wall-clock time the settings panel would close automatically.
|
|
device.set_override_interactive_timeout(99999)
|
|
|
|
pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"])
|
|
script = build_script(pm, main_layout, variant)
|
|
script_index = 0
|
|
|
|
send_fn: Callable | None = None
|
|
frame = 0
|
|
# Override raylib timing functions to return deterministic values based on frame count instead of real time
|
|
rl.get_frame_time = lambda: 1.0 / FPS
|
|
rl.get_time = lambda: frame / FPS
|
|
|
|
# Main loop to replay events and render frames
|
|
with tqdm(total=script[-1][0] + 1, desc="Replaying", unit="frame", disable=bool(os.getenv("CI"))) as pbar:
|
|
for _ in gui_app.render():
|
|
# Handle all events for the current frame
|
|
while script_index < len(script) and script[script_index][0] == frame:
|
|
_, event = script[script_index]
|
|
# Call setup function, if any
|
|
if event.setup:
|
|
event.setup()
|
|
# Send mouse events to the application
|
|
if event.mouse_events:
|
|
with gui_app._mouse._lock:
|
|
gui_app._mouse._events.extend(event.mouse_events)
|
|
# Update persistent send function
|
|
if event.send_fn is not None:
|
|
send_fn = event.send_fn
|
|
# Move to next script event
|
|
script_index += 1
|
|
|
|
# Keep sending cereal messages for persistent states (onroad, alerts)
|
|
if send_fn:
|
|
send_fn()
|
|
|
|
ui_state.update()
|
|
|
|
frame += 1
|
|
pbar.update(1)
|
|
|
|
if script_index >= len(script):
|
|
break
|
|
|
|
gui_app.close()
|
|
|
|
print(f"Total frames: {frame}")
|
|
print(f"Video saved to: {os.environ['RECORD_OUTPUT']}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--big', action='store_true', help='Use big UI layout (tizi/tici) instead of mici layout')
|
|
args = parser.parse_args()
|
|
|
|
variant: LayoutVariant = 'tizi' if args.big else 'mici'
|
|
|
|
if args.big:
|
|
os.environ["BIG"] = "1"
|
|
os.environ["RECORD"] = "1"
|
|
os.environ["RECORD_QUALITY"] = "0" # Use CRF 0 ("lossless" encode) for deterministic output across different machines
|
|
os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ.get("RECORD_OUTPUT", f"{variant}_ui_replay.mp4"))
|
|
|
|
print(f"Running {variant} UI replay...")
|
|
with OpenpilotPrefix():
|
|
sources = ["openpilot.system.ui"]
|
|
if variant == "mici":
|
|
sources.append("openpilot.selfdrive.ui.mici")
|
|
omit = ["**/*tizi*", "**/*tici*"] # exclude files containing "tizi" or "tici"
|
|
else:
|
|
sources.extend(["openpilot.selfdrive.ui.layouts", "openpilot.selfdrive.ui.onroad", "openpilot.selfdrive.ui.widgets"])
|
|
omit = ["**/*mici*"] # exclude files containing "mici"
|
|
cov = coverage.Coverage(source=sources, omit=omit)
|
|
with cov.collect():
|
|
run_replay(variant)
|
|
cov.save()
|
|
cov.report()
|
|
directory = os.path.join(DIFF_OUT_DIR, f"htmlcov-{variant}")
|
|
cov.html_report(directory=directory)
|
|
print(f"HTML report: {directory}/index.html")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|