@ -2,9 +2,13 @@
import os
import os
import sys
import sys
import multiprocessing
import multiprocessing
import platform
import shutil
import subprocess
import subprocess
import tarfile
import tempfile
import requests
import argparse
import argparse
from tempfile import NamedTemporaryFile
from common . basedir import BASEDIR
from common . basedir import BASEDIR
from selfdrive . test . process_replay . compare_logs import save_log
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__ ) )
juggle_dir = os . path . dirname ( os . path . realpath ( __file__ ) )
DEMO_ROUTE = " 4cf7a6ad03080c90|2021-09-29--13-46-36 "
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 ) :
def load_segment ( segment_name ) :
print ( f " Loading { segment_name } " )
if segment_name is None :
if segment_name is None :
return [ ]
return [ ]
@ -29,25 +56,26 @@ def load_segment(segment_name):
print ( f " Error parsing { segment_name } : { e } " )
print ( f " Error parsing { segment_name } : { e } " )
return [ ]
return [ ]
def start_juggler ( fn = None , dbc = None , layout = None ) :
def start_juggler ( fn = None , dbc = None , layout = None ) :
env = os . environ . copy ( )
env = os . environ . copy ( )
env [ " BASEDIR " ] = BASEDIR
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 :
if dbc :
env [ " DBC_NAME " ] = dbc
env [ " DBC_NAME " ] = dbc
extra_args = [ ]
extra_args = " "
if fn is not None :
if fn is not None :
extra_args . append ( f ' -d { fn } ' )
extra_args + = f " -d { fn } "
if layout is not None :
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 ) :
def juggle_route ( route_name , segment_number , segment_count , qlog , can , layout ) :
# TODO: abstract out the cabana stuff
if ' cabana ' in route_name :
if ' cabana ' in route_name :
query = parse_qs ( urlparse ( route_name ) . query )
query = parse_qs ( urlparse ( route_name ) . query )
api = CommaApi ( get_token ( ) )
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 ]
logs = logs [ segment_number : segment_number + segment_count ]
if None in logs :
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) : " )
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 fallback_ answer == ' y ' :
if ans == ' y ' :
logs = r . qlog_paths ( )
logs = r . qlog_paths ( )
if segment_number is not None :
if segment_number is not None :
logs = logs [ segment_number : segment_number + segment_count ]
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 :
try :
DBC = __import__ ( f " selfdrive.car. { cp . carParams . carName } .values " , fromlist = [ ' DBC ' ] ) . DBC
DBC = __import__ ( f " selfdrive.car. { cp . carParams . carName } .values " , fromlist = [ ' DBC ' ] ) . DBC
dbc = DBC [ cp . carParams . carFingerprint ] [ ' pt ' ]
dbc = DBC [ cp . carParams . carFingerprint ] [ ' pt ' ]
except ( ImportError , KeyError , AttributeError ) :
except Exception :
pass
pass
break
break
tempfile = NamedTemporaryFile ( suffix = ' .rlog ' , dir = juggle_dir )
with tempfile . NamedTemporaryFile ( suffix = ' .rlog ' , dir = juggle_dir ) as tmp :
save_log ( te mpfile . name , all_data , compress = False )
save_log ( tmp . name , all_data , compress = False )
del all_data
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 " ,
parser = argparse . ArgumentParser ( description = " A helper to run PlotJuggler on openpilot routes " ,
formatter_class = argparse . ArgumentDefaultsHelpFormatter )
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 ( " --can " , action = " store_true " , help = " Parse CAN data " )
parser . add_argument ( " --stream " , action = " store_true " , help = " Start PlotJuggler in streaming mode " )
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 ( " --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 ( " 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_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 )
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 :
if len ( sys . argv ) == 1 :
arg_parser . print_help ( )
parser . print_help ( )
sys . exit ( )
args = parser . parse_args ( )
if args . install :
install ( )
sys . exit ( )
sys . exit ( )
args = arg_parser . parse_args ( sys . argv [ 1 : ] )
if args . stream :
if args . stream :
start_juggler ( layout = args . layout )
start_juggler ( layout = args . layout )