@ -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 )