diff --git a/docs/CARS.md b/docs/CARS.md
index 55098a39a3..bb55f9e0b7 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -28,7 +28,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Chevrolet|Volt 2017-18[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[](##)|[](##)|Parts
- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here |
|
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
-|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Chrysler|Pacifica Hybrid 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None||
diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py
index 9ef991e8ed..28f590b23a 100644
--- a/selfdrive/car/chrysler/fingerprints.py
+++ b/selfdrive/car/chrysler/fingerprints.py
@@ -62,6 +62,29 @@ FINGERPRINTS = {
}
FW_VERSIONS = {
+ CAR.PACIFICA_2020: {
+ (Ecu.combinationMeter, 0x742, None): [
+ b'68594993AB',
+ ],
+ (Ecu.srs, 0x744, None): [
+ b'68526663AB',
+ ],
+ (Ecu.abs, 0x747, None): [
+ b'68593395AA',
+ ],
+ (Ecu.fwdRadar, 0x753, None): [
+ b'68598670AB',
+ ],
+ (Ecu.eps, 0x75a, None): [
+ b'68594340AB',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'68700306AB ',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'68586231AD',
+ ],
+ },
CAR.JEEP_GRAND_CHEROKEE_2019: {
(Ecu.combinationMeter, 0x742, None): [
b'68402971AD',
diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py
index 2a3bceec96..8d873705fa 100644
--- a/selfdrive/car/chrysler/values.py
+++ b/selfdrive/car/chrysler/values.py
@@ -70,7 +70,7 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = {
CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"),
CAR.PACIFICA_2020: [
ChryslerCarInfo("Chrysler Pacifica 2019-20"),
- ChryslerCarInfo("Chrysler Pacifica 2021", package="All"),
+ ChryslerCarInfo("Chrysler Pacifica 2021-23", package="All"),
],
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"),
diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py
index 8594ab95df..0cd812bc6b 100644
--- a/selfdrive/car/ford/fingerprints.py
+++ b/selfdrive/car/ford/fingerprints.py
@@ -145,6 +145,7 @@ FW_VERSIONS = {
b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
+ b'MJ98-14C204-BBP\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'MJ98-14C204-BBS\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py
index 072c2ae4e4..547904ee47 100755
--- a/selfdrive/car/fw_versions.py
+++ b/selfdrive/car/fw_versions.py
@@ -105,11 +105,14 @@ def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude=
return set()
-def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True) -> Set[str]:
+def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True, extra_fw_versions=None) -> Set[str]:
"""Do an exact FW match. Returns all cars that match the given
FW versions for a list of "essential" ECUs. If an ECU is not considered
essential the FW version can be missing to get a fingerprint, but if it's present it
needs to match the database."""
+ if extra_fw_versions is None:
+ extra_fw_versions = {}
+
invalid = set()
candidates = {c: f for c, f in FW_VERSIONS.items() if
is_brand(MODEL_TO_BRAND[c], match_brand)}
@@ -117,6 +120,7 @@ def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True) -> Set[s
for candidate, fws in candidates.items():
config = FW_QUERY_CONFIGS[MODEL_TO_BRAND[candidate]]
for ecu, expected_versions in fws.items():
+ expected_versions = expected_versions + extra_fw_versions.get(candidate, {}).get(ecu, [])
ecu_type = ecu[0]
addr = ecu[1:]
diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py
index 4ee23c32c3..c013b2056f 100644
--- a/selfdrive/car/nissan/values.py
+++ b/selfdrive/car/nissan/values.py
@@ -59,27 +59,35 @@ NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83'
NISSAN_RX_OFFSET = 0x20
FW_QUERY_CONFIG = FwQueryConfig(
- requests=[
+ requests=[request for bus, logging in ((0, True), (1, False)) for request in [
Request(
[NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP],
[NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP],
+ bus=bus,
+ logging=logging,
),
Request(
[NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP],
[NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP],
rx_offset=NISSAN_RX_OFFSET,
+ bus=bus,
+ logging=logging,
),
# Rogue's engine solely responds to this
Request(
[NISSAN_DIAGNOSTIC_REQUEST_KWP_2, NISSAN_VERSION_REQUEST_KWP],
[NISSAN_DIAGNOSTIC_RESPONSE_KWP_2, NISSAN_VERSION_RESPONSE_KWP],
+ bus=bus,
+ logging=logging,
),
Request(
[StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
[StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
rx_offset=NISSAN_RX_OFFSET,
+ bus=bus,
+ logging=logging,
),
- ],
+ ]],
)
DBC = {
diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py
index 36619f4cde..893c79c797 100755
--- a/selfdrive/car/tests/test_fw_fingerprint.py
+++ b/selfdrive/car/tests/test_fw_fingerprint.py
@@ -238,7 +238,7 @@ class TestFwFingerprintTiming(unittest.TestCase):
@pytest.mark.timeout(60)
def test_fw_query_timing(self):
- total_ref_time = 6.1
+ total_ref_time = 6.5
brand_ref_times = {
1: {
'body': 0.1,
@@ -247,7 +247,7 @@ class TestFwFingerprintTiming(unittest.TestCase):
'honda': 0.45,
'hyundai': 0.65,
'mazda': 0.2,
- 'nissan': 0.4,
+ 'nissan': 0.8,
'subaru': 0.45,
'tesla': 0.2,
'toyota': 1.6,
diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py
index 78f7a5d3e5..dd6243a44c 100755
--- a/selfdrive/debug/test_fw_query_on_routes.py
+++ b/selfdrive/debug/test_fw_query_on_routes.py
@@ -6,8 +6,8 @@ import argparse
import os
import traceback
from tqdm import tqdm
-from openpilot.tools.lib.logreader import LogReader
-from openpilot.tools.lib.route import Route
+from openpilot.tools.lib.logreader import LogReader, ReadMode
+from openpilot.tools.lib.route import SegmentRange
from openpilot.selfdrive.car.car_helpers import interface_names
from openpilot.selfdrive.car.fw_versions import VERSIONS, match_fw_to_car
@@ -44,23 +44,14 @@ if __name__ == "__main__":
dongles = []
for route in tqdm(routes):
- route = route.rstrip()
- dongle_id, time = route.split('|')
+ dongle_id = SegmentRange(route).dongle_id
if dongle_id in dongles:
continue
- if NO_API:
- qlog_path = f"cd:/{dongle_id}/{time}/0/qlog.bz2"
- else:
- route = Route(route)
- qlog_path = next((p for p in route.qlog_paths() if p is not None), None)
-
- if qlog_path is None:
- continue
+ lr = LogReader(route, default_mode=ReadMode.QLOG)
try:
- lr = LogReader(qlog_path)
dongles.append(dongle_id)
CP = None
@@ -98,13 +89,11 @@ if __name__ == "__main__":
if len(fuzzy_matches) == 1:
if list(fuzzy_matches)[0] != live_fingerprint:
wrong_fuzzy += 1
- print(f"{dongle_id}|{time}")
print("Fuzzy match wrong! Fuzzy:", fuzzy_matches, "Live:", live_fingerprint)
else:
good_fuzzy += 1
break
- print(f"{dongle_id}|{time}")
print("Old style:", live_fingerprint, "Vin", CP.carVin)
print("New style (exact):", exact_matches)
print("New style (fuzzy):", fuzzy_matches)
diff --git a/tools/car_porting/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py
index fce7e33182..e14a018917 100755
--- a/tools/car_porting/auto_fingerprint.py
+++ b/tools/car_porting/auto_fingerprint.py
@@ -7,7 +7,7 @@ from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versio
from openpilot.selfdrive.car.fw_versions import match_fw_to_car
from openpilot.selfdrive.car.interfaces import get_interface_attr
-from openpilot.tools.lib.logreader import LogReader, ReadMode
+from openpilot.tools.lib.logreader import LogReader, ReadMode, get_first_message
ALL_FW_VERSIONS = get_interface_attr("FW_VERSIONS")
@@ -32,16 +32,15 @@ if __name__ == "__main__":
platform: Optional[str] = None
- for msg in lr:
- if msg.which() == "carParams":
- carFw = msg.carParams.carFw
- carVin = msg.carParams.carVin
- carPlatform = msg.carParams.carFingerprint
- break
+ CP = get_first_message(lr, "carParams")
- if carFw is None:
+ if CP is None:
raise Exception("No fw versions in the provided route...")
+ carFw = CP.carParams.carFw
+ carVin = CP.carParams.carVin
+ carPlatform = CP.carParams.carFingerprint
+
if args.platform is None: # attempt to auto-determine platform with other fuzzy fingerprints
_, possible_platforms = match_fw_to_car(carFw, log=False)
diff --git a/tools/car_porting/examples/ford_vin_fingerprint.ipynb b/tools/car_porting/examples/ford_vin_fingerprint.ipynb
index bc689a5aad..c4f2088b38 100644
--- a/tools/car_porting/examples/ford_vin_fingerprint.ipynb
+++ b/tools/car_porting/examples/ford_vin_fingerprint.ipynb
@@ -2,9 +2,17 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": 1,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "kj/filesystem-disk-unix.c++:1703: warning: PWD environment variable doesn't match current directory; pwd = /mnt/c/Users/jnewb/AppData/Local/Programs/Microsoft VS Code\n"
+ ]
+ }
+ ],
"source": [
"\"\"\"In this example, we use the public comma car segments database to check if vin fingerprinting is feasible for ford.\"\"\"\n",
"\n",
@@ -19,7 +27,7 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
@@ -54,7 +62,7 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 3,
"metadata": {},
"outputs": [
{
@@ -66,7 +74,7 @@
}
],
"source": [
- "from openpilot.tools.lib.logreader import comma_car_segments_source, get_first_message\n",
+ "from openpilot.tools.lib.logreader import get_first_message\n",
"\n",
"\n",
"VINS_TO_CHECK = set()\n",
@@ -77,35 +85,35 @@
" continue\n",
"\n",
" for segment in database[platform]:\n",
- " lr = LogReader(segment, default_source=comma_car_segments_source)\n",
+ " lr = LogReader(segment)\n",
" CP = get_first_message(lr, \"carParams\").carParams\n",
" VINS_TO_CHECK.add((CP.carVin, CP.carFingerprint))"
]
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
+ "vin: 3FTTW8E31PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n",
"vin: 1FM5K8GC7LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n",
- "vin: 00000000000XXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n",
- "vin: 3FTTW8F98NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n",
- "vin: 1FTVW1EL4NWXXXXXX real platform: FORD F-150 LIGHTNING 1ST GEN determined platform: FORD F-150 LIGHTNING 1ST GEN correct: True\n",
+ "vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n",
+ "vin: 1FM5K8GC7NGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n",
"vin: 1FM5K7LC0MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n",
+ "vin: 1FM5K8HC7MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n",
+ "vin: 3FMTK3SU0MMXXXXXX real platform: FORD MUSTANG MACH-E 1ST GEN determined platform: FORD MUSTANG MACH-E 1ST GEN correct: True\n",
"vin: WF0NXXGCHNJXXXXXX real platform: FORD FOCUS 4TH GEN determined platform: mock correct: False\n",
- "vin: 1FMCU9J94MUXXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n",
+ "vin: 1FTVW1EL4NWXXXXXX real platform: FORD F-150 LIGHTNING 1ST GEN determined platform: FORD F-150 LIGHTNING 1ST GEN correct: True\n",
+ "vin: 00000000000XXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n",
+ "vin: 3FTTW8F98NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n",
"vin: 5LM5J7XC9LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n",
- "vin: 3FMCR9B69NRXXXXXX real platform: FORD BRONCO SPORT 1ST GEN determined platform: mock correct: False\n",
- "vin: 3FMTK3SU0MMXXXXXX real platform: FORD MUSTANG MACH-E 1ST GEN determined platform: FORD MUSTANG MACH-E 1ST GEN correct: True\n",
- "vin: 1FM5K8HC7MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n",
- "vin: 1FM5K8GC7NGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n",
"vin: 5LM5J7XC8MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n",
- "vin: 3FTTW8E31PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n",
- "vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n"
+ "vin: 3FMCR9B69NRXXXXXX real platform: FORD BRONCO SPORT 1ST GEN determined platform: mock correct: False\n",
+ "vin: 1FMCU9J94MUXXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n"
]
}
],
diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py
index c29662dfbc..58883c6865 100755
--- a/tools/lib/logreader.py
+++ b/tools/lib/logreader.py
@@ -12,7 +12,7 @@ import sys
import urllib.parse
import warnings
-from typing import Iterable, Iterator, List, Type
+from typing import Dict, Iterable, Iterator, List, Type
from urllib.parse import parse_qs, urlparse
from cereal import log as capnp_log
@@ -129,7 +129,7 @@ def comma_api_source(sr: SegmentRange, mode: ReadMode):
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]
+ qlog_paths = [route.qlog_paths()[seg] for seg in segs]
return apply_strategy(mode, rlog_paths, qlog_paths)
@@ -173,7 +173,7 @@ def check_source(source, *args):
def auto_source(*args):
# Automatically determine viable source
- for source in [internal_source, openpilotci_source]:
+ for source in [comma_car_segments_source, internal_source, openpilotci_source]:
valid, ret = check_source(source, *args)
if valid:
return ret
@@ -232,20 +232,25 @@ class LogReader:
self.sort_by_time = sort_by_time
self.only_union_types = only_union_types
+ self.__lrs: Dict[int, _LogFileReader] = {}
self.reset()
+ def _get_lr(self, i):
+ if i not in self.__lrs:
+ self.__lrs[i] = _LogFileReader(self.logreader_identifiers[i])
+ return self.__lrs[i]
+
def __iter__(self):
- for identifier in self.logreader_identifiers:
- yield from _LogFileReader(identifier)
+ for i in range(len(self.logreader_identifiers)):
+ yield from self._get_lr(i)
- def _run_on_segment(self, func, identifier):
- lr = _LogFileReader(identifier)
- return func(lr)
+ def _run_on_segment(self, func, i):
+ return func(self._get_lr(i))
def run_across_segments(self, num_processes, func):
with multiprocessing.Pool(num_processes) as pool:
ret = []
- for p in pool.map(partial(self._run_on_segment, func), self.logreader_identifiers):
+ for p in pool.map(partial(self._run_on_segment, func), range(len(self.logreader_identifiers))):
ret.extend(p)
return ret
diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py
index f7874a3fb3..d8a9c14088 100644
--- a/tools/lib/tests/test_logreader.py
+++ b/tools/lib/tests/test_logreader.py
@@ -3,8 +3,11 @@ import tempfile
import numpy as np
import unittest
import pytest
-from parameterized import parameterized
import requests
+
+from parameterized import parameterized
+from unittest import mock
+
from openpilot.tools.lib.logreader import LogReader, parse_indirect, parse_slice, ReadMode
from openpilot.tools.lib.route import SegmentRange
@@ -104,11 +107,15 @@ class TestLogReader(unittest.TestCase):
self.assertEqual(qlog_len*2, qlog_len_2)
@pytest.mark.slow
- def test_multiple_iterations(self):
+ @mock.patch("openpilot.tools.lib.logreader._LogFileReader")
+ def test_multiple_iterations(self, init_mock):
lr = LogReader(f"{TEST_ROUTE}/0/q")
qlog_len1 = len(list(lr))
qlog_len2 = len(list(lr))
+ # ensure we don't create multiple instances of _LogFileReader, which means downloading the files twice
+ self.assertEqual(init_mock.call_count, 1)
+
self.assertEqual(qlog_len1, qlog_len2)