|  |  |  | @ -2,9 +2,13 @@ | 
			
		
	
		
			
				
					|  |  |  |  | import os | 
			
		
	
		
			
				
					|  |  |  |  | import sys | 
			
		
	
		
			
				
					|  |  |  |  | import multiprocessing | 
			
		
	
		
			
				
					|  |  |  |  | import platform | 
			
		
	
		
			
				
					|  |  |  |  | import shutil | 
			
		
	
		
			
				
					|  |  |  |  | import subprocess | 
			
		
	
		
			
				
					|  |  |  |  | import tarfile | 
			
		
	
		
			
				
					|  |  |  |  | import tempfile | 
			
		
	
		
			
				
					|  |  |  |  | import requests | 
			
		
	
		
			
				
					|  |  |  |  | import argparse | 
			
		
	
		
			
				
					|  |  |  |  | from tempfile import NamedTemporaryFile | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | from common.basedir import BASEDIR | 
			
		
	
		
			
				
					|  |  |  |  | from selfdrive.test.process_replay.compare_logs import save_log | 
			
		
	
	
		
			
				
					|  |  |  | @ -17,9 +21,32 @@ from urllib.parse import urlparse, parse_qs | 
			
		
	
		
			
				
					|  |  |  |  | juggle_dir = os.path.dirname(os.path.realpath(__file__)) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" | 
			
		
	
		
			
				
					|  |  |  |  | RELEASES_URL="https://github.com/commaai/PlotJuggler/releases/download/latest" | 
			
		
	
		
			
				
					|  |  |  |  | INSTALL_DIR = os.path.join(juggle_dir, "bin") | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def install(): | 
			
		
	
		
			
				
					|  |  |  |  |   m = f"{platform.system()}-{platform.machine()}" | 
			
		
	
		
			
				
					|  |  |  |  |   supported = ("Linux-x86_64", "Darwin-arm64", "Darwin-x86_64") | 
			
		
	
		
			
				
					|  |  |  |  |   if m not in supported: | 
			
		
	
		
			
				
					|  |  |  |  |     raise Exception(f"Unsupported platform: '{m}'. Supported platforms: {supported}") | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   if os.path.exists(INSTALL_DIR): | 
			
		
	
		
			
				
					|  |  |  |  |     shutil.rmtree(INSTALL_DIR) | 
			
		
	
		
			
				
					|  |  |  |  |   os.mkdir(INSTALL_DIR) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   url = os.path.join(RELEASES_URL, m + ".tar.gz") | 
			
		
	
		
			
				
					|  |  |  |  |   with requests.get(url, stream=True) as r, tempfile.NamedTemporaryFile() as tmp: | 
			
		
	
		
			
				
					|  |  |  |  |     r.raise_for_status() | 
			
		
	
		
			
				
					|  |  |  |  |     with open(tmp.name, 'wb') as tmpf: | 
			
		
	
		
			
				
					|  |  |  |  |       for chunk in r.iter_content(chunk_size=1024*1024): | 
			
		
	
		
			
				
					|  |  |  |  |         tmpf.write(chunk) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     with tarfile.open(tmp.name) as tar: | 
			
		
	
		
			
				
					|  |  |  |  |       tar.extractall(path=INSTALL_DIR) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def load_segment(segment_name): | 
			
		
	
		
			
				
					|  |  |  |  |   print(f"Loading {segment_name}") | 
			
		
	
		
			
				
					|  |  |  |  |   if segment_name is None: | 
			
		
	
		
			
				
					|  |  |  |  |     return [] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -29,25 +56,26 @@ def load_segment(segment_name): | 
			
		
	
		
			
				
					|  |  |  |  |     print(f"Error parsing {segment_name}: {e}") | 
			
		
	
		
			
				
					|  |  |  |  |     return [] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def start_juggler(fn=None, dbc=None, layout=None): | 
			
		
	
		
			
				
					|  |  |  |  |   env = os.environ.copy() | 
			
		
	
		
			
				
					|  |  |  |  |   env["BASEDIR"] = BASEDIR | 
			
		
	
		
			
				
					|  |  |  |  |   pj = os.getenv("PLOTJUGGLER_PATH", os.path.join(juggle_dir, "bin/plotjuggler")) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   env["PATH"] = f"{INSTALL_DIR}:{os.getenv('PATH', '')}" | 
			
		
	
		
			
				
					|  |  |  |  |   if dbc: | 
			
		
	
		
			
				
					|  |  |  |  |     env["DBC_NAME"] = dbc | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   extra_args = [] | 
			
		
	
		
			
				
					|  |  |  |  |   extra_args = "" | 
			
		
	
		
			
				
					|  |  |  |  |   if fn is not None: | 
			
		
	
		
			
				
					|  |  |  |  |     extra_args.append(f'-d {fn}') | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     extra_args += f"-d {fn}" | 
			
		
	
		
			
				
					|  |  |  |  |   if layout is not None: | 
			
		
	
		
			
				
					|  |  |  |  |     extra_args.append(f'-l {layout}') | 
			
		
	
		
			
				
					|  |  |  |  |     extra_args += f"-l {layout}" | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   subprocess.call(f'plotjuggler --plugin_folders {INSTALL_DIR} {extra_args}', | 
			
		
	
		
			
				
					|  |  |  |  |                   shell=True, env=env, cwd=juggle_dir) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   extra_args = " ".join(extra_args) | 
			
		
	
		
			
				
					|  |  |  |  |   subprocess.call(f'{pj} --plugin_folders {os.path.join(juggle_dir, "bin")} {extra_args}', shell=True, env=env, cwd=juggle_dir) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def juggle_route(route_name, segment_number, segment_count, qlog, can, layout): | 
			
		
	
		
			
				
					|  |  |  |  |   # TODO: abstract out the cabana stuff | 
			
		
	
		
			
				
					|  |  |  |  |   if 'cabana' in route_name: | 
			
		
	
		
			
				
					|  |  |  |  |     query = parse_qs(urlparse(route_name).query) | 
			
		
	
		
			
				
					|  |  |  |  |     api = CommaApi(get_token()) | 
			
		
	
	
		
			
				
					|  |  |  | @ -62,8 +90,8 @@ def juggle_route(route_name, segment_number, segment_count, qlog, can, layout): | 
			
		
	
		
			
				
					|  |  |  |  |     logs = logs[segment_number:segment_number+segment_count] | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   if None in logs: | 
			
		
	
		
			
				
					|  |  |  |  |     fallback_answer = input("At least one of the rlogs in this segment does not exist, would you like to use the qlogs? (y/n) : ") | 
			
		
	
		
			
				
					|  |  |  |  |     if fallback_answer == 'y': | 
			
		
	
		
			
				
					|  |  |  |  |     ans = input(f"{logs.count(None)}/{len(logs)} of the rlogs in this segment are missing, would you like to fall back to the qlogs? (y/n) ") | 
			
		
	
		
			
				
					|  |  |  |  |     if ans == 'y': | 
			
		
	
		
			
				
					|  |  |  |  |       logs = r.qlog_paths() | 
			
		
	
		
			
				
					|  |  |  |  |       if segment_number is not None: | 
			
		
	
		
			
				
					|  |  |  |  |         logs = logs[segment_number:segment_number+segment_count] | 
			
		
	
	
		
			
				
					|  |  |  | @ -85,17 +113,17 @@ def juggle_route(route_name, segment_number, segment_count, qlog, can, layout): | 
			
		
	
		
			
				
					|  |  |  |  |     try: | 
			
		
	
		
			
				
					|  |  |  |  |       DBC = __import__(f"selfdrive.car.{cp.carParams.carName}.values", fromlist=['DBC']).DBC | 
			
		
	
		
			
				
					|  |  |  |  |       dbc = DBC[cp.carParams.carFingerprint]['pt'] | 
			
		
	
		
			
				
					|  |  |  |  |     except (ImportError, KeyError, AttributeError): | 
			
		
	
		
			
				
					|  |  |  |  |     except Exception: | 
			
		
	
		
			
				
					|  |  |  |  |       pass | 
			
		
	
		
			
				
					|  |  |  |  |     break | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   tempfile = NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) | 
			
		
	
		
			
				
					|  |  |  |  |   save_log(tempfile.name, all_data, compress=False) | 
			
		
	
		
			
				
					|  |  |  |  |   del all_data | 
			
		
	
		
			
				
					|  |  |  |  |   with tempfile.NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) as tmp: | 
			
		
	
		
			
				
					|  |  |  |  |     save_log(tmp.name, all_data, compress=False) | 
			
		
	
		
			
				
					|  |  |  |  |     del all_data | 
			
		
	
		
			
				
					|  |  |  |  |     start_juggler(tmp.name, dbc, layout) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   start_juggler(tempfile.name, dbc, layout) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | def get_arg_parser(): | 
			
		
	
		
			
				
					|  |  |  |  | if __name__ == "__main__": | 
			
		
	
		
			
				
					|  |  |  |  |   parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes", | 
			
		
	
		
			
				
					|  |  |  |  |                                    formatter_class=argparse.ArgumentDefaultsHelpFormatter) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -104,17 +132,18 @@ def get_arg_parser(): | 
			
		
	
		
			
				
					|  |  |  |  |   parser.add_argument("--can", action="store_true", help="Parse CAN data") | 
			
		
	
		
			
				
					|  |  |  |  |   parser.add_argument("--stream", action="store_true", help="Start PlotJuggler in streaming mode") | 
			
		
	
		
			
				
					|  |  |  |  |   parser.add_argument("--layout", nargs='?', help="Run PlotJuggler with a pre-defined layout") | 
			
		
	
		
			
				
					|  |  |  |  |   parser.add_argument("--install", action="store_true", help="Install or update PlotJuggler + plugins") | 
			
		
	
		
			
				
					|  |  |  |  |   parser.add_argument("route_name", nargs='?', help="The route name to plot (cabana share URL accepted)") | 
			
		
	
		
			
				
					|  |  |  |  |   parser.add_argument("segment_number", type=int, nargs='?', help="The index of the segment to plot") | 
			
		
	
		
			
				
					|  |  |  |  |   parser.add_argument("segment_count", type=int, nargs='?', help="The number of segments to plot", default=1) | 
			
		
	
		
			
				
					|  |  |  |  |   return parser | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | if __name__ == "__main__": | 
			
		
	
		
			
				
					|  |  |  |  |   arg_parser = get_arg_parser() | 
			
		
	
		
			
				
					|  |  |  |  |   if len(sys.argv) == 1: | 
			
		
	
		
			
				
					|  |  |  |  |     arg_parser.print_help() | 
			
		
	
		
			
				
					|  |  |  |  |     parser.print_help() | 
			
		
	
		
			
				
					|  |  |  |  |     sys.exit() | 
			
		
	
		
			
				
					|  |  |  |  |   args = parser.parse_args() | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   if args.install: | 
			
		
	
		
			
				
					|  |  |  |  |     install() | 
			
		
	
		
			
				
					|  |  |  |  |     sys.exit() | 
			
		
	
		
			
				
					|  |  |  |  |   args = arg_parser.parse_args(sys.argv[1:]) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   if args.stream: | 
			
		
	
		
			
				
					|  |  |  |  |     start_juggler(layout=args.layout) | 
			
		
	
	
		
			
				
					|  |  |  | 
 |