diff --git a/examples/longitudinal-profiles.py b/examples/longitudinal-profiles.py deleted file mode 100755 index e7a366d1e2..0000000000 --- a/examples/longitudinal-profiles.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env python3 -import io -import os -import time -import base64 -import argparse -import numpy as np -import matplotlib.pyplot as plt -from collections import defaultdict -from dataclasses import dataclass, asdict -from pathlib import Path - -from opendbc.car.structs import CarControl -from opendbc.car.panda_runner import PandaRunner -from opendbc.car.common.conversions import Conversions - -DT = 0.01 # step time (s) - -# TODOs -# - support lateral maneuvers -# - setup: show countdown? - - -class Ratekeeper: - def __init__(self, rate: float) -> None: - self.interval = 1. / rate - self.next_frame_time = time.monotonic() + self.interval - - def keep_time(self) -> bool: - lagged = False - remaining = self.next_frame_time - time.monotonic() - self.next_frame_time += self.interval - if remaining < -0.1: - print(f"lagging by {-remaining * 1000:.2f} ms") - lagged = True - - if remaining > 0: - time.sleep(remaining) - return lagged - -@dataclass -class Action: - accel: float # m/s^2 - duration: float # seconds - longControlState: CarControl.Actuators.LongControlState = CarControl.Actuators.LongControlState.pid - - def get_msgs(self): - return [ - (t, CarControl( - enabled=True, - longActive=True, - actuators=CarControl.Actuators( - accel=self.accel, - longControlState=self.longControlState, - ), - )) - for t in np.linspace(0, self.duration, int(self.duration/DT)) - ] - -@dataclass -class Maneuver: - description: str - actions: list[Action] - repeat: int = 1 - initial_speed: float = 0. # m/s - - def get_msgs(self): - t0 = 0 - for action in self.actions: - for lt, msg in action.get_msgs(): - yield lt + t0, msg - t0 += lt - -MANEUVERS = [ - Maneuver( - "creep: alternate between +1m/ss and -1m/ss", - [ - Action(1, 2), Action(-1, 2), - Action(1, 2), Action(-1, 2), - Action(1, 2), Action(-1, 2), - ], - repeat=2, - initial_speed=0., - ), - Maneuver( - "brake step response: -1m/ss from 20mph", - [Action(0, 2), Action(-1, 3)], - repeat=3, - initial_speed=20. * Conversions.MPH_TO_MS, - ), - Maneuver( - "brake step response: -4m/ss from 20mph", - [Action(0, 2), Action(-4, 3)], - repeat=3, - initial_speed=20. * Conversions.MPH_TO_MS, - ), - Maneuver( - "gas step response: +1m/ss from 20mph", - [Action(0, 2), Action(1, 3)], - repeat=3, - initial_speed=20. * Conversions.MPH_TO_MS, - ), - Maneuver( - "gas step response: +4m/ss from 20mph", - [Action(0, 2), Action(4, 3)], - repeat=3, - initial_speed=20. * Conversions.MPH_TO_MS, - ), -] - -def report(args, logs, fp): - output_path = Path(__file__).resolve().parent / "longitudinal_reports" - output_fn = args.output or output_path / f"{fp}_{time.strftime('%Y%m%d-%H_%M_%S')}.html" - output_path.mkdir(exist_ok=True) - with open(output_fn, "w") as f: - f.write("

Longitudinal maneuver report

\n") - f.write(f"

{fp}

\n") - if args.desc: - f.write(f"

{args.desc}

") - for description, runs in logs.items(): - f.write("
\n") - f.write(f"

{description}

\n") - for run, log in runs.items(): - f.write(f"

Run #{int(run)+1}

\n") - plt.rcParams['font.size'] = 40 - fig = plt.figure(figsize=(30, 25)) - ax = fig.subplots(4, 1, sharex=True, gridspec_kw={'hspace': 0, 'height_ratios': [5, 3, 1, 1]}) - - ax[0].grid(linewidth=4) - ax[0].plot(log["t"], log["carControl.actuators.accel"], label='accel command', linewidth=6) - ax[0].plot(log["t"], log["carState.aEgo"], label='aEgo', linewidth=6) - ax[0].set_ylabel('Acceleration (m/s^2)') - #ax[0].set_ylim(-6.5, 6.5) - ax[0].legend() - - ax[1].grid(linewidth=4) - ax[1].plot(log["t"], log["carState.vEgo"], 'g', label='vEgo', linewidth=6) - ax[1].set_ylabel('Velocity (m/s)') - ax[1].legend() - - ax[2].plot(log["t"], log["carControl.enabled"], label='enabled', linewidth=6) - ax[3].plot(log["t"], log["carState.gasPressed"], label='gasPressed', linewidth=6) - ax[3].plot(log["t"], log["carState.brakePressed"], label='brakePressed', linewidth=6) - for i in (2, 3): - ax[i].set_yticks([0, 1], minor=False) - ax[i].set_ylim(-1, 2) - ax[i].legend() - - ax[-1].set_xlabel("Time (s)") - fig.tight_layout() - - buffer = io.BytesIO() - fig.savefig(buffer, format='png') - buffer.seek(0) - f.write(f"\n") - - import json - f.write(f"

{json.dumps(logs)}

") - print(f"\nReport written to {output_fn}\n") - -def main(args): - with PandaRunner() as p: - print("\n\n") - - maneuvers = MANEUVERS - if len(args.maneuvers): - maneuvers = [MANEUVERS[i-1] for i in set(args.maneuvers)] - - logs = {} - rk = Ratekeeper(int(1./DT)) - for i, m in enumerate(maneuvers): - logs[m.description] = {} - print(f"Running {i+1}/{len(MANEUVERS)} '{m.description}'") - for run in range(m.repeat): - print(f"- run #{run}") - print("- setting up, engage cruise") - ready_cnt = 0 - for _ in range(int(2*60./DT)): - cs = p.read(strict=False) - cc = CarControl( - enabled=True, - longActive=True, - actuators=CarControl.Actuators( - accel=(m.initial_speed - cs.vEgo)*0.8, - longControlState=CarControl.Actuators.LongControlState.pid, - ), - ) - if m.initial_speed < 0.1: - cc.actuators.accel = -2 - cc.actuators.longControlState = CarControl.Actuators.LongControlState.stopping - p.write(cc) - - ready = cs.cruiseState.enabled and not cs.cruiseState.standstill and ((m.initial_speed - 0.6) < cs.vEgo < (m.initial_speed + 0.6)) - ready_cnt = (ready_cnt+1) if ready else 0 - if ready_cnt > (2./DT): - break - rk.keep_time() - else: - print("ERROR: failed to setup") - continue - - print("- executing maneuver") - logs[m.description][run] = defaultdict(list) - for t, cc in m.get_msgs(): - cs = p.read() - p.write(cc) - - logs[m.description][run]["t"].append(t) - to_log = {"carControl": cc, "carState": cs, "carControl.actuators": cc.actuators, - "carControl.cruiseControl": cc.cruiseControl, "carState.cruiseState": cs.cruiseState} - for k, v in to_log.items(): - for k2, v2 in asdict(v).items(): - logs[m.description][run][f"{k}.{k2}"].append(v2) - - rk.keep_time() - - print("writing out report") - with open('/tmp/logs.json', 'w') as f: - import json - json.dump(logs, f, indent=2) - report(args, logs, p.CI.CP.carFingerprint) - - -if __name__ == "__main__": - maneuver_help = "\n".join([f"{i+1}. {m.description}" for i, m in enumerate(MANEUVERS)]) - parser = argparse.ArgumentParser(description="A tool for longitudinal control testing.", - formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('--desc', help="Extra description to include in report.") - parser.add_argument('--output', help="Write out report to this file.", default=None) - parser.add_argument('maneuvers', nargs='*', type=int, default=None, help=f'Deafult is all.\n{maneuver_help}') - args = parser.parse_args() - print(args) - - if "REPORT_TEST" in os.environ: - with open(os.environ["REPORT_TEST"]) as f: - import json - logs = json.loads(f.read().split("none'>")[1].split('

')[0]) - report(args, logs, "testing") - exit() - - assert args.output is None or args.output.endswith(".html"), "Output filename must end with '.html'" - - main(args)