From 018676ea7096dcbb64c1de6f5d61c24b74471c2c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 19 Jun 2023 23:31:41 -0700 Subject: [PATCH] add test from hyundai --- .../car/toyota/tests/print_platform_codes.py | 21 +++ selfdrive/car/toyota/tests/test_toyota.py | 140 ++++++++++++++++++ selfdrive/car/toyota/values.py | 35 ++++- 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100755 selfdrive/car/toyota/tests/print_platform_codes.py diff --git a/selfdrive/car/toyota/tests/print_platform_codes.py b/selfdrive/car/toyota/tests/print_platform_codes.py new file mode 100755 index 0000000000..1bc8a4e366 --- /dev/null +++ b/selfdrive/car/toyota/tests/print_platform_codes.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +from cereal import car +from selfdrive.car.hyundai.values import FW_VERSIONS, PLATFORM_CODE_ECUS, get_platform_codes + +Ecu = car.CarParams.Ecu +ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} + +if __name__ == "__main__": + for car_model, ecus in FW_VERSIONS.items(): + print() + print(car_model) + for ecu in sorted(ecus, key=lambda x: int(x[0])): + if ecu[0] not in PLATFORM_CODE_ECUS: + continue + + platform_codes = get_platform_codes(ecus[ecu]) + codes = {code for code, _ in platform_codes} + dates = {date for _, date in platform_codes if date is not None} + print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):') + print(f' Codes: {codes}') + print(f' Dates: {dates}') diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py index 5648f75fe7..61553b34bd 100755 --- a/selfdrive/car/toyota/tests/test_toyota.py +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -1,13 +1,153 @@ #!/usr/bin/env python3 import unittest +from cereal import car +from selfdrive.car.fw_versions import build_fw_dict +# from selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \ +# EV_CAR, FW_QUERY_CONFIG, FW_VERSIONS, LEGACY_SAFETY_MODE_CAR, \ +# PLATFORM_CODE_ECUS, get_platform_codes from selfdrive.car.toyota.values import TSS2_CAR, ANGLE_CONTROL_CAR +Ecu = car.CarParams.Ecu +ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} + +# # Some platforms have date codes in a different format we don't yet parse (or are missing). +# # For now, assert list of expected missing date cars +# NO_DATES_PLATFORMS = { +# # CAN FD +# CAR.KIA_SPORTAGE_5TH_GEN, +# CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, +# CAR.SANTA_CRUZ_1ST_GEN, +# CAR.TUCSON_4TH_GEN, +# CAR.TUCSON_HYBRID_4TH_GEN, +# # CAN +# CAR.ELANTRA, +# CAR.KIA_CEED, +# CAR.KIA_FORTE, +# CAR.KIA_OPTIMA_G4, +# CAR.KIA_OPTIMA_G4_FL, +# CAR.KIA_SORENTO, +# CAR.KONA, +# CAR.KONA_EV, +# CAR.KONA_EV_2022, +# CAR.KONA_HEV, +# CAR.SONATA_LF, +# CAR.VELOSTER, +# } + class TestToyotaInterfaces(unittest.TestCase): def test_angle_car_set(self): self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0) +class TestToyotaFingerprint(unittest.TestCase): + # Tests for platform codes, part numbers, and FW dates which Hyundai will use to fuzzy + # fingerprint in the absence of full FW matches: + # def test_platform_code_ecus_available(self): + # # TODO: add queries for these non-CAN FD cars to get EPS + # no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, + # CAR.SONATA_LF, CAR.TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80} + # + # # Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms + # for car_model, ecus in FW_VERSIONS.items(): + # with self.subTest(car_model=car_model): + # for platform_code_ecu in PLATFORM_CODE_ECUS: + # if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS: + # continue + # if platform_code_ecu == Ecu.eps and car_model in no_eps_platforms: + # continue + # self.assertIn(platform_code_ecu, [e[0] for e in ecus]) + # + # def test_fw_format(self): + # # Asserts: + # # - every supported ECU FW version returns one platform code + # # - every supported ECU FW version has a part number + # # - expected parsing of ECU FW dates + # + # for car_model, ecus in FW_VERSIONS.items(): + # with self.subTest(car_model=car_model): + # for ecu, fws in ecus.items(): + # if ecu[0] not in PLATFORM_CODE_ECUS: + # continue + # + # codes = set() + # for fw in fws: + # result = get_platform_codes([fw]) + # self.assertEqual(1, len(result), f"Unable to parse FW: {fw}") + # codes |= result + # + # if ecu[0] not in DATE_FW_ECUS or car_model in NO_DATES_PLATFORMS: + # self.assertTrue(all({date is None for _, date in codes})) + # else: + # self.assertTrue(all({date is not None for _, date in codes})) + # + # if car_model == CAR.HYUNDAI_GENESIS: + # raise unittest.SkipTest("No part numbers for car model") + # + # # Hyundai places the ECU part number in their FW versions, assert all parsable + # # Some examples of valid formats: b"56310-L0010", b"56310L0010", b"56310/M6300" + # self.assertTrue(all({b"-" in code for code, _ in codes}), + # f"FW does not have part number: {fw}") + # + # def test_platform_codes_spot_check(self): + # # Asserts basic platform code parsing behavior for a few cases + # results = get_platform_codes([b"\xf1\x00DH LKAS 1.1 -150210"]) + # self.assertEqual(results, {(b"DH", b"150210")}) + # + # # Some cameras and all radars do not have dates + # results = get_platform_codes([b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 "]) + # self.assertEqual(results, {(b"AEhe-G2000", None)}) + # + # results = get_platform_codes([b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 "]) + # self.assertEqual(results, {(b"CV1-CV000", None)}) + # + # results = get_platform_codes([ + # b"\xf1\x00DH LKAS 1.1 -150210", + # b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ", + # b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ", + # ]) + # self.assertEqual(results, {(b"DH", b"150210"), (b"AEhe-G2000", None), (b"CV1-CV000", None)}) + # + # results = get_platform_codes([ + # b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 220222", + # b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 211103", + # b"\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 190405", + # b"\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 190720", + # ]) + # self.assertEqual(results, {(b"LX2-S8100", b"220222"), (b"LX2-S8100", b"211103"), + # (b"ON-S9100", b"190405"), (b"ON-S9100", b"190720")}) + # + # def test_fuzzy_excluded_platforms(self): + # # Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared. + # # This list can be shrunk as we combine platforms and detect features + # excluded_platforms = { + # CAR.GENESIS_G70, # shared platform code, part number, and date + # CAR.GENESIS_G70_2020, + # CAR.TUCSON_4TH_GEN, # shared platform code and part number + # CAR.TUCSON_HYBRID_4TH_GEN, + # } + # excluded_platforms |= CANFD_CAR - EV_CAR # shared platform codes + # excluded_platforms |= NO_DATES_PLATFORMS # date codes are required to match + # + # platforms_with_shared_codes = set() + # for platform, fw_by_addr in FW_VERSIONS.items(): + # car_fw = [] + # for ecu, fw_versions in fw_by_addr.items(): + # ecu_name, addr, sub_addr = ecu + # for fw in fw_versions: + # car_fw.append({"ecu": ecu_name, "fwVersion": fw, "address": addr, + # "subAddress": 0 if sub_addr is None else sub_addr}) + # + # CP = car.CarParams.new_message(carFw=car_fw) + # matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw)) + # if len(matches) == 1: + # self.assertEqual(list(matches)[0], platform) + # else: + # platforms_with_shared_codes.add(platform) + # + # self.assertEqual(platforms_with_shared_codes, excluded_platforms) + + if __name__ == "__main__": unittest.main() diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 58c53b738f..062a784f16 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1,7 +1,8 @@ +impor re from collections import defaultdict from dataclasses import dataclass from enum import Enum, IntFlag -from typing import Dict, List, Union +from typing import Dict, List, Optional, Set, Tuple, Union from cereal import car from common.conversions import Conversions as CV @@ -215,9 +216,41 @@ STATIC_DSU_MSGS = [ (0x4CB, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 0, 100, b'\x0c\x00\x00\x00\x00\x00\x00\x00'), ] + +def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[bytes]]]: + # Returns unique, platform-specific identification codes for a set of versions + codes = set() # (code-Optional[part], date) + for fw in fw_versions: + code_match = PLATFORM_CODE_FW_PATTERN.search(fw) + part_match = PART_NUMBER_FW_PATTERN.search(fw) + date_match = DATE_FW_PATTERN.search(fw) + if code_match is not None: + code: bytes = code_match.group() + part = part_match.group() if part_match else None + date = date_match.group() if date_match else None + if part is not None: + # part number starts with generic ECU part type, add what is specific to platform + code += b"-" + part[-5:] + + codes.add((code, date)) + return codes + + TOYOTA_VERSION_REQUEST_KWP = b'\x1a\x88\x01' TOYOTA_VERSION_RESPONSE_KWP = b'\x5a\x88\x01' +# # Regex patterns for parsing platform code, FW date, and part number from FW versions +# PLATFORM_CODE_FW_PATTERN = re.compile(b'((?<=' + HYUNDAI_VERSION_REQUEST_LONG[1:] + +# b')[A-Z]{2}[A-Za-z0-9]{0,2})') +# DATE_FW_PATTERN = re.compile(b'(?<=[ -])([0-9]{6}$)') +# PART_NUMBER_FW_PATTERN = re.compile(b'(?<=[0-9][.,][0-9]{2} )([0-9]{5}[-/]?[A-Z][A-Z0-9]{3}[0-9])') + +# List of ECUs expected to have platform codes, camera and radar should exist on all cars +# TODO: use abs, it has the platform code and part number on many platforms +PLATFORM_CODE_ECUS = [Ecu.abs, Ecu.engine] +# So far we've only seen dates in fwdCamera +DATE_FW_ECUS = [Ecu.fwdCamera] + FW_QUERY_CONFIG = FwQueryConfig( requests=[ Request(