Merge remote-tracking branch 'commaai/master' into ford-platform-codes

pull/31124/head
Cameron Clough 2 years ago
commit 4936d7adaf
  1. 20
      .github/workflows/tools_tests.yaml
  2. 2
      docs/CARS.md
  3. 14
      selfdrive/car/chrysler/fingerprints.py
  4. 2
      selfdrive/car/chrysler/values.py
  5. 6
      selfdrive/car/honda/fingerprints.py
  6. 2
      tools/lib/helpers.py
  7. 89
      tools/lib/logreader.py
  8. 16
      tools/lib/route.py
  9. 17
      tools/lib/tests/test_logreader.py
  10. 2
      tools/lib/url_file.py
  11. 4
      tools/plotjuggler/juggle.py

@ -88,4 +88,22 @@ jobs:
devcontainer exec --workspace-folder . scons -j$(nproc) cereal/ common/ devcontainer exec --workspace-folder . scons -j$(nproc) cereal/ common/
devcontainer exec --workspace-folder . pip install pip-install-test devcontainer exec --workspace-folder . pip install pip-install-test
devcontainer exec --workspace-folder . touch /home/batman/.comma/auth.json devcontainer exec --workspace-folder . touch /home/batman/.comma/auth.json
devcontainer exec --workspace-folder . sudo touch /root/test.txt 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/"

@ -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)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=Leaf 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/vaMbtAh_0cY" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=Rogue 2018-20">Buy Here</a></sub></details>|| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=Rogue 2018-20">Buy Here</a></sub></details>||
|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=X-Trail 2017">Buy Here</a></sub></details>|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Nissan A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Nissan&model=X-Trail 2017">Buy Here</a></sub></details>||
|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)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Ram connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ram&model=1500 2019-23">Buy Here</a></sub></details>|| |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)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Ram connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ram&model=1500 2019-24">Buy Here</a></sub></details>||
|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,13</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=SEAT&model=Ateca 2018">Buy Here</a></sub></details>|| |SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,13</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=SEAT&model=Ateca 2018">Buy Here</a></sub></details>||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,13</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=SEAT&model=Leon 2014-20">Buy Here</a></sub></details>|| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,13</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=SEAT&model=Leon 2014-20">Buy Here</a></sub></details>||
|Subaru|Ascent 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Subaru&model=Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|| |Subaru|Ascent 2019-21|All[<sup>7</sup>](#footnotes)|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Subaru&model=Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>||

@ -66,6 +66,7 @@ FW_VERSIONS = {
(Ecu.combinationMeter, 0x742, None): [ (Ecu.combinationMeter, 0x742, None): [
b'68402971AD', b'68402971AD',
b'68454144AD', b'68454144AD',
b'68454152AB',
], ],
(Ecu.srs, 0x744, None): [ (Ecu.srs, 0x744, None): [
b'68355363AB', b'68355363AB',
@ -87,6 +88,7 @@ FW_VERSIONS = {
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'05035707AA', b'05035707AA',
b'68495807AA', b'68495807AA',
b'68495807AB',
], ],
}, },
CAR.RAM_1500: { CAR.RAM_1500: {
@ -98,6 +100,7 @@ FW_VERSIONS = {
b'68294063AH', b'68294063AH',
b'68294063AI', b'68294063AI',
b'68434846AC', b'68434846AC',
b'68434849AC',
b'68434858AC', b'68434858AC',
b'68434859AC', b'68434859AC',
b'68434860AC', b'68434860AC',
@ -112,7 +115,10 @@ FW_VERSIONS = {
b'68453513AC', b'68453513AC',
b'68453513AD', b'68453513AD',
b'68453514AD', b'68453514AD',
b'68505633AB',
b'68510277AG',
b'68510280AG', b'68510280AG',
b'68510282AG',
b'68510282AH', b'68510282AH',
b'68510283AG', b'68510283AG',
b'68527346AE', b'68527346AE',
@ -120,6 +126,7 @@ FW_VERSIONS = {
b'68527375AD', b'68527375AD',
b'68527382AE', b'68527382AE',
b'68527387AE', b'68527387AE',
b'68631942AA',
], ],
(Ecu.srs, 0x744, None): [ (Ecu.srs, 0x744, None): [
b'68428609AB', b'68428609AB',
@ -192,15 +199,19 @@ FW_VERSIONS = {
b'05149591AD ', b'05149591AD ',
b'05149591AE ', b'05149591AE ',
b'05149592AE ', b'05149592AE ',
b'05149600AD ',
b'05149846AA ', b'05149846AA ',
b'05149848AA ', b'05149848AA ',
b'05190341AD',
b'68378695AJ ', b'68378695AJ ',
b'68378696AJ ', b'68378696AJ ',
b'68378701AI ', b'68378701AI ',
b'68378748AL ', b'68378748AL ',
b'68378758AM ', b'68378758AM ',
b'68448163AJ', b'68448163AJ',
b'68448163AK',
b'68448163AL', b'68448163AL',
b'68448165AG',
b'68448165AK', b'68448165AK',
b'68455119AC ', b'68455119AC ',
b'68455145AC ', b'68455145AC ',
@ -218,11 +229,13 @@ FW_VERSIONS = {
b'68539650AF', b'68539650AF',
b'68586101AA ', b'68586101AA ',
b'68586105AB ', b'68586105AB ',
b'68629926AC ',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'05035706AD', b'05035706AD',
b'05036069AA', b'05036069AA',
b'05149536AC', b'05149536AC',
b'05149537AC',
b'68360078AL', b'68360078AL',
b'68360080AM', b'68360080AM',
b'68360081AM', b'68360081AM',
@ -240,6 +253,7 @@ FW_VERSIONS = {
b'68520867AE', b'68520867AE',
b'68520867AF', b'68520867AF',
b'68540431AB', b'68540431AB',
b'68629936AC',
], ],
}, },
CAR.RAM_HD: { CAR.RAM_HD: {

@ -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: 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.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: [ CAR.RAM_HD: [
ChryslerCarInfo("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])), ChryslerCarInfo("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])),
ChryslerCarInfo("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])), ChryslerCarInfo("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])),

@ -928,6 +928,8 @@ FW_VERSIONS = {
(Ecu.transmission, 0x18da1ef1, None): [ (Ecu.transmission, 0x18da1ef1, None): [
b'28101-5EY-A050\x00\x00', b'28101-5EY-A050\x00\x00',
b'28101-5EY-A100\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-A050\x00\x00',
b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A060\x00\x00',
b'28101-5EZ-A100\x00\x00', b'28101-5EZ-A100\x00\x00',
@ -947,6 +949,7 @@ FW_VERSIONS = {
b'37805-RLV-B220\x00\x00', b'37805-RLV-B220\x00\x00',
b'37805-RLV-B420\x00\x00', b'37805-RLV-B420\x00\x00',
b'37805-RLV-B430\x00\x00', b'37805-RLV-B430\x00\x00',
b'37805-RLV-B620\x00\x00',
b'37805-RLV-B710\x00\x00', b'37805-RLV-B710\x00\x00',
b'37805-RLV-B720\x00\x00', b'37805-RLV-B720\x00\x00',
b'37805-RLV-C430\x00\x00', b'37805-RLV-C430\x00\x00',
@ -958,6 +961,7 @@ FW_VERSIONS = {
b'37805-RLV-L090\x00\x00', b'37805-RLV-L090\x00\x00',
b'37805-RLV-L160\x00\x00', b'37805-RLV-L160\x00\x00',
b'37805-RLV-L180\x00\x00', b'37805-RLV-L180\x00\x00',
b'37805-RLV-L350\x00\x00',
b'37805-RLV-L410\x00\x00', b'37805-RLV-L410\x00\x00',
b'37805-RLV-L430\x00\x00', b'37805-RLV-L430\x00\x00',
b'37805-RLV-L850\x00\x00', b'37805-RLV-L850\x00\x00',
@ -987,6 +991,7 @@ FW_VERSIONS = {
b'36161-TG7-D520\x00\x00', b'36161-TG7-D520\x00\x00',
b'36161-TG7-D630\x00\x00', b'36161-TG7-D630\x00\x00',
b'36161-TG7-Y630\x00\x00', b'36161-TG7-Y630\x00\x00',
b'36161-TG8-A410\x00\x00',
b'36161-TG8-A520\x00\x00', b'36161-TG8-A520\x00\x00',
b'36161-TG8-A630\x00\x00', b'36161-TG8-A630\x00\x00',
b'36161-TG8-A720\x00\x00', b'36161-TG8-A720\x00\x00',
@ -1031,6 +1036,7 @@ FW_VERSIONS = {
b'78109-TG8-AJ10\x00\x00', b'78109-TG8-AJ10\x00\x00',
b'78109-TG8-AJ20\x00\x00', b'78109-TG8-AJ20\x00\x00',
b'78109-TG8-AK20\x00\x00', b'78109-TG8-AK20\x00\x00',
b'78109-TG8-AS20\x00\x00',
b'78109-TGS-AB10\x00\x00', b'78109-TGS-AB10\x00\x00',
b'78109-TGS-AC10\x00\x00', b'78109-TGS-AC10\x00\x00',
b'78109-TGS-AD10\x00\x00', b'78109-TGS-AD10\x00\x00',

@ -11,7 +11,7 @@ class RE:
SEGMENT_NAME = r'{}(?:--|/)(?P<segment_num>[0-9]+)'.format(ROUTE_NAME) SEGMENT_NAME = r'{}(?:--|/)(?P<segment_num>[0-9]+)'.format(ROUTE_NAME)
INDEX = r'-?[0-9]+' INDEX = r'-?[0-9]+'
SLICE = r'(?P<start>{})?:?(?P<end>{})?:?(?P<step>{})?'.format(INDEX, INDEX, INDEX) SLICE = r'(?P<start>{})?:?(?P<end>{})?:?(?P<step>{})?'.format(INDEX, INDEX, INDEX)
SEGMENT_RANGE = r'{}(?:--|/)?(?P<slice>({}))?/?(?P<selector>([qr]))?'.format(ROUTE_NAME, SLICE) SEGMENT_RANGE = r'{}(?:--|/)?(?P<slice>({}))?/?(?P<selector>([qra]))?'.format(ROUTE_NAME, SLICE)
BOOTLOG_NAME = ROUTE_NAME BOOTLOG_NAME = ROUTE_NAME
EXPLORER_FILE = r'^(?P<segment_name>{})--(?P<file_name>[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME) EXPLORER_FILE = r'^(?P<segment_name>{})--(?P<file_name>[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME)

@ -16,6 +16,7 @@ from typing import Iterable, Iterator, List, Type
from urllib.parse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
from cereal import log as capnp_log 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.comma_car_segments import get_url as get_comma_segments_url
from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.openpilotci import get_url
from openpilot.tools.lib.filereader import FileReader, file_exists from openpilot.tools.lib.filereader import FileReader, file_exists
@ -71,8 +72,8 @@ class _LogFileReader:
class ReadMode(enum.StrEnum): class ReadMode(enum.StrEnum):
RLOG = "r" # only read rlogs RLOG = "r" # only read rlogs
QLOG = "q" # only read qlogs 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): def create_slice_from_string(s: str):
m = re.fullmatch(RE.SLICE, s) m = re.fullmatch(RE.SLICE, s)
@ -86,44 +87,86 @@ def create_slice_from_string(s: str):
return start return start
return slice(start, end, step) return slice(start, end, step)
def parse_slice(sr: SegmentRange, route: Route): def auto_strategy(rlog_paths, qlog_paths, interactive):
segs = np.arange(route.max_seg_number+1) # 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):
s = create_slice_from_string(sr._slice) 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.RLOG): route = Route(sr.route_name)
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, mode: ReadMode):
segs = parse_slice(sr)
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): rlog_paths = [get_internal_url(sr, seg, "rlog") for seg in segs]
segs = parse_slice(sr, route) 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, mode: ReadMode):
segs = parse_slice(sr, route) segs = parse_slice(sr)
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]
def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): return apply_strategy(mode, rlog_paths, qlog_paths)
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] return [get_comma_segments_url(sr.route_name, seg) for seg in segs]
def direct_source(file_or_url): def direct_source(file_or_url):
return [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): def check_source(source, *args):
try: try:
files = source(*args) files = source(*args)
assert all(file_exists(f) for f in files) assert len(get_invalid_files(files)) == 0
return True, files return True, files
except Exception: except Exception:
return False, None return False, None
@ -176,11 +219,10 @@ class LogReader:
return direct_source(identifier) return direct_source(identifier)
sr = SegmentRange(parsed) sr = SegmentRange(parsed)
route = Route(sr.route_name)
mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) mode = self.default_mode if sr.selector is None else ReadMode(sr.selector)
source = self.default_source if source is None else source 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): 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 self.default_mode = default_mode
@ -209,6 +251,9 @@ class LogReader:
def reset(self): def reset(self):
self.logreader_identifiers = self._parse_identifiers(self.identifier) 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 @staticmethod
def from_bytes(dat): def from_bytes(dat):

@ -1,5 +1,6 @@
import os import os
import re import re
from functools import cache
from urllib.parse import urlparse from urllib.parse import urlparse
from collections import defaultdict from collections import defaultdict
from itertools import chain from itertools import chain
@ -231,11 +232,23 @@ class SegmentName:
def __str__(self) -> str: return self._canonical_name 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: class SegmentRange:
def __init__(self, segment_range: str): def __init__(self, segment_range: str):
self.m = re.fullmatch(RE.SEGMENT_RANGE, segment_range) self.m = re.fullmatch(RE.SEGMENT_RANGE, segment_range)
assert self.m, f"Segment range is not valid {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 @property
def route_name(self): def route_name(self):
return self.m.group("route_name") return self.m.group("route_name")
@ -255,3 +268,6 @@ class SegmentRange:
@property @property
def selector(self): def selector(self):
return self.m.group("selector") 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 "")

@ -6,7 +6,7 @@ import pytest
from parameterized import parameterized from parameterized import parameterized
import requests import requests
from openpilot.tools.lib.logreader import LogReader, parse_indirect, parse_slice, ReadMode 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 NUM_SEGS = 17 # number of segments in the test route
ALL_SEGS = list(np.arange(NUM_SEGS)) ALL_SEGS = list(np.arange(NUM_SEGS))
@ -42,10 +42,21 @@ class TestLogReader(unittest.TestCase):
def test_indirect_parsing(self, identifier, expected): def test_indirect_parsing(self, identifier, expected):
parsed, _, _ = parse_indirect(identifier) parsed, _, _ = parse_indirect(identifier)
sr = SegmentRange(parsed) sr = SegmentRange(parsed)
route = Route(sr.route_name) segs = parse_slice(sr)
segs = parse_slice(sr, route)
self.assertListEqual(list(segs), expected) 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): def test_direct_parsing(self):
qlog = tempfile.NamedTemporaryFile(mode='wb', delete=False) qlog = tempfile.NamedTemporaryFile(mode='wb', delete=False)

@ -1,3 +1,4 @@
import logging
import os import os
import time import time
import threading import threading
@ -12,6 +13,7 @@ from openpilot.system.hardware.hw import Paths
K = 1000 K = 1000
CHUNK_SIZE = 1000 * K CHUNK_SIZE = 1000 * K
logging.getLogger("urllib3").setLevel(logging.WARNING)
def hash_256(link): def hash_256(link):
hsh = str(sha256((link.split("?")[0]).encode('utf-8')).hexdigest()) hsh = str(sha256((link.split("?")[0]).encode('utf-8')).hexdigest())

@ -13,7 +13,7 @@ from functools import partial
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.tools.lib.helpers import save_log 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__)) 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']] 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): 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)) all_data = sr.run_across_segments(24, partial(process, can))

Loading…
Cancel
Save