From 35e1e8ecd2e2a8a458467ecb3ec18730610da18a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 11:40:22 -0800 Subject: [PATCH 1/7] SegmentRange: __str__ get preferred format (#31126) get canonical --- tools/lib/route.py | 3 +++ tools/lib/tests/test_logreader.py | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/tools/lib/route.py b/tools/lib/route.py index d3df93ccda..09eb7f5426 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -255,3 +255,6 @@ class SegmentRange: @property def selector(self): return self.m.group("selector") + + def __str__(self): + return f"{self.dongle_id}/{self.timestamp}" + (f"/{self._slice}" if self._slice else "") + (f"/{self.selector}" if self.selector else "") diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index 74ee82d152..6ec3edc9ac 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -46,6 +46,18 @@ class TestLogReader(unittest.TestCase): segs = parse_slice(sr, route) self.assertListEqual(list(segs), expected) + @parameterized.expand([ + (f"{TEST_ROUTE}", f"{TEST_ROUTE}"), + (f"{TEST_ROUTE.replace('/', '|')}", f"{TEST_ROUTE}"), + (f"{TEST_ROUTE}--5", f"{TEST_ROUTE}/5"), + (f"{TEST_ROUTE}/0/q", f"{TEST_ROUTE}/0/q"), + (f"{TEST_ROUTE}/5:6/r", f"{TEST_ROUTE}/5:6/r"), + (f"{TEST_ROUTE}/5", f"{TEST_ROUTE}/5"), + ]) + def test_canonical_name(self, identifier, expected): + sr = SegmentRange(identifier) + self.assertEqual(str(sr), expected) + def test_direct_parsing(self): qlog = tempfile.NamedTemporaryFile(mode='wb', delete=False) From 65490bb3c55fa81d5144eba2c09f054b73179cc4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 12:53:19 -0800 Subject: [PATCH 2/7] plotjuggler: auto fallback to qlogs with prompt (#31110) * qlog fallback * show a warning for that * qlog fallback * fix * wording --- tools/lib/helpers.py | 2 +- tools/lib/logreader.py | 62 +++++++++++++++++++++++++++++-------- tools/plotjuggler/juggle.py | 4 +-- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index c184efe6a2..e2c8d07dd8 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -11,7 +11,7 @@ class RE: SEGMENT_NAME = r'{}(?:--|/)(?P[0-9]+)'.format(ROUTE_NAME) INDEX = r'-?[0-9]+' SLICE = r'(?P{})?:?(?P{})?:?(?P{})?'.format(INDEX, INDEX, INDEX) - SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?/?(?P([qr]))?'.format(ROUTE_NAME, SLICE) + SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?/?(?P([qra]))?'.format(ROUTE_NAME, SLICE) BOOTLOG_NAME = ROUTE_NAME EXPLORER_FILE = r'^(?P{})--(?P[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index c1f4f0f4a9..e28f41341c 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -16,6 +16,7 @@ from typing import Iterable, Iterator, List, Type from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log +from openpilot.common.swaglog import cloudlog from openpilot.tools.lib.comma_car_segments import get_url as get_comma_segments_url from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.filereader import FileReader, file_exists @@ -71,8 +72,8 @@ class _LogFileReader: class ReadMode(enum.StrEnum): RLOG = "r" # only read rlogs QLOG = "q" # only read qlogs - #AUTO = "a" # default to rlogs, fallback to qlogs, not supported yet - + AUTO = "a" # default to rlogs, fallback to qlogs + AUTO_INTERACIVE = "i" # default to rlogs, fallback to qlogs with a prompt from the user def create_slice_from_string(s: str): m = re.fullmatch(RE.SLICE, s) @@ -86,31 +87,60 @@ def create_slice_from_string(s: str): return start return slice(start, end, step) +def auto_strategy(rlog_paths, qlog_paths, interactive): + # auto select logs based on availability + if any(rlog is None or not file_exists(rlog) for rlog in rlog_paths): + if interactive: + if input("Some rlogs were not found, would you like to fallback to qlogs for those segments? (y/n) ").lower() != "y": + return rlog_paths + else: + cloudlog.warning("Some rlogs were not found, falling back to qlogs for those segments...") + + return [rlog if (rlog is not None and file_exists(rlog)) else (qlog if (qlog is not None and file_exists(qlog)) else None) + for (rlog, qlog) in zip(rlog_paths, qlog_paths, strict=True)] + return rlog_paths + +def apply_strategy(mode: ReadMode, rlog_paths, qlog_paths): + if mode == ReadMode.RLOG: + return rlog_paths + elif mode == ReadMode.QLOG: + return qlog_paths + elif mode == ReadMode.AUTO: + return auto_strategy(rlog_paths, qlog_paths, False) + elif mode == ReadMode.AUTO_INTERACIVE: + return auto_strategy(rlog_paths, qlog_paths, True) + def parse_slice(sr: SegmentRange, route: Route): segs = np.arange(route.max_seg_number+1) s = create_slice_from_string(sr._slice) return segs[s] if isinstance(s, slice) else [segs[s]] -def comma_api_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): +def comma_api_source(sr: SegmentRange, route: Route, mode: ReadMode): segs = parse_slice(sr, route) - log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() + rlog_paths = [route.log_paths()[seg] for seg in segs] + qlog_paths = [route.log_paths()[seg] for seg in segs] - invalid_segs = [seg for seg in segs if log_paths[seg] is None] + return apply_strategy(mode, rlog_paths, qlog_paths) - assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" +def internal_source(sr: SegmentRange, route: Route, mode: ReadMode): + segs = parse_slice(sr, route) - return [(log_paths[seg]) for seg in segs] + def get_internal_url(sr: SegmentRange, seg, file): + return f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{file}.bz2" -def internal_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): - segs = parse_slice(sr, route) + rlog_paths = [get_internal_url(sr, seg, "rlog") for seg in segs] + qlog_paths = [get_internal_url(sr, seg, "qlog") for seg in segs] - return [f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2" for seg in segs] + return apply_strategy(mode, rlog_paths, qlog_paths) -def openpilotci_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): +def openpilotci_source(sr: SegmentRange, route: Route, mode: ReadMode): segs = parse_slice(sr, route) - return [get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog') for seg in segs] + rlog_paths = [get_url(sr.route_name, seg, "rlog") for seg in segs] + qlog_paths = [get_url(sr.route_name, seg, "qlog") for seg in segs] + + return apply_strategy(mode, rlog_paths, qlog_paths) def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): segs = parse_slice(sr, route) @@ -120,10 +150,13 @@ def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG def direct_source(file_or_url): return [file_or_url] +def get_invalid_files(files): + return [f for f in files if f is None or not file_exists(f)] + def check_source(source, *args): try: files = source(*args) - assert all(file_exists(f) for f in files) + assert len(get_invalid_files(files)) == 0 return True, files except Exception: return False, None @@ -209,6 +242,9 @@ class LogReader: def reset(self): self.logreader_identifiers = self._parse_identifiers(self.identifier) + invalid_count = len(get_invalid_files(self.logreader_identifiers)) + assert invalid_count == 0, f"{invalid_count}/{len(self.logreader_identifiers)} invalid log(s) found, please ensure all logs \ +are uploaded or auto fallback to qlogs with '/a' selector at the end of the route name." @staticmethod def from_bytes(dat): diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 292db5a50a..cc21095414 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -13,7 +13,7 @@ from functools import partial from openpilot.common.basedir import BASEDIR from openpilot.tools.lib.helpers import save_log -from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.logreader import LogReader, ReadMode juggle_dir = os.path.dirname(os.path.realpath(__file__)) @@ -73,7 +73,7 @@ def process(can, lr): return [d for d in lr if can or d.which() not in ['can', 'sendcan']] def juggle_route(route_or_segment_name, can, layout, dbc=None): - sr = LogReader(route_or_segment_name) + sr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACIVE) all_data = sr.run_across_segments(24, partial(process, can)) From 0234cadec3aefdca2e71c251ff6f8885a247c34a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 12:55:50 -0800 Subject: [PATCH 3/7] LogReader: remove redirect message for internal data (#31088) fix logging --- tools/lib/url_file.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 97c0a639a7..5c6f187eee 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -1,3 +1,4 @@ +import logging import os import time import threading @@ -12,6 +13,7 @@ from openpilot.system.hardware.hw import Paths K = 1000 CHUNK_SIZE = 1000 * K +logging.getLogger("urllib3").setLevel(logging.WARNING) def hash_256(link): hsh = str(sha256((link.split("?")[0]).encode('utf-8')).hexdigest()) From ebc916e25c2136d9cadbb98672442cfbc5e84ea5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 23 Jan 2024 14:23:11 -0800 Subject: [PATCH 4/7] Export fingerprints --- selfdrive/car/chrysler/fingerprints.py | 14 ++++++++++++++ selfdrive/car/honda/fingerprints.py | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 1a04e79c7d..9ef991e8ed 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -66,6 +66,7 @@ FW_VERSIONS = { (Ecu.combinationMeter, 0x742, None): [ b'68402971AD', b'68454144AD', + b'68454152AB', ], (Ecu.srs, 0x744, None): [ b'68355363AB', @@ -87,6 +88,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'05035707AA', b'68495807AA', + b'68495807AB', ], }, CAR.RAM_1500: { @@ -98,6 +100,7 @@ FW_VERSIONS = { b'68294063AH', b'68294063AI', b'68434846AC', + b'68434849AC', b'68434858AC', b'68434859AC', b'68434860AC', @@ -112,7 +115,10 @@ FW_VERSIONS = { b'68453513AC', b'68453513AD', b'68453514AD', + b'68505633AB', + b'68510277AG', b'68510280AG', + b'68510282AG', b'68510282AH', b'68510283AG', b'68527346AE', @@ -120,6 +126,7 @@ FW_VERSIONS = { b'68527375AD', b'68527382AE', b'68527387AE', + b'68631942AA', ], (Ecu.srs, 0x744, None): [ b'68428609AB', @@ -192,15 +199,19 @@ FW_VERSIONS = { b'05149591AD ', b'05149591AE ', b'05149592AE ', + b'05149600AD ', b'05149846AA ', b'05149848AA ', + b'05190341AD', b'68378695AJ ', b'68378696AJ ', b'68378701AI ', b'68378748AL ', b'68378758AM ', b'68448163AJ', + b'68448163AK', b'68448163AL', + b'68448165AG', b'68448165AK', b'68455119AC ', b'68455145AC ', @@ -218,11 +229,13 @@ FW_VERSIONS = { b'68539650AF', b'68586101AA ', b'68586105AB ', + b'68629926AC ', ], (Ecu.transmission, 0x7e1, None): [ b'05035706AD', b'05036069AA', b'05149536AC', + b'05149537AC', b'68360078AL', b'68360080AM', b'68360081AM', @@ -240,6 +253,7 @@ FW_VERSIONS = { b'68520867AE', b'68520867AF', b'68540431AB', + b'68629936AC', ], }, CAR.RAM_HD: { diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 0d174f91fb..25fb88aa39 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -928,6 +928,8 @@ FW_VERSIONS = { (Ecu.transmission, 0x18da1ef1, None): [ b'28101-5EY-A050\x00\x00', b'28101-5EY-A100\x00\x00', + b'28101-5EY-A430\x00\x00', + b'28101-5EY-A500\x00\x00', b'28101-5EZ-A050\x00\x00', b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A100\x00\x00', @@ -947,6 +949,7 @@ FW_VERSIONS = { b'37805-RLV-B220\x00\x00', b'37805-RLV-B420\x00\x00', b'37805-RLV-B430\x00\x00', + b'37805-RLV-B620\x00\x00', b'37805-RLV-B710\x00\x00', b'37805-RLV-B720\x00\x00', b'37805-RLV-C430\x00\x00', @@ -958,6 +961,7 @@ FW_VERSIONS = { b'37805-RLV-L090\x00\x00', b'37805-RLV-L160\x00\x00', b'37805-RLV-L180\x00\x00', + b'37805-RLV-L350\x00\x00', b'37805-RLV-L410\x00\x00', b'37805-RLV-L430\x00\x00', b'37805-RLV-L850\x00\x00', @@ -987,6 +991,7 @@ FW_VERSIONS = { b'36161-TG7-D520\x00\x00', b'36161-TG7-D630\x00\x00', b'36161-TG7-Y630\x00\x00', + b'36161-TG8-A410\x00\x00', b'36161-TG8-A520\x00\x00', b'36161-TG8-A630\x00\x00', b'36161-TG8-A720\x00\x00', @@ -1031,6 +1036,7 @@ FW_VERSIONS = { b'78109-TG8-AJ10\x00\x00', b'78109-TG8-AJ20\x00\x00', b'78109-TG8-AK20\x00\x00', + b'78109-TG8-AS20\x00\x00', b'78109-TGS-AB10\x00\x00', b'78109-TGS-AC10\x00\x00', b'78109-TGS-AD10\x00\x00', From a2028cbfd55712ef23cefa31d909892592ab2e8d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 23 Jan 2024 14:29:31 -0800 Subject: [PATCH 5/7] New MY! --- docs/CARS.md | 2 +- selfdrive/car/chrysler/values.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index b27fb1b943..55098a39a3 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -181,7 +181,7 @@ A supported vehicle is one that just works when you install a comma device. All |Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ram|1500 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 055efba829..2a3bceec96 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -74,7 +74,7 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { ], CAR.JEEP_GRAND_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), CAR.JEEP_GRAND_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), - CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-23", car_parts=CarParts.common([CarHarness.ram])), + CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-24", car_parts=CarParts.common([CarHarness.ram])), CAR.RAM_HD: [ ChryslerCarInfo("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])), ChryslerCarInfo("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])), From d574013d7bda94a43b29095887e176b9723d13cb Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 14:51:45 -0800 Subject: [PATCH 6/7] LogReader: don't use route for max_segment (#31129) * dont use route * fix and cleanup --- tools/lib/logreader.py | 35 +++++++++++++++++++------------ tools/lib/route.py | 13 ++++++++++++ tools/lib/tests/test_logreader.py | 5 ++--- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index e28f41341c..c29662dfbc 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -110,21 +110,31 @@ def apply_strategy(mode: ReadMode, rlog_paths, qlog_paths): elif mode == ReadMode.AUTO_INTERACIVE: return auto_strategy(rlog_paths, qlog_paths, True) -def parse_slice(sr: SegmentRange, route: Route): - segs = np.arange(route.max_seg_number+1) +def parse_slice(sr: SegmentRange): s = create_slice_from_string(sr._slice) - return segs[s] if isinstance(s, slice) else [segs[s]] + if isinstance(s, slice): + if s.stop is None or s.stop < 0 or (s.start is not None and s.start < 0): # we need the number of segments in order to parse this slice + segs = np.arange(sr.get_max_seg_number()+1) + else: + segs = np.arange(s.stop + 1) + return segs[s] + else: + if s < 0: + s = sr.get_max_seg_number() + s + 1 + return [s] + +def comma_api_source(sr: SegmentRange, mode: ReadMode): + segs = parse_slice(sr) -def comma_api_source(sr: SegmentRange, route: Route, mode: ReadMode): - segs = parse_slice(sr, route) + route = Route(sr.route_name) rlog_paths = [route.log_paths()[seg] for seg in segs] qlog_paths = [route.log_paths()[seg] for seg in segs] return apply_strategy(mode, rlog_paths, qlog_paths) -def internal_source(sr: SegmentRange, route: Route, mode: ReadMode): - segs = parse_slice(sr, route) +def internal_source(sr: SegmentRange, mode: ReadMode): + segs = parse_slice(sr) def get_internal_url(sr: SegmentRange, seg, file): return f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{file}.bz2" @@ -134,16 +144,16 @@ def internal_source(sr: SegmentRange, route: Route, mode: ReadMode): return apply_strategy(mode, rlog_paths, qlog_paths) -def openpilotci_source(sr: SegmentRange, route: Route, mode: ReadMode): - segs = parse_slice(sr, route) +def openpilotci_source(sr: SegmentRange, mode: ReadMode): + segs = parse_slice(sr) rlog_paths = [get_url(sr.route_name, seg, "rlog") for seg in segs] qlog_paths = [get_url(sr.route_name, seg, "qlog") for seg in segs] return apply_strategy(mode, rlog_paths, qlog_paths) -def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): - segs = parse_slice(sr, route) +def comma_car_segments_source(sr: SegmentRange, mode=ReadMode.RLOG): + segs = parse_slice(sr) return [get_comma_segments_url(sr.route_name, seg) for seg in segs] @@ -209,11 +219,10 @@ class LogReader: return direct_source(identifier) sr = SegmentRange(parsed) - route = Route(sr.route_name) mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) source = self.default_source if source is None else source - return source(sr, route, mode) + return source(sr, mode) def __init__(self, identifier: str | List[str], default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False, only_union_types=False): self.default_mode = default_mode diff --git a/tools/lib/route.py b/tools/lib/route.py index 09eb7f5426..f7c3c432c8 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -1,5 +1,6 @@ import os import re +from functools import cache from urllib.parse import urlparse from collections import defaultdict from itertools import chain @@ -231,11 +232,23 @@ class SegmentName: def __str__(self) -> str: return self._canonical_name +@cache +def get_max_seg_number_cached(sr: 'SegmentRange'): + try: + api = CommaApi(get_token()) + return api.get("/v1/route/" + sr.route_name.replace("/", "|"))["segment_numbers"][-1] + except Exception as e: + raise Exception("unable to get max_segment_number. ensure you have access to this route or the route is public.") from e + + class SegmentRange: def __init__(self, segment_range: str): self.m = re.fullmatch(RE.SEGMENT_RANGE, segment_range) assert self.m, f"Segment range is not valid {segment_range}" + def get_max_seg_number(self): + return get_max_seg_number_cached(self) + @property def route_name(self): return self.m.group("route_name") diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index 6ec3edc9ac..f7874a3fb3 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -6,7 +6,7 @@ import pytest from parameterized import parameterized import requests from openpilot.tools.lib.logreader import LogReader, parse_indirect, parse_slice, ReadMode -from openpilot.tools.lib.route import Route, SegmentRange +from openpilot.tools.lib.route import SegmentRange NUM_SEGS = 17 # number of segments in the test route ALL_SEGS = list(np.arange(NUM_SEGS)) @@ -42,8 +42,7 @@ class TestLogReader(unittest.TestCase): def test_indirect_parsing(self, identifier, expected): parsed, _, _ = parse_indirect(identifier) sr = SegmentRange(parsed) - route = Route(sr.route_name) - segs = parse_slice(sr, route) + segs = parse_slice(sr) self.assertListEqual(list(segs), expected) @parameterized.expand([ From f22b6681d67a0ef11d0446edfb40f50a5c1f8417 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 14:57:31 -0800 Subject: [PATCH 7/7] CI: test the car_porting example notebooks (#31131) * dont use route * fix and cleanup * test the example notebooks --- .github/workflows/tools_tests.yaml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 5cecfb7e4e..e141071d09 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -88,4 +88,22 @@ jobs: devcontainer exec --workspace-folder . scons -j$(nproc) cereal/ common/ devcontainer exec --workspace-folder . pip install pip-install-test devcontainer exec --workspace-folder . touch /home/batman/.comma/auth.json - devcontainer exec --workspace-folder . sudo touch /root/test.txt \ No newline at end of file + devcontainer exec --workspace-folder . sudo touch /root/test.txt + + notebooks: + name: notebooks + runs-on: ubuntu-20.04 + if: github.repository == 'commaai/openpilot' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: ./.github/workflows/setup-with-retry + - name: Build openpilot + timeout-minutes: 5 + run: ${{ env.RUN }} "scons -j$(nproc)" + - name: Test notebooks + timeout-minutes: 2 + run: | + ${{ env.RUN }} "pip install nbmake && pytest --nbmake tools/car_porting/examples/" \ No newline at end of file