Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plotjuggler: support segment names #23263

Merged
merged 6 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion tools/lib/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from tools.lib.auth_config import get_token
from tools.lib.api import CommaApi

SEGMENT_NAME_RE = r'[a-z0-9]{16}[|_][0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2}--[0-9]+'
ROUTE_NAME_RE = r'[a-z0-9]{16}[|_][0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2}'
SEGMENT_NAME_RE = r'{}--[0-9]+'.format(ROUTE_NAME_RE)
gregjhogan marked this conversation as resolved.
Show resolved Hide resolved
EXPLORER_FILE_RE = r'^({})--([a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME_RE)
OP_SEGMENT_DIR_RE = r'^({})$'.format(SEGMENT_NAME_RE)

Expand Down Expand Up @@ -179,6 +180,10 @@ def __init__(self, name_str):
self._route_name_str, num_str = self._segment_name_str.rsplit("--", 1)
self._num = int(num_str)

@property
def route_name(self):
return self._route_name_str

@property
def segment_num(self):
return self._num
Expand Down
26 changes: 15 additions & 11 deletions tools/plotjuggler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,32 @@ Once you've cloned and are in openpilot, this command will download PlotJuggler

```
$ ./juggle.py -h
usage: juggle.py [-h] [--demo] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [route_name] [segment_number] [segment_count]
usage: juggle.py [-h] [--demo] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [route_or_segment_name] [segment_count]

A helper to run PlotJuggler on openpilot routes

positional arguments:
route_name The route name to plot (cabana share URL accepted) (default: None)
segment_number The index of the segment to plot (default: None)
segment_count The number of segments to plot (default: 1)
route_or_segment_name
The route or segment name to plot (cabana share URL accepted) (default: None)
segment_count The number of segments to plot (default: None)

optional arguments:
-h, --help show this help message and exit
--demo Use the demo route instead of providing one (default: False)
--qlog Use qlogs (default: False)
--can Parse CAN data (default: False)
--stream Start PlotJuggler in streaming mode (default: False)
--layout [LAYOUT] Run PlotJuggler with a pre-defined layout (default: None)
-h, --help show this help message and exit
--demo Use the demo route instead of providing one (default: False)
--qlog Use qlogs (default: False)
--can Parse CAN data (default: False)
--stream Start PlotJuggler in streaming mode (default: False)
--layout [LAYOUT] Run PlotJuggler with a pre-defined layout (default: None)
```

Example:
Examples using route name:

`./juggle.py "4cf7a6ad03080c90|2021-09-29--13-46-36"`

Examples using segment name:

`./juggle.py "4cf7a6ad03080c90|2021-09-29--13-46-36--1"`

## Streaming

Explore live data from your car! Follow these steps to stream from your comma device to your laptop:
Expand Down
45 changes: 28 additions & 17 deletions tools/plotjuggler/juggle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,29 @@
import multiprocessing
import subprocess
import argparse
import re
from tempfile import NamedTemporaryFile

from common.basedir import BASEDIR
from selfdrive.test.process_replay.compare_logs import save_log
from tools.lib.api import CommaApi
from tools.lib.auth_config import get_token
from tools.lib.robust_logreader import RobustLogReader
from tools.lib.route import Route
from tools.lib.route import Route, RouteSegmentName, SEGMENT_NAME_RE, ROUTE_NAME_RE
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"

def get_route_and_segment_num(name):
gregjhogan marked this conversation as resolved.
Show resolved Hide resolved
if re.fullmatch(SEGMENT_NAME_RE, name):
rsn = RouteSegmentName(name)
return rsn.route_name, rsn.segment_num
if re.fullmatch(ROUTE_NAME_RE, name):
return name, None
raise Exception("invalid route or segment name:", name)

def load_segment(segment_name):
print(f"Loading {segment_name}")
if segment_name is None:
Expand Down Expand Up @@ -47,28 +56,31 @@ def start_juggler(fn=None, dbc=None, layout=None):
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):
if 'cabana' in route_name:
query = parse_qs(urlparse(route_name).query)
def juggle_route(route_or_segment_name, segment_count, qlog, can, layout):
segment_start = 0
if 'cabana' in route_or_segment_name:
query = parse_qs(urlparse(route_or_segment_name).query)
api = CommaApi(get_token())
logs = api.get(f'v1/route/{query["route"][0]}/log_urls?sig={query["sig"][0]}&exp={query["exp"][0]}')
elif route_name.startswith("http://") or route_name.startswith("https://") or os.path.isfile(route_name):
logs = [route_name]
elif route_or_segment_name.startswith("http://") or route_or_segment_name.startswith("https://") or os.path.isfile(route_or_segment_name):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to have RouteSegmentName handle cabana URLs too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a good candidate for a separate PR to enhance the Route class (I assume you need to maintain the cabana URL scheme for files in case you don't have API access to the device)

logs = [route_or_segment_name]
else:
route_name, segment_number = get_route_and_segment_num(route_or_segment_name)
segment_start = segment_number or 0
if segment_number is not None and segment_count is None:
segment_count = 1
r = Route(route_name)
logs = r.qlog_paths() if qlog else r.log_paths()

if segment_number is not None:
logs = logs[segment_number:segment_number+segment_count]
segment_end = segment_start + segment_count if segment_count else -1
logs = logs[segment_start:segment_end]

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':
logs = r.qlog_paths()
if segment_number is not None:
logs = logs[segment_number:segment_number+segment_count]
logs = r.qlog_paths()[segment_start:segment_end]
else:
print(f"Please try a different {'segment' if segment_number is not None else 'route'}")
print("Please try a different route or segment")
return

all_data = []
Expand Down Expand Up @@ -104,9 +116,8 @@ 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("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)
parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name to plot (cabana share URL accepted)")
parser.add_argument("segment_count", type=int, nargs='?', help="The number of segments to plot")
return parser

if __name__ == "__main__":
Expand All @@ -119,5 +130,5 @@ def get_arg_parser():
if args.stream:
start_juggler(layout=args.layout)
else:
route = DEMO_ROUTE if args.demo else args.route_name.strip()
juggle_route(route, args.segment_number, args.segment_count, args.qlog, args.can, args.layout)
route_or_segment_name = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip()
juggle_route(route_or_segment_name, args.segment_count, args.qlog, args.can, args.layout)