Add openpilot tests
This commit is contained in:
1
selfdrive/car/tests/.gitignore
vendored
Normal file
1
selfdrive/car/tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.bz2
|
||||
12
selfdrive/car/tests/big_cars_test.sh
Normal file
12
selfdrive/car/tests/big_cars_test.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
BASEDIR=$(realpath "$SCRIPT_DIR/../../../")
|
||||
cd $BASEDIR
|
||||
|
||||
MAX_EXAMPLES=300
|
||||
INTERNAL_SEG_CNT=300
|
||||
FILEREADER_CACHE=1
|
||||
INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt
|
||||
|
||||
cd selfdrive/car/tests && pytest test_models.py test_car_interfaces.py
|
||||
299
selfdrive/car/tests/routes.py
Normal file
299
selfdrive/car/tests/routes.py
Normal file
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import NamedTuple
|
||||
|
||||
from openpilot.selfdrive.car.chrysler.values import CAR as CHRYSLER
|
||||
from openpilot.selfdrive.car.gm.values import CAR as GM
|
||||
from openpilot.selfdrive.car.ford.values import CAR as FORD
|
||||
from openpilot.selfdrive.car.honda.values import CAR as HONDA
|
||||
from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI
|
||||
from openpilot.selfdrive.car.nissan.values import CAR as NISSAN
|
||||
from openpilot.selfdrive.car.mazda.values import CAR as MAZDA
|
||||
from openpilot.selfdrive.car.subaru.values import CAR as SUBARU
|
||||
from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA
|
||||
from openpilot.selfdrive.car.volkswagen.values import CAR as VOLKSWAGEN
|
||||
from openpilot.selfdrive.car.tesla.values import CAR as TESLA
|
||||
from openpilot.selfdrive.car.body.values import CAR as COMMA
|
||||
|
||||
# TODO: add routes for these cars
|
||||
non_tested_cars = [
|
||||
FORD.F_150_MK14,
|
||||
GM.CADILLAC_ATS,
|
||||
GM.HOLDEN_ASTRA,
|
||||
GM.MALIBU,
|
||||
HYUNDAI.GENESIS_G90,
|
||||
HONDA.ODYSSEY_CHN,
|
||||
VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter
|
||||
SUBARU.FORESTER_HYBRID,
|
||||
]
|
||||
|
||||
|
||||
class CarTestRoute(NamedTuple):
|
||||
route: str
|
||||
car_model: str | None
|
||||
segment: int | None = None
|
||||
|
||||
|
||||
routes = [
|
||||
CarTestRoute("efdf9af95e71cd84|2022-05-13--19-03-31", COMMA.BODY),
|
||||
|
||||
CarTestRoute("0c94aa1e1296d7c6|2021-05-05--19-48-37", CHRYSLER.JEEP_GRAND_CHEROKEE),
|
||||
CarTestRoute("91dfedae61d7bd75|2021-05-22--20-07-52", CHRYSLER.JEEP_GRAND_CHEROKEE_2019),
|
||||
CarTestRoute("420a8e183f1aed48|2020-03-05--07-15-29", CHRYSLER.PACIFICA_2017_HYBRID),
|
||||
CarTestRoute("43a685a66291579b|2021-05-27--19-47-29", CHRYSLER.PACIFICA_2018),
|
||||
CarTestRoute("378472f830ee7395|2021-05-28--07-38-43", CHRYSLER.PACIFICA_2018_HYBRID),
|
||||
CarTestRoute("8190c7275a24557b|2020-01-29--08-33-58", CHRYSLER.PACIFICA_2019_HYBRID),
|
||||
CarTestRoute("3d84727705fecd04|2021-05-25--08-38-56", CHRYSLER.PACIFICA_2020),
|
||||
CarTestRoute("221c253375af4ee9|2022-06-15--18-38-24", CHRYSLER.RAM_1500),
|
||||
CarTestRoute("8fb5eabf914632ae|2022-08-04--17-28-53", CHRYSLER.RAM_HD, segment=6),
|
||||
CarTestRoute("3379c85aeedc8285|2023-12-07--17-49-39", CHRYSLER.DODGE_DURANGO),
|
||||
|
||||
CarTestRoute("54827bf84c38b14f|2023-01-25--14-14-11", FORD.BRONCO_SPORT_MK1),
|
||||
CarTestRoute("f8eaaccd2a90aef8|2023-05-04--15-10-09", FORD.ESCAPE_MK4),
|
||||
CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.EXPLORER_MK6),
|
||||
CarTestRoute("e886087f430e7fe7|2023-06-16--23-06-36", FORD.FOCUS_MK4),
|
||||
CarTestRoute("bd37e43731e5964b|2023-04-30--10-42-26", FORD.MAVERICK_MK1),
|
||||
CarTestRoute("112e4d6e0cad05e1|2023-11-14--08-21-43", FORD.F_150_LIGHTNING_MK1),
|
||||
CarTestRoute("83a4e056c7072678|2023-11-13--16-51-33", FORD.MUSTANG_MACH_E_MK1),
|
||||
#TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION),
|
||||
|
||||
CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA),
|
||||
CarTestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL),
|
||||
CarTestRoute("75a6bcb9b8b40373|2023-03-11--22-47-33", GM.BUICK_LACROSSE),
|
||||
CarTestRoute("e746f59bc96fd789|2024-01-31--22-25-58", GM.EQUINOX),
|
||||
CarTestRoute("ef8f2185104d862e|2023-02-09--18-37-13", GM.ESCALADE),
|
||||
CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV),
|
||||
CarTestRoute("168f8b3be57f66ae|2023-09-12--21-44-42", GM.ESCALADE_ESV_2019),
|
||||
CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT),
|
||||
CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1),
|
||||
CarTestRoute("555d4087cf86aa91|2022-12-02--12-15-07", GM.BOLT_EUV, segment=14), # Bolt EV
|
||||
CarTestRoute("38aa7da107d5d252|2022-08-15--16-01-12", GM.SILVERADO),
|
||||
CarTestRoute("5085c761395d1fe6|2023-04-07--18-20-06", GM.TRAILBLAZER),
|
||||
|
||||
CarTestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G),
|
||||
CarTestRoute("a74b011b32b51b56|2020-07-26--17-09-36", HONDA.CIVIC),
|
||||
CarTestRoute("a859a044a447c2b0|2020-03-03--18-42-45", HONDA.CRV_EU),
|
||||
CarTestRoute("68aac44ad69f838e|2021-05-18--20-40-52", HONDA.CRV),
|
||||
CarTestRoute("14fed2e5fa0aa1a5|2021-05-25--14-59-42", HONDA.CRV_HYBRID),
|
||||
CarTestRoute("52f3e9ae60c0d886|2021-05-23--15-59-43", HONDA.FIT),
|
||||
CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED),
|
||||
CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV),
|
||||
CarTestRoute("320098ff6c5e4730|2023-04-13--17-47-46", HONDA.HRV_3G),
|
||||
CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX),
|
||||
CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T
|
||||
CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T
|
||||
CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs
|
||||
CarTestRoute("07585b0da3c88459|2021-05-26--18-52-04", HONDA.ACCORD), # hybrid
|
||||
CarTestRoute("f29e2b57a55e7ad5|2021-03-24--20-52-38", HONDA.ACCORD), # hybrid, 2021 with new style HUD msgs
|
||||
CarTestRoute("1ad763dd22ef1a0e|2020-02-29--18-37-03", HONDA.CRV_5G),
|
||||
CarTestRoute("0a96f86fcfe35964|2020-02-05--07-25-51", HONDA.ODYSSEY),
|
||||
CarTestRoute("d83f36766f8012a5|2020-02-05--18-42-21", HONDA.CIVIC_BOSCH_DIESEL),
|
||||
CarTestRoute("f0890d16a07a236b|2021-05-25--17-27-22", HONDA.INSIGHT),
|
||||
CarTestRoute("07d37d27996096b6|2020-03-04--21-57-27", HONDA.PILOT),
|
||||
CarTestRoute("684e8f96bd491a0e|2021-11-03--11-08-42", HONDA.PILOT), # Passport
|
||||
CarTestRoute("0a78dfbacc8504ef|2020-03-04--13-29-55", HONDA.CIVIC_BOSCH),
|
||||
CarTestRoute("f34a60d68d83b1e5|2020-10-06--14-35-55", HONDA.ACURA_RDX),
|
||||
CarTestRoute("54fd8451b3974762|2021-04-01--14-50-10", HONDA.RIDGELINE),
|
||||
CarTestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E),
|
||||
CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022),
|
||||
|
||||
CarTestRoute("87d7f06ade479c2e|2023-09-11--23-30-11", HYUNDAI.AZERA_6TH_GEN),
|
||||
CarTestRoute("66189dd8ec7b50e6|2023-09-20--07-02-12", HYUNDAI.AZERA_HEV_6TH_GEN),
|
||||
CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS),
|
||||
CarTestRoute("b5d6dc830ad63071|2022-12-12--21-28-25", HYUNDAI.GENESIS_GV60_EV_1ST_GEN, segment=12),
|
||||
CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70),
|
||||
CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN),
|
||||
CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80),
|
||||
CarTestRoute("0bbe367c98fa1538|2023-09-16--00-16-49", HYUNDAI.CUSTIN_1ST_GEN),
|
||||
CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN),
|
||||
CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE),
|
||||
CarTestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022),
|
||||
CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022),
|
||||
CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022, segment=1),
|
||||
CarTestRoute("de59124955b921d8|2023-06-24--00-12-50", HYUNDAI.KIA_CARNIVAL_4TH_GEN),
|
||||
CarTestRoute("409c9409979a8abc|2023-07-11--09-06-44", HYUNDAI.KIA_CARNIVAL_4TH_GEN), # Chinese model
|
||||
CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED),
|
||||
CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4),
|
||||
CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0),
|
||||
CarTestRoute("f9716670b2481438|2023-08-23--14-49-50", HYUNDAI.KIA_OPTIMA_H),
|
||||
CarTestRoute("6a42c1197b2a8179|2023-09-21--10-23-44", HYUNDAI.KIA_OPTIMA_H_G4_FL),
|
||||
CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS),
|
||||
CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA),
|
||||
CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF),
|
||||
CarTestRoute("c344fd2492c7a9d2|2023-12-11--09-03-23", HYUNDAI.STARIA_4TH_GEN),
|
||||
CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON),
|
||||
CarTestRoute("db68bbe12250812c|2022-12-05--00-54-12", HYUNDAI.TUCSON_4TH_GEN), # 2023
|
||||
CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_4TH_GEN), # hybrid
|
||||
CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO),
|
||||
CarTestRoute("1d0d000db3370fd0|2023-01-04--22-28-42", HYUNDAI.KIA_SORENTO_4TH_GEN, segment=5),
|
||||
CarTestRoute("fc19648042eb6896|2023-08-16--11-43-27", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN, segment=14),
|
||||
CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN), # plug-in hybrid
|
||||
CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE),
|
||||
CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2
|
||||
CarTestRoute("eb4eae1476647463|2023-08-26--18-07-04", HYUNDAI.IONIQ_6, segment=6), # HDA2
|
||||
CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019),
|
||||
CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV),
|
||||
CarTestRoute("e1107f9d04dfb1e2|2023-09-05--22-32-12", HYUNDAI.IONIQ_PHEV), # openpilot longitudinal enabled
|
||||
CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020),
|
||||
CarTestRoute("610ebb9faaad6b43|2020-06-13--15-28-36", HYUNDAI.IONIQ_EV_LTD),
|
||||
CarTestRoute("2c5cf2dd6102e5da|2020-06-26--16-00-08", HYUNDAI.IONIQ),
|
||||
CarTestRoute("012c95f06918eca4|2023-01-15--11-19-36", HYUNDAI.IONIQ), # openpilot longitudinal enabled
|
||||
CarTestRoute("ab59fe909f626921|2021-10-18--18-34-28", HYUNDAI.IONIQ_HEV_2022),
|
||||
CarTestRoute("22d955b2cd499c22|2020-08-10--19-58-21", HYUNDAI.KONA),
|
||||
CarTestRoute("efc48acf44b1e64d|2021-05-28--21-05-04", HYUNDAI.KONA_EV),
|
||||
CarTestRoute("f90d3cd06caeb6fa|2023-09-06--17-15-47", HYUNDAI.KONA_EV), # openpilot longitudinal enabled
|
||||
CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11),
|
||||
CarTestRoute("1618132d68afc876|2023-08-27--09-32-14", HYUNDAI.KONA_EV_2ND_GEN, segment=13),
|
||||
CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV),
|
||||
CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER),
|
||||
CarTestRoute("5b50b883a4259afb|2022-11-09--15-00-42", HYUNDAI.KIA_STINGER_2022),
|
||||
CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER),
|
||||
CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2
|
||||
CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1
|
||||
CarTestRoute("9b25e8c1484a1b67|2023-04-13--10-41-45", HYUNDAI.KIA_EV6),
|
||||
CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021),
|
||||
CarTestRoute("c58dfc9fc16590e0|2023-01-14--13-51-48", HYUNDAI.KIA_K5_HEV_2020),
|
||||
CarTestRoute("78ad5150de133637|2023-09-13--16-15-57", HYUNDAI.KIA_K8_HEV_1ST_GEN),
|
||||
CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV),
|
||||
CarTestRoute("b153671049a867b3|2023-04-05--10-00-30", HYUNDAI.KIA_NIRO_EV_2ND_GEN),
|
||||
CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV),
|
||||
CarTestRoute("23349923ba5c4e3b|2023-12-02--08-51-54", HYUNDAI.KIA_NIRO_PHEV_2022),
|
||||
CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021),
|
||||
CarTestRoute("db04d2c63990e3ba|2023-02-08--16-52-39", HYUNDAI.KIA_NIRO_HEV_2ND_GEN),
|
||||
CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE),
|
||||
CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN),
|
||||
CarTestRoute("09559f1fcaed4704|2023-11-16--02-24-57", HYUNDAI.KIA_SPORTAGE_5TH_GEN), # openpilot longitudinal
|
||||
CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_5TH_GEN), # hybrid
|
||||
CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA),
|
||||
CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA_GT_I30),
|
||||
CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021),
|
||||
CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.ELANTRA_HEV_2021),
|
||||
CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.SONATA_HYBRID),
|
||||
CarTestRoute("715ac05b594e9c59|2021-10-27--23-24-56", HYUNDAI.GENESIS_G70_2020),
|
||||
CarTestRoute("6b0d44d22df18134|2023-05-06--10-36-55", HYUNDAI.GENESIS_GV80),
|
||||
|
||||
CarTestRoute("00c829b1b7613dea|2021-06-24--09-10-10", TOYOTA.ALPHARD_TSS2),
|
||||
CarTestRoute("912119ebd02c7a42|2022-03-19--07-24-50", TOYOTA.ALPHARD_TSS2), # hybrid
|
||||
CarTestRoute("000cf3730200c71c|2021-05-24--10-42-05", TOYOTA.AVALON),
|
||||
CarTestRoute("0bb588106852abb7|2021-05-26--12-22-01", TOYOTA.AVALON_2019),
|
||||
CarTestRoute("87bef2930af86592|2021-05-30--09-40-54", TOYOTA.AVALON_2019), # hybrid
|
||||
CarTestRoute("e9966711cfb04ce3|2022-01-11--07-59-43", TOYOTA.AVALON_TSS2),
|
||||
CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.AVALON_TSS2), # hybrid
|
||||
CarTestRoute("6cdecc4728d4af37|2020-02-23--15-44-18", TOYOTA.CAMRY),
|
||||
CarTestRoute("2f37c007683e85ba|2023-09-02--14-39-44", TOYOTA.CAMRY), # openpilot longitudinal, with radar CAN filter
|
||||
CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.CAMRY), # hybrid
|
||||
CarTestRoute("3456ad0cd7281b24|2020-12-13--17-45-56", TOYOTA.CAMRY_TSS2),
|
||||
CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.CAMRY_TSS2), # hybrid
|
||||
CarTestRoute("4e45c89c38e8ec4d|2021-05-02--02-49-28", TOYOTA.COROLLA),
|
||||
CarTestRoute("5f5afb36036506e4|2019-05-14--02-09-54", TOYOTA.COROLLA_TSS2),
|
||||
CarTestRoute("5ceff72287a5c86c|2019-10-19--10-59-02", TOYOTA.COROLLA_TSS2), # hybrid
|
||||
CarTestRoute("d2525c22173da58b|2021-04-25--16-47-04", TOYOTA.PRIUS),
|
||||
CarTestRoute("b14c5b4742e6fc85|2020-07-28--19-50-11", TOYOTA.RAV4),
|
||||
CarTestRoute("32a7df20486b0f70|2020-02-06--16-06-50", TOYOTA.RAV4H),
|
||||
CarTestRoute("cdf2f7de565d40ae|2019-04-25--03-53-41", TOYOTA.RAV4_TSS2),
|
||||
CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022),
|
||||
CarTestRoute("ad5a3fa719bc2f83|2023-10-17--19-48-42", TOYOTA.RAV4_TSS2_2023),
|
||||
CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4_TSS2), # hybrid
|
||||
CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4_TSS2_2022), # hybrid
|
||||
CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES),
|
||||
CarTestRoute("37041c500fd30100|2020-12-30--12-17-24", TOYOTA.LEXUS_ES), # hybrid
|
||||
CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2),
|
||||
CarTestRoute("f49e8041283f2939|2019-05-30--11-51-51", TOYOTA.LEXUS_ES_TSS2), # hybrid
|
||||
CarTestRoute("da23c367491f53e2|2021-05-21--09-09-11", TOYOTA.LEXUS_CTH, segment=3),
|
||||
CarTestRoute("32696cea52831b02|2021-11-19--18-13-30", TOYOTA.LEXUS_RC),
|
||||
CarTestRoute("ab9b64a5e5960cba|2023-10-24--17-32-08", TOYOTA.LEXUS_GS_F),
|
||||
CarTestRoute("886fcd8408d570e9|2020-01-29--02-18-55", TOYOTA.LEXUS_RX),
|
||||
CarTestRoute("d27ad752e9b08d4f|2021-05-26--19-39-51", TOYOTA.LEXUS_RX), # hybrid
|
||||
CarTestRoute("01b22eb2ed121565|2020-02-02--11-25-51", TOYOTA.LEXUS_RX_TSS2),
|
||||
CarTestRoute("b74758c690a49668|2020-05-20--15-58-57", TOYOTA.LEXUS_RX_TSS2), # hybrid
|
||||
CarTestRoute("964c09eb11ca8089|2020-11-03--22-04-00", TOYOTA.LEXUS_NX),
|
||||
CarTestRoute("ec429c0f37564e3c|2020-02-01--17-28-12", TOYOTA.LEXUS_NX), # hybrid
|
||||
CarTestRoute("3fd5305f8b6ca765|2021-04-28--19-26-49", TOYOTA.LEXUS_NX_TSS2),
|
||||
CarTestRoute("09ae96064ed85a14|2022-06-09--12-22-31", TOYOTA.LEXUS_NX_TSS2), # hybrid
|
||||
CarTestRoute("4765fbbf59e3cd88|2024-02-06--17-45-32", TOYOTA.LEXUS_LC_TSS2),
|
||||
CarTestRoute("0a302ffddbb3e3d3|2020-02-08--16-19-08", TOYOTA.HIGHLANDER_TSS2),
|
||||
CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.HIGHLANDER_TSS2), # hybrid
|
||||
CarTestRoute("3183cd9b021e89ce|2021-05-25--10-34-44", TOYOTA.HIGHLANDER),
|
||||
CarTestRoute("80d16a262e33d57f|2021-05-23--20-01-43", TOYOTA.HIGHLANDER), # hybrid
|
||||
CarTestRoute("eb6acd681135480d|2019-06-20--20-00-00", TOYOTA.SIENNA),
|
||||
CarTestRoute("2e07163a1ba9a780|2019-08-25--13-15-13", TOYOTA.LEXUS_IS),
|
||||
CarTestRoute("649bf2997ada6e3a|2023-08-08--18-04-22", TOYOTA.LEXUS_IS_TSS2),
|
||||
CarTestRoute("0a0de17a1e6a2d15|2020-09-21--21-24-41", TOYOTA.PRIUS_TSS2),
|
||||
CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.MIRAI),
|
||||
CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.CHR),
|
||||
CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHR), # hybrid
|
||||
CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2),
|
||||
CarTestRoute("ea8fbe72b96a185c|2023-02-22--09-20-34", TOYOTA.CHR_TSS2), # openpilot longitudinal, with smartDSU
|
||||
CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHR_TSS2), # hybrid
|
||||
CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.CHR_TSS2), # hybrid, openpilot longitudinal, radar disabled
|
||||
CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V),
|
||||
|
||||
CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1),
|
||||
CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.ATLAS_MK1),
|
||||
#CarTestRoute("ffcd23abbbd02219|2024-02-28--14-59-38", VOLKSWAGEN.CADDY_MK3),
|
||||
CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), # Stock ACC
|
||||
CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.GOLF_MK7), # openpilot longitudinal
|
||||
CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.JETTA_MK7),
|
||||
CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.PASSAT_MK8),
|
||||
CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.PASSAT_NMS),
|
||||
CarTestRoute("0cd0b7f7e31a3853|2021-11-03--19-30-22", VOLKSWAGEN.POLO_MK6),
|
||||
CarTestRoute("064d1816e448f8eb|2022-09-29--15-32-34", VOLKSWAGEN.SHARAN_MK2),
|
||||
CarTestRoute("7d82b2f3a9115f1f|2021-10-21--15-39-42", VOLKSWAGEN.TAOS_MK1),
|
||||
CarTestRoute("2744c89a8dda9a51|2021-07-24--21-28-06", VOLKSWAGEN.TCROSS_MK1),
|
||||
CarTestRoute("2cef8a0b898f331a|2021-03-25--20-13-57", VOLKSWAGEN.TIGUAN_MK2),
|
||||
CarTestRoute("a589dcc642fdb10a|2021-06-14--20-54-26", VOLKSWAGEN.TOURAN_MK2),
|
||||
CarTestRoute("a459f4556782eba1|2021-09-19--09-48-00", VOLKSWAGEN.TRANSPORTER_T61),
|
||||
CarTestRoute("0cd0b7f7e31a3853|2021-11-18--00-38-32", VOLKSWAGEN.TROC_MK1),
|
||||
CarTestRoute("07667b885add75fd|2021-01-23--19-48-42", VOLKSWAGEN.AUDI_A3_MK3),
|
||||
CarTestRoute("6c6b466346192818|2021-06-06--14-17-47", VOLKSWAGEN.AUDI_Q2_MK1),
|
||||
CarTestRoute("0cd0b7f7e31a3853|2021-12-03--03-12-05", VOLKSWAGEN.AUDI_Q3_MK2),
|
||||
CarTestRoute("8f205bdd11bcbb65|2021-03-26--01-00-17", VOLKSWAGEN.SEAT_ATECA_MK1),
|
||||
CarTestRoute("fc6b6c9a3471c846|2021-05-27--13-39-56", VOLKSWAGEN.SEAT_LEON_MK3),
|
||||
CarTestRoute("0bbe367c98fa1538|2023-03-04--17-46-11", VOLKSWAGEN.SKODA_FABIA_MK4),
|
||||
CarTestRoute("12d6ae3057c04b0d|2021-09-15--00-04-07", VOLKSWAGEN.SKODA_KAMIQ_MK1),
|
||||
CarTestRoute("12d6ae3057c04b0d|2021-09-04--21-21-21", VOLKSWAGEN.SKODA_KAROQ_MK1),
|
||||
CarTestRoute("90434ff5d7c8d603|2021-03-15--12-07-31", VOLKSWAGEN.SKODA_KODIAQ_MK1),
|
||||
CarTestRoute("66e5edc3a16459c5|2021-05-25--19-00-29", VOLKSWAGEN.SKODA_OCTAVIA_MK3),
|
||||
CarTestRoute("026b6d18fba6417f|2021-03-26--09-17-04", VOLKSWAGEN.SKODA_SCALA_MK1),
|
||||
CarTestRoute("b2e9858e29db492b|2021-03-26--16-58-42", VOLKSWAGEN.SKODA_SUPERB_MK3),
|
||||
|
||||
CarTestRoute("3c8f0c502e119c1c|2020-06-30--12-58-02", SUBARU.ASCENT),
|
||||
CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER),
|
||||
CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA),
|
||||
CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020),
|
||||
CarTestRoute("8de015561e1ea4a0|2023-08-29--17-08-31", SUBARU.IMPREZA), # openpilot longitudinal
|
||||
# CarTestRoute("c3d1ccb52f5f9d65|2023-07-22--01-23-20", SUBARU.OUTBACK, segment=9), # gen2 longitudinal, eyesight disabled
|
||||
CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=10),
|
||||
CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3),
|
||||
CarTestRoute("f4e3a0c511a076f4|2022-08-04--16-16-48", SUBARU.CROSSTREK_HYBRID, segment=2),
|
||||
CarTestRoute("7fd1e4f3a33c1673|2022-12-04--15-09-53", SUBARU.FORESTER_2022, segment=4),
|
||||
CarTestRoute("f3b34c0d2632aa83|2023-07-23--20-43-25", SUBARU.OUTBACK_2023, segment=7),
|
||||
CarTestRoute("99437cef6d5ff2ee|2023-03-13--21-21-38", SUBARU.ASCENT_2023, segment=7),
|
||||
# Pre-global, dashcam
|
||||
CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL),
|
||||
CarTestRoute("df5ca7660000fba8|2020-06-16--17-37-19", SUBARU.LEGACY_PREGLOBAL),
|
||||
CarTestRoute("5ab784f361e19b78|2020-06-08--16-30-41", SUBARU.OUTBACK_PREGLOBAL),
|
||||
CarTestRoute("e19eb5d5353b1ac1|2020-08-09--14-37-56", SUBARU.OUTBACK_PREGLOBAL_2018),
|
||||
|
||||
CarTestRoute("fbbfa6af821552b9|2020-03-03--08-09-43", NISSAN.XTRAIL),
|
||||
CarTestRoute("5b7c365c50084530|2020-03-25--22-10-13", NISSAN.LEAF),
|
||||
CarTestRoute("22c3dcce2dd627eb|2020-12-30--16-38-48", NISSAN.LEAF_IC),
|
||||
CarTestRoute("059ab9162e23198e|2020-05-30--09-41-01", NISSAN.ROGUE),
|
||||
CarTestRoute("b72d3ec617c0a90f|2020-12-11--15-38-17", NISSAN.ALTIMA),
|
||||
|
||||
CarTestRoute("32a319f057902bb3|2020-04-27--15-18-58", MAZDA.CX5),
|
||||
CarTestRoute("10b5a4b380434151|2020-08-26--17-11-45", MAZDA.CX9),
|
||||
CarTestRoute("74f1038827005090|2020-08-26--20-05-50", MAZDA.MAZDA3),
|
||||
CarTestRoute("fb53c640f499b73d|2021-06-01--04-17-56", MAZDA.MAZDA6),
|
||||
CarTestRoute("f6d5b1a9d7a1c92e|2021-07-08--06-56-59", MAZDA.CX9_2021),
|
||||
CarTestRoute("a4af1602d8e668ac|2022-02-03--12-17-07", MAZDA.CX5_2022),
|
||||
|
||||
CarTestRoute("6c14ee12b74823ce|2021-06-30--11-49-02", TESLA.AP1_MODELS),
|
||||
CarTestRoute("bb50caf5f0945ab1|2021-06-19--17-20-18", TESLA.AP2_MODELS),
|
||||
#CarTestRoute("66c1699b7697267d/2024-03-03--13-09-53", TESLA.MODELS_RAVEN),
|
||||
|
||||
# Segments that test specific issues
|
||||
# Controls mismatch due to interceptor threshold
|
||||
CarTestRoute("cfb32f0fb91b173b|2022-04-06--14-54-45", HONDA.CIVIC, segment=21),
|
||||
# Controls mismatch due to standstill threshold
|
||||
CarTestRoute("bec2dcfde6a64235|2022-04-08--14-21-32", HONDA.CRV_HYBRID, segment=22),
|
||||
]
|
||||
67
selfdrive/car/tests/test_can_fingerprint.py
Normal file
67
selfdrive/car/tests/test_can_fingerprint.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
from parameterized import parameterized
|
||||
import unittest
|
||||
|
||||
from cereal import log, messaging
|
||||
from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, can_fingerprint
|
||||
from openpilot.selfdrive.car.fingerprints import _FINGERPRINTS as FINGERPRINTS
|
||||
|
||||
|
||||
class TestCanFingerprint(unittest.TestCase):
|
||||
@parameterized.expand(list(FINGERPRINTS.items()))
|
||||
def test_can_fingerprint(self, car_model, fingerprints):
|
||||
"""Tests online fingerprinting function on offline fingerprints"""
|
||||
|
||||
for fingerprint in fingerprints: # can have multiple fingerprints for each platform
|
||||
can = messaging.new_message('can', 1)
|
||||
can.can = [log.CanData(address=address, dat=b'\x00' * length, src=src)
|
||||
for address, length in fingerprint.items() for src in (0, 1)]
|
||||
|
||||
fingerprint_iter = iter([can])
|
||||
empty_can = messaging.new_message('can', 0)
|
||||
car_fingerprint, finger = can_fingerprint(lambda: next(fingerprint_iter, empty_can)) # noqa: B023
|
||||
|
||||
self.assertEqual(car_fingerprint, car_model)
|
||||
self.assertEqual(finger[0], fingerprint)
|
||||
self.assertEqual(finger[1], fingerprint)
|
||||
self.assertEqual(finger[2], {})
|
||||
|
||||
def test_timing(self):
|
||||
# just pick any CAN fingerprinting car
|
||||
car_model = 'CHEVROLET BOLT EUV 2022'
|
||||
fingerprint = FINGERPRINTS[car_model][0]
|
||||
|
||||
cases = []
|
||||
|
||||
# case 1 - one match, make sure we keep going for 100 frames
|
||||
can = messaging.new_message('can', 1)
|
||||
can.can = [log.CanData(address=address, dat=b'\x00' * length, src=src)
|
||||
for address, length in fingerprint.items() for src in (0, 1)]
|
||||
cases.append((FRAME_FINGERPRINT, car_model, can))
|
||||
|
||||
# case 2 - no matches, make sure we keep going for 100 frames
|
||||
can = messaging.new_message('can', 1)
|
||||
can.can = [log.CanData(address=1, dat=b'\x00' * 1, src=src) for src in (0, 1)] # uncommon address
|
||||
cases.append((FRAME_FINGERPRINT, None, can))
|
||||
|
||||
# case 3 - multiple matches, make sure we keep going for 200 frames to try to eliminate some
|
||||
can = messaging.new_message('can', 1)
|
||||
can.can = [log.CanData(address=2016, dat=b'\x00' * 8, src=src) for src in (0, 1)] # common address
|
||||
cases.append((FRAME_FINGERPRINT * 2, None, can))
|
||||
|
||||
for expected_frames, car_model, can in cases:
|
||||
with self.subTest(expected_frames=expected_frames, car_model=car_model):
|
||||
frames = 0
|
||||
|
||||
def test():
|
||||
nonlocal frames
|
||||
frames += 1
|
||||
return can # noqa: B023
|
||||
|
||||
car_fingerprint, _ = can_fingerprint(test)
|
||||
self.assertEqual(car_fingerprint, car_model)
|
||||
self.assertEqual(frames, expected_frames + 2) # TODO: fix extra frames
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -22,7 +22,7 @@ from openpilot.selfdrive.test.fuzzy_generation import DrawType, FuzzyGenerator
|
||||
|
||||
ALL_ECUS = list({ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()})
|
||||
|
||||
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '20'))
|
||||
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '40'))
|
||||
|
||||
|
||||
def get_fuzzy_car_interface_args(draw: DrawType) -> dict:
|
||||
|
||||
97
selfdrive/car/tests/test_docs.py
Normal file
97
selfdrive/car/tests/test_docs.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
import os
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces
|
||||
from openpilot.selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_info
|
||||
from openpilot.selfdrive.car.docs_definitions import Cable, Column, PartType, Star
|
||||
from openpilot.selfdrive.car.honda.values import CAR as HONDA
|
||||
from openpilot.selfdrive.car.values import PLATFORMS
|
||||
from openpilot.selfdrive.debug.dump_car_info import dump_car_info
|
||||
from openpilot.selfdrive.debug.print_docs_diff import print_car_info_diff
|
||||
|
||||
|
||||
class TestCarDocs(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.all_cars = get_all_car_info()
|
||||
|
||||
def test_generator(self):
|
||||
generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
|
||||
with open(CARS_MD_OUT) as f:
|
||||
current_cars_md = f.read()
|
||||
|
||||
self.assertEqual(generated_cars_md, current_cars_md,
|
||||
"Run selfdrive/car/docs.py to update the compatibility documentation")
|
||||
|
||||
def test_docs_diff(self):
|
||||
dump_path = os.path.join(BASEDIR, "selfdrive", "car", "tests", "cars_dump")
|
||||
dump_car_info(dump_path)
|
||||
print_car_info_diff(dump_path)
|
||||
os.remove(dump_path)
|
||||
|
||||
def test_duplicate_years(self):
|
||||
make_model_years = defaultdict(list)
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car_info_name=car.name):
|
||||
make_model = (car.make, car.model)
|
||||
for year in car.year_list:
|
||||
self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year")
|
||||
make_model_years[make_model].append(year)
|
||||
|
||||
def test_missing_car_info(self):
|
||||
all_car_info_platforms = [name for name, config in PLATFORMS.items()]
|
||||
for platform in sorted(interfaces.keys()):
|
||||
with self.subTest(platform=platform):
|
||||
self.assertTrue(platform in all_car_info_platforms, f"Platform: {platform} doesn't have a CarInfo entry")
|
||||
|
||||
def test_naming_conventions(self):
|
||||
# Asserts market-standard car naming conventions by brand
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car=car):
|
||||
tokens = car.model.lower().split(" ")
|
||||
if car.car_name == "hyundai":
|
||||
self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`")
|
||||
self.assertNotIn("hev", tokens, "Use `Hybrid`")
|
||||
if "plug-in hybrid" in car.model.lower():
|
||||
self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization")
|
||||
if car.make != "Kia":
|
||||
self.assertNotIn("ev", tokens, "Use `Electric`")
|
||||
elif car.car_name == "toyota":
|
||||
if "rav4" in tokens:
|
||||
self.assertIn("RAV4", car.model, "Use correct capitalization")
|
||||
|
||||
def test_torque_star(self):
|
||||
# Asserts brand-specific assumptions around steering torque star
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car=car):
|
||||
# honda sanity check, it's the definition of a no torque star
|
||||
if car.car_fingerprint in (HONDA.ACCORD, HONDA.CIVIC, HONDA.CRV, HONDA.ODYSSEY, HONDA.PILOT):
|
||||
self.assertEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has full torque star")
|
||||
elif car.car_name in ("toyota", "hyundai"):
|
||||
self.assertNotEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has no torque star")
|
||||
|
||||
def test_year_format(self):
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car=car):
|
||||
self.assertIsNone(re.search(r"\d{4}-\d{4}", car.name), f"Format years correctly: {car.name}")
|
||||
|
||||
def test_harnesses(self):
|
||||
for car in self.all_cars:
|
||||
with self.subTest(car=car):
|
||||
if car.name == "comma body":
|
||||
raise unittest.SkipTest
|
||||
|
||||
car_part_type = [p.part_type for p in car.car_parts.all_parts()]
|
||||
car_parts = list(car.car_parts.all_parts())
|
||||
self.assertTrue(len(car_parts) > 0, f"Need to specify car parts: {car.name}")
|
||||
self.assertTrue(car_part_type.count(PartType.connector) == 1, f"Need to specify one harness connector: {car.name}")
|
||||
self.assertTrue(car_part_type.count(PartType.mount) == 1, f"Need to specify one mount: {car.name}")
|
||||
self.assertTrue(Cable.right_angle_obd_c_cable_1_5ft in car_parts, f"Need to specify a right angle OBD-C cable (1.5ft): {car.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
96
selfdrive/car/tests/test_fingerprints.py
Normal file
96
selfdrive/car/tests/test_fingerprints.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
# messages reserved for CAN based ignition (see can_ignition_hook function in panda/board/drivers/can)
|
||||
# (addr, len)
|
||||
CAN_IGNITION_MSGS = {
|
||||
'gm': [(0x1F1, 8), (0x160, 5)],
|
||||
#'tesla' : [(0x348, 8)],
|
||||
}
|
||||
|
||||
def _get_fingerprints():
|
||||
# read all the folders in selfdrive/car and return a dict where:
|
||||
# - keys are all the car names that which we have a fingerprint dict for
|
||||
# - values are dicts of fingeprints for each trim
|
||||
fingerprints = {}
|
||||
for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]:
|
||||
car_name = car_folder.split('/')[-1]
|
||||
try:
|
||||
fingerprints[car_name] = __import__(f'selfdrive.car.{car_name}.values', fromlist=['FINGERPRINTS']).FINGERPRINTS
|
||||
except (ImportError, OSError, AttributeError):
|
||||
pass
|
||||
|
||||
return fingerprints
|
||||
|
||||
|
||||
def check_fingerprint_consistency(f1, f2):
|
||||
# return false if it finds a fingerprint fully included in another
|
||||
# max message worth checking is 1800, as above that they usually come too infrequently and not
|
||||
# usable for fingerprinting
|
||||
|
||||
max_msg = 1800
|
||||
|
||||
is_f1_in_f2 = True
|
||||
for k in f1:
|
||||
if (k not in f2 or f1[k] != f2[k]) and k < max_msg:
|
||||
is_f1_in_f2 = False
|
||||
|
||||
is_f2_in_f1 = True
|
||||
for k in f2:
|
||||
if (k not in f1 or f2[k] != f1[k]) and k < max_msg:
|
||||
is_f2_in_f1 = False
|
||||
|
||||
return not is_f1_in_f2 and not is_f2_in_f1
|
||||
|
||||
|
||||
def check_can_ignition_conflicts(fingerprints, brands):
|
||||
# loops through all the fingerprints and exits if CAN ignition dedicated messages
|
||||
# are found in unexpected fingerprints
|
||||
|
||||
for brand_can, msgs_can in CAN_IGNITION_MSGS.items():
|
||||
for i, f in enumerate(fingerprints):
|
||||
for msg_can in msgs_can:
|
||||
if brand_can != brands[i] and msg_can[0] in f and msg_can[1] == f[msg_can[0]]:
|
||||
print("CAN ignition dedicated msg %d with len %d found in %s fingerprints!" % (msg_can[0], msg_can[1], brands[i]))
|
||||
print("TEST FAILED")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fingerprints = _get_fingerprints()
|
||||
|
||||
fingerprints_flat: list[dict] = []
|
||||
car_names = []
|
||||
brand_names = []
|
||||
for brand in fingerprints:
|
||||
for car in fingerprints[brand]:
|
||||
fingerprints_flat += fingerprints[brand][car]
|
||||
for _ in range(len(fingerprints[brand][car])):
|
||||
car_names.append(car)
|
||||
brand_names.append(brand)
|
||||
|
||||
# first check if CAN ignition specific messages are unexpectedly included in other fingerprints
|
||||
check_can_ignition_conflicts(fingerprints_flat, brand_names)
|
||||
|
||||
valid = True
|
||||
for idx1, f1 in enumerate(fingerprints_flat):
|
||||
for idx2, f2 in enumerate(fingerprints_flat):
|
||||
if idx1 < idx2 and not check_fingerprint_consistency(f1, f2):
|
||||
valid = False
|
||||
print(f"Those two fingerprints are inconsistent {car_names[idx1]} {car_names[idx2]}")
|
||||
print("")
|
||||
print(', '.join("%d: %d" % v for v in sorted(f1.items())))
|
||||
print("")
|
||||
print(', '.join("%d: %d" % v for v in sorted(f2.items())))
|
||||
print("")
|
||||
|
||||
print(f"Found {len(fingerprints_flat)} individual fingerprints")
|
||||
if not valid or len(fingerprints_flat) == 0:
|
||||
print("TEST FAILED")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("TEST SUCCESSFUL")
|
||||
313
selfdrive/car/tests/test_fw_fingerprint.py
Normal file
313
selfdrive/car/tests/test_fw_fingerprint.py
Normal file
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import time
|
||||
import unittest
|
||||
from collections import defaultdict
|
||||
from parameterized import parameterized
|
||||
from unittest import mock
|
||||
|
||||
from cereal import car
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces
|
||||
from openpilot.selfdrive.car.fingerprints import FW_VERSIONS
|
||||
from openpilot.selfdrive.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, \
|
||||
match_fw_to_car, get_brand_ecu_matches, get_fw_versions, get_present_ecus
|
||||
from openpilot.selfdrive.car.vin import get_vin
|
||||
|
||||
CarFw = car.CarParams.CarFw
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
|
||||
|
||||
class FakeSocket:
|
||||
def receive(self, non_blocking=False):
|
||||
pass
|
||||
|
||||
def send(self, msg):
|
||||
pass
|
||||
|
||||
|
||||
class TestFwFingerprint(unittest.TestCase):
|
||||
def assertFingerprints(self, candidates, expected):
|
||||
candidates = list(candidates)
|
||||
self.assertEqual(len(candidates), 1, f"got more than one candidate: {candidates}")
|
||||
self.assertEqual(candidates[0], expected)
|
||||
|
||||
@parameterized.expand([(b, c, e[c], n) for b, e in VERSIONS.items() for c in e for n in (True, False)])
|
||||
def test_exact_match(self, brand, car_model, ecus, test_non_essential):
|
||||
config = FW_QUERY_CONFIGS[brand]
|
||||
CP = car.CarParams.new_message()
|
||||
for _ in range(100):
|
||||
fw = []
|
||||
for ecu, fw_versions in ecus.items():
|
||||
# Assume non-essential ECUs apply to all cars, so we catch cases where Car A with
|
||||
# missing ECUs won't match to Car B where only Car B has labeled non-essential ECUs
|
||||
if ecu[0] in config.non_essential_ecus and test_non_essential:
|
||||
continue
|
||||
|
||||
ecu_name, addr, sub_addr = ecu
|
||||
fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand,
|
||||
"address": addr, "subAddress": 0 if sub_addr is None else sub_addr})
|
||||
CP.carFw = fw
|
||||
_, matches = match_fw_to_car(CP.carFw, allow_fuzzy=False)
|
||||
if not test_non_essential:
|
||||
self.assertFingerprints(matches, car_model)
|
||||
else:
|
||||
# if we're removing ECUs we expect some match loss, but it shouldn't mismatch
|
||||
if len(matches) != 0:
|
||||
self.assertFingerprints(matches, car_model)
|
||||
|
||||
@parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e])
|
||||
def test_custom_fuzzy_match(self, brand, car_model, ecus):
|
||||
# Assert brand-specific fuzzy fingerprinting function doesn't disagree with standard fuzzy function
|
||||
config = FW_QUERY_CONFIGS[brand]
|
||||
if config.match_fw_to_car_fuzzy is None:
|
||||
raise unittest.SkipTest("Brand does not implement custom fuzzy fingerprinting function")
|
||||
|
||||
CP = car.CarParams.new_message()
|
||||
for _ in range(5):
|
||||
fw = []
|
||||
for ecu, fw_versions in ecus.items():
|
||||
ecu_name, addr, sub_addr = ecu
|
||||
fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand,
|
||||
"address": addr, "subAddress": 0 if sub_addr is None else sub_addr})
|
||||
CP.carFw = fw
|
||||
_, matches = match_fw_to_car(CP.carFw, allow_exact=False, log=False)
|
||||
brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), VERSIONS[brand])
|
||||
|
||||
# If both have matches, they must agree
|
||||
if len(matches) == 1 and len(brand_matches) == 1:
|
||||
self.assertEqual(matches, brand_matches)
|
||||
|
||||
@parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e])
|
||||
def test_fuzzy_match_ecu_count(self, brand, car_model, ecus):
|
||||
# Asserts that fuzzy matching does not count matching FW, but ECU address keys
|
||||
valid_ecus = [e for e in ecus if e[0] not in FUZZY_EXCLUDE_ECUS]
|
||||
if not len(valid_ecus):
|
||||
raise unittest.SkipTest("Car model has no compatible ECUs for fuzzy matching")
|
||||
|
||||
fw = []
|
||||
for ecu in valid_ecus:
|
||||
ecu_name, addr, sub_addr = ecu
|
||||
for _ in range(5):
|
||||
# Add multiple FW versions to simulate ECU returning to multiple queries in a brand
|
||||
fw.append({"ecu": ecu_name, "fwVersion": random.choice(ecus[ecu]), 'brand': brand,
|
||||
"address": addr, "subAddress": 0 if sub_addr is None else sub_addr})
|
||||
CP = car.CarParams.new_message(carFw=fw)
|
||||
_, matches = match_fw_to_car(CP.carFw, allow_exact=False, log=False)
|
||||
|
||||
# Assert no match if there are not enough unique ECUs
|
||||
unique_ecus = {(f['address'], f['subAddress']) for f in fw}
|
||||
if len(unique_ecus) < 2:
|
||||
self.assertEqual(len(matches), 0, car_model)
|
||||
# There won't always be a match due to shared FW, but if there is it should be correct
|
||||
elif len(matches):
|
||||
self.assertFingerprints(matches, car_model)
|
||||
|
||||
def test_fw_version_lists(self):
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
for ecu, ecu_fw in ecus.items():
|
||||
with self.subTest(ecu):
|
||||
duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1}
|
||||
self.assertFalse(len(duplicates), f'{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}')
|
||||
self.assertGreater(len(ecu_fw), 0, f'{car_model}: No FW versions: Ecu.{ECU_NAME[ecu[0]]}')
|
||||
|
||||
def test_all_addrs_map_to_one_ecu(self):
|
||||
for brand, cars in VERSIONS.items():
|
||||
addr_to_ecu = defaultdict(set)
|
||||
for ecus in cars.values():
|
||||
for ecu_type, addr, sub_addr in ecus.keys():
|
||||
addr_to_ecu[(addr, sub_addr)].add(ecu_type)
|
||||
ecus_for_addr = addr_to_ecu[(addr, sub_addr)]
|
||||
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_for_addr])
|
||||
self.assertLessEqual(len(ecus_for_addr), 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})")
|
||||
|
||||
def test_data_collection_ecus(self):
|
||||
# Asserts no extra ECUs are in the fingerprinting database
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
for car_model, ecus in VERSIONS[brand].items():
|
||||
bad_ecus = set(ecus).intersection(config.extra_ecus)
|
||||
with self.subTest(car_model=car_model.value):
|
||||
self.assertFalse(len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}')
|
||||
|
||||
def test_blacklisted_ecus(self):
|
||||
blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with self.subTest(car_model=car_model.value):
|
||||
CP = interfaces[car_model][0].get_non_essential_params(car_model)
|
||||
if CP.carName == 'subaru':
|
||||
for ecu in ecus.keys():
|
||||
self.assertNotIn(ecu[1], blacklisted_addrs, f'{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})')
|
||||
|
||||
elif CP.carName == "chrysler":
|
||||
# Some HD trucks have a combined TCM and ECM
|
||||
if CP.carFingerprint.startswith("RAM HD"):
|
||||
for ecu in ecus.keys():
|
||||
self.assertNotEqual(ecu[0], Ecu.transmission, f"{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})")
|
||||
|
||||
def test_missing_versions_and_configs(self):
|
||||
brand_versions = set(VERSIONS.keys())
|
||||
brand_configs = set(FW_QUERY_CONFIGS.keys())
|
||||
if len(brand_configs - brand_versions):
|
||||
with self.subTest():
|
||||
self.fail(f"Brands do not implement FW_VERSIONS: {brand_configs - brand_versions}")
|
||||
|
||||
if len(brand_versions - brand_configs):
|
||||
with self.subTest():
|
||||
self.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}")
|
||||
|
||||
# Ensure each brand has at least 1 ECU to query, and extra ECU retrieval
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
self.assertEqual(len(config.get_all_ecus({}, include_extra_ecus=False)), 0)
|
||||
self.assertEqual(config.get_all_ecus({}), set(config.extra_ecus))
|
||||
self.assertGreater(len(config.get_all_ecus(VERSIONS[brand])), 0)
|
||||
|
||||
def test_fw_request_ecu_whitelist(self):
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with self.subTest(brand=brand):
|
||||
whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus}
|
||||
brand_ecus = {fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw}
|
||||
brand_ecus |= {ecu[0] for ecu in config.extra_ecus}
|
||||
|
||||
# each ecu in brand's fw versions + extra ecus needs to be whitelisted at least once
|
||||
ecus_not_whitelisted = brand_ecus - whitelisted_ecus
|
||||
|
||||
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted])
|
||||
self.assertFalse(len(whitelisted_ecus) and len(ecus_not_whitelisted),
|
||||
f'{brand.title()}: ECUs not in any FW query whitelists: {ecu_strings}')
|
||||
|
||||
def test_fw_requests(self):
|
||||
# Asserts equal length request and response lists
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with self.subTest(brand=brand):
|
||||
for request_obj in config.requests:
|
||||
self.assertEqual(len(request_obj.request), len(request_obj.response))
|
||||
|
||||
# No request on the OBD port (bus 1, multiplexed) should be run on an aux panda
|
||||
self.assertFalse(request_obj.auxiliary and request_obj.bus == 1 and request_obj.obd_multiplexing,
|
||||
f"{brand.title()}: OBD multiplexed request is marked auxiliary: {request_obj}")
|
||||
|
||||
def test_brand_ecu_matches(self):
|
||||
empty_response = {brand: set() for brand in FW_QUERY_CONFIGS}
|
||||
self.assertEqual(get_brand_ecu_matches(set()), empty_response)
|
||||
|
||||
# we ignore bus
|
||||
expected_response = empty_response | {'toyota': {(0x750, 0xf)}}
|
||||
self.assertEqual(get_brand_ecu_matches({(0x758, 0xf, 99)}), expected_response)
|
||||
|
||||
|
||||
class TestFwFingerprintTiming(unittest.TestCase):
|
||||
N: int = 5
|
||||
TOL: float = 0.05
|
||||
|
||||
# for patched functions
|
||||
current_obd_multiplexing: bool
|
||||
total_time: float
|
||||
|
||||
def fake_set_obd_multiplexing(self, _, obd_multiplexing):
|
||||
"""The 10Hz blocking params loop adds on average 50ms to the query time for each OBD multiplexing change"""
|
||||
if obd_multiplexing != self.current_obd_multiplexing:
|
||||
self.current_obd_multiplexing = obd_multiplexing
|
||||
self.total_time += 0.1 / 2
|
||||
|
||||
def fake_get_data(self, timeout):
|
||||
self.total_time += timeout
|
||||
return {}
|
||||
|
||||
def _benchmark_brand(self, brand, num_pandas):
|
||||
fake_socket = FakeSocket()
|
||||
self.total_time = 0
|
||||
with (mock.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing),
|
||||
mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)):
|
||||
for _ in range(self.N):
|
||||
# Treat each brand as the most likely (aka, the first) brand with OBD multiplexing initially on
|
||||
self.current_obd_multiplexing = True
|
||||
|
||||
t = time.perf_counter()
|
||||
get_fw_versions(fake_socket, fake_socket, brand, num_pandas=num_pandas)
|
||||
self.total_time += time.perf_counter() - t
|
||||
|
||||
return self.total_time / self.N
|
||||
|
||||
def _assert_timing(self, avg_time, ref_time):
|
||||
self.assertLess(avg_time, ref_time + self.TOL)
|
||||
self.assertGreater(avg_time, ref_time - self.TOL, "Performance seems to have improved, update test refs.")
|
||||
|
||||
def test_startup_timing(self):
|
||||
# Tests worse-case VIN query time and typical present ECU query time
|
||||
vin_ref_times = {'worst': 1.2, 'best': 0.6} # best assumes we go through all queries to get a match
|
||||
present_ecu_ref_time = 0.75
|
||||
|
||||
def fake_get_ecu_addrs(*_, timeout):
|
||||
self.total_time += timeout
|
||||
return set()
|
||||
|
||||
fake_socket = FakeSocket()
|
||||
self.total_time = 0.0
|
||||
with (mock.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing),
|
||||
mock.patch("openpilot.selfdrive.car.fw_versions.get_ecu_addrs", fake_get_ecu_addrs)):
|
||||
for _ in range(self.N):
|
||||
self.current_obd_multiplexing = True
|
||||
get_present_ecus(fake_socket, fake_socket, num_pandas=2)
|
||||
self._assert_timing(self.total_time / self.N, present_ecu_ref_time)
|
||||
print(f'get_present_ecus, query time={self.total_time / self.N} seconds')
|
||||
|
||||
for name, args in (('worst', {}), ('best', {'retry': 1})):
|
||||
with self.subTest(name=name):
|
||||
self.total_time = 0.0
|
||||
with (mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)):
|
||||
for _ in range(self.N):
|
||||
get_vin(fake_socket, fake_socket, (0, 1), **args)
|
||||
self._assert_timing(self.total_time / self.N, vin_ref_times[name])
|
||||
print(f'get_vin {name} case, query time={self.total_time / self.N} seconds')
|
||||
|
||||
def test_fw_query_timing(self):
|
||||
total_ref_time = {1: 8.4, 2: 9.3}
|
||||
brand_ref_times = {
|
||||
1: {
|
||||
'gm': 1.0,
|
||||
'body': 0.1,
|
||||
'chrysler': 0.3,
|
||||
'ford': 1.5,
|
||||
'honda': 0.55,
|
||||
'hyundai': 1.05,
|
||||
'mazda': 0.1,
|
||||
'nissan': 0.8,
|
||||
'subaru': 0.45,
|
||||
'tesla': 0.3,
|
||||
'toyota': 1.6,
|
||||
'volkswagen': 0.65,
|
||||
},
|
||||
2: {
|
||||
'ford': 1.6,
|
||||
'hyundai': 1.85,
|
||||
'tesla': 0.3,
|
||||
}
|
||||
}
|
||||
|
||||
total_times = {1: 0.0, 2: 0.0}
|
||||
for num_pandas in (1, 2):
|
||||
for brand, config in FW_QUERY_CONFIGS.items():
|
||||
with self.subTest(brand=brand, num_pandas=num_pandas):
|
||||
avg_time = self._benchmark_brand(brand, num_pandas)
|
||||
total_times[num_pandas] += avg_time
|
||||
avg_time = round(avg_time, 2)
|
||||
|
||||
ref_time = brand_ref_times[num_pandas].get(brand)
|
||||
if ref_time is None:
|
||||
# ref time should be same as 1 panda if no aux queries
|
||||
ref_time = brand_ref_times[num_pandas - 1][brand]
|
||||
|
||||
self._assert_timing(avg_time, ref_time)
|
||||
print(f'{brand=}, {num_pandas=}, {len(config.requests)=}, avg FW query time={avg_time} seconds')
|
||||
|
||||
for num_pandas in (1, 2):
|
||||
with self.subTest(brand='all_brands', num_pandas=num_pandas):
|
||||
total_time = round(total_times[num_pandas], 2)
|
||||
self._assert_timing(total_time, total_ref_time[num_pandas])
|
||||
print(f'all brands, total FW query time={total_time} seconds')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
101
selfdrive/car/tests/test_lateral_limits.py
Normal file
101
selfdrive/car/tests/test_lateral_limits.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
import importlib
|
||||
from parameterized import parameterized_class
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.car.car_helpers import interfaces
|
||||
from openpilot.selfdrive.car.fingerprints import all_known_cars
|
||||
from openpilot.selfdrive.car.interfaces import get_torque_params
|
||||
from openpilot.selfdrive.car.subaru.values import CAR as SUBARU
|
||||
|
||||
CAR_MODELS = all_known_cars()
|
||||
|
||||
# ISO 11270 - allowed up jerk is strictly lower than recommended limits
|
||||
MAX_LAT_ACCEL = 3.0 # m/s^2
|
||||
MAX_LAT_JERK_UP = 2.5 # m/s^3
|
||||
MAX_LAT_JERK_DOWN = 5.0 # m/s^3
|
||||
MAX_LAT_JERK_UP_TOLERANCE = 0.5 # m/s^3
|
||||
|
||||
# jerk is measured over half a second
|
||||
JERK_MEAS_T = 0.5
|
||||
|
||||
# TODO: put these cars within limits
|
||||
ABOVE_LIMITS_CARS = [
|
||||
SUBARU.LEGACY,
|
||||
SUBARU.OUTBACK,
|
||||
]
|
||||
|
||||
car_model_jerks: defaultdict[str, dict[str, float]] = defaultdict(dict)
|
||||
|
||||
|
||||
@parameterized_class('car_model', [(c,) for c in sorted(CAR_MODELS)])
|
||||
class TestLateralLimits(unittest.TestCase):
|
||||
car_model: str
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
CarInterface, _, _ = interfaces[cls.car_model]
|
||||
CP = CarInterface.get_non_essential_params(cls.car_model)
|
||||
|
||||
if CP.dashcamOnly:
|
||||
raise unittest.SkipTest("Platform is behind dashcamOnly")
|
||||
|
||||
# TODO: test all platforms
|
||||
if CP.lateralTuning.which() != 'torque':
|
||||
raise unittest.SkipTest
|
||||
|
||||
if CP.notCar:
|
||||
raise unittest.SkipTest
|
||||
|
||||
if CP.carFingerprint in ABOVE_LIMITS_CARS:
|
||||
raise unittest.SkipTest
|
||||
|
||||
CarControllerParams = importlib.import_module(f'selfdrive.car.{CP.carName}.values').CarControllerParams
|
||||
cls.control_params = CarControllerParams(CP)
|
||||
cls.torque_params = get_torque_params(cls.car_model)
|
||||
|
||||
@staticmethod
|
||||
def calculate_0_5s_jerk(control_params, torque_params):
|
||||
steer_step = control_params.STEER_STEP
|
||||
max_lat_accel = torque_params['MAX_LAT_ACCEL_MEASURED']
|
||||
|
||||
# Steer up/down delta per 10ms frame, in percentage of max torque
|
||||
steer_up_per_frame = control_params.STEER_DELTA_UP / control_params.STEER_MAX / steer_step
|
||||
steer_down_per_frame = control_params.STEER_DELTA_DOWN / control_params.STEER_MAX / steer_step
|
||||
|
||||
# Lateral acceleration reached in 0.5 seconds, clipping to max torque
|
||||
accel_up_0_5_sec = min(steer_up_per_frame * JERK_MEAS_T / DT_CTRL, 1.0) * max_lat_accel
|
||||
accel_down_0_5_sec = min(steer_down_per_frame * JERK_MEAS_T / DT_CTRL, 1.0) * max_lat_accel
|
||||
|
||||
# Convert to m/s^3
|
||||
return accel_up_0_5_sec / JERK_MEAS_T, accel_down_0_5_sec / JERK_MEAS_T
|
||||
|
||||
def test_jerk_limits(self):
|
||||
up_jerk, down_jerk = self.calculate_0_5s_jerk(self.control_params, self.torque_params)
|
||||
car_model_jerks[self.car_model] = {"up_jerk": up_jerk, "down_jerk": down_jerk}
|
||||
self.assertLessEqual(up_jerk, MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE)
|
||||
self.assertLessEqual(down_jerk, MAX_LAT_JERK_DOWN)
|
||||
|
||||
def test_max_lateral_accel(self):
|
||||
self.assertLessEqual(self.torque_params["MAX_LAT_ACCEL_MEASURED"], MAX_LAT_ACCEL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = unittest.main(exit=False)
|
||||
|
||||
print(f"\n\n---- Lateral limit report ({len(CAR_MODELS)} cars) ----\n")
|
||||
|
||||
max_car_model_len = max([len(car_model) for car_model in car_model_jerks])
|
||||
for car_model, _jerks in sorted(car_model_jerks.items(), key=lambda i: i[1]['up_jerk'], reverse=True):
|
||||
violation = _jerks["up_jerk"] > MAX_LAT_JERK_UP + MAX_LAT_JERK_UP_TOLERANCE or \
|
||||
_jerks["down_jerk"] > MAX_LAT_JERK_DOWN
|
||||
violation_str = " - VIOLATION" if violation else ""
|
||||
|
||||
print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} " +
|
||||
f"m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}")
|
||||
|
||||
# exit with test result
|
||||
sys.exit(not result.result.wasSuccessful())
|
||||
488
selfdrive/car/tests/test_models.py
Normal file
488
selfdrive/car/tests/test_models.py
Normal file
@@ -0,0 +1,488 @@
|
||||
#!/usr/bin/env python3
|
||||
import capnp
|
||||
import os
|
||||
import importlib
|
||||
import pytest
|
||||
import random
|
||||
import unittest
|
||||
from collections import defaultdict, Counter
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import Phase, given, settings
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from cereal import messaging, log, car
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.car import gen_empty_fingerprint
|
||||
from openpilot.selfdrive.car.fingerprints import all_known_cars
|
||||
from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces
|
||||
from openpilot.selfdrive.car.honda.values import CAR as HONDA, HondaFlags
|
||||
from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.selfdrive.test.helpers import read_segment_list
|
||||
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||
from openpilot.tools.lib.logreader import LogReader, internal_source, openpilotci_source
|
||||
from openpilot.tools.lib.route import SegmentName
|
||||
|
||||
from panda.tests.libpanda import libpanda_py
|
||||
|
||||
EventName = car.CarEvent.EventName
|
||||
PandaType = log.PandaState.PandaType
|
||||
SafetyModel = car.CarParams.SafetyModel
|
||||
|
||||
NUM_JOBS = int(os.environ.get("NUM_JOBS", "1"))
|
||||
JOB_ID = int(os.environ.get("JOB_ID", "0"))
|
||||
INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "")
|
||||
INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0"))
|
||||
MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "300"))
|
||||
CI = os.environ.get("CI", None) is not None
|
||||
|
||||
|
||||
def get_test_cases() -> list[tuple[str, CarTestRoute | None]]:
|
||||
# build list of test cases
|
||||
test_cases = []
|
||||
if not len(INTERNAL_SEG_LIST):
|
||||
routes_by_car = defaultdict(set)
|
||||
for r in routes:
|
||||
routes_by_car[r.car_model].add(r)
|
||||
|
||||
for i, c in enumerate(sorted(all_known_cars())):
|
||||
if i % NUM_JOBS == JOB_ID:
|
||||
test_cases.extend(sorted((c, r) for r in routes_by_car.get(c, (None,))))
|
||||
|
||||
else:
|
||||
segment_list = read_segment_list(os.path.join(BASEDIR, INTERNAL_SEG_LIST))
|
||||
segment_list = random.sample(segment_list, INTERNAL_SEG_CNT or len(segment_list))
|
||||
for platform, segment in segment_list:
|
||||
segment_name = SegmentName(segment)
|
||||
test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform,
|
||||
segment=segment_name.segment_num)))
|
||||
return test_cases
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.shared_download_cache
|
||||
class TestCarModelBase(unittest.TestCase):
|
||||
car_model: str | None = None
|
||||
test_route: CarTestRoute | None = None
|
||||
test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket
|
||||
|
||||
can_msgs: list[capnp.lib.capnp._DynamicStructReader]
|
||||
fingerprint: dict[int, dict[int, int]]
|
||||
elm_frame: int | None
|
||||
car_safety_mode_frame: int | None
|
||||
|
||||
@classmethod
|
||||
def get_testing_data_from_logreader(cls, lr):
|
||||
car_fw = []
|
||||
can_msgs = []
|
||||
cls.elm_frame = None
|
||||
cls.car_safety_mode_frame = None
|
||||
cls.fingerprint = gen_empty_fingerprint()
|
||||
experimental_long = False
|
||||
for msg in lr:
|
||||
if msg.which() == "can":
|
||||
can_msgs.append(msg)
|
||||
if len(can_msgs) <= FRAME_FINGERPRINT:
|
||||
for m in msg.can:
|
||||
if m.src < 64:
|
||||
cls.fingerprint[m.src][m.address] = len(m.dat)
|
||||
|
||||
elif msg.which() == "carParams":
|
||||
car_fw = msg.carParams.carFw
|
||||
if msg.carParams.openpilotLongitudinalControl:
|
||||
experimental_long = True
|
||||
if cls.car_model is None and not cls.ci:
|
||||
cls.car_model = msg.carParams.carFingerprint
|
||||
|
||||
# Log which can frame the panda safety mode left ELM327, for CAN validity checks
|
||||
elif msg.which() == 'pandaStates':
|
||||
for ps in msg.pandaStates:
|
||||
if cls.elm_frame is None and ps.safetyModel != SafetyModel.elm327:
|
||||
cls.elm_frame = len(can_msgs)
|
||||
if cls.car_safety_mode_frame is None and ps.safetyModel not in \
|
||||
(SafetyModel.elm327, SafetyModel.noOutput):
|
||||
cls.car_safety_mode_frame = len(can_msgs)
|
||||
|
||||
elif msg.which() == 'pandaStateDEPRECATED':
|
||||
if cls.elm_frame is None and msg.pandaStateDEPRECATED.safetyModel != SafetyModel.elm327:
|
||||
cls.elm_frame = len(can_msgs)
|
||||
if cls.car_safety_mode_frame is None and msg.pandaStateDEPRECATED.safetyModel not in \
|
||||
(SafetyModel.elm327, SafetyModel.noOutput):
|
||||
cls.car_safety_mode_frame = len(can_msgs)
|
||||
|
||||
if len(can_msgs) > int(50 / DT_CTRL):
|
||||
return car_fw, can_msgs, experimental_long
|
||||
|
||||
raise Exception("no can data found")
|
||||
|
||||
@classmethod
|
||||
def get_testing_data(cls):
|
||||
test_segs = (2, 1, 0)
|
||||
if cls.test_route.segment is not None:
|
||||
test_segs = (cls.test_route.segment,)
|
||||
|
||||
is_internal = len(INTERNAL_SEG_LIST)
|
||||
|
||||
for seg in test_segs:
|
||||
segment_range = f"{cls.test_route.route}/{seg}"
|
||||
|
||||
try:
|
||||
lr = LogReader(segment_range, default_source=internal_source if is_internal else openpilotci_source)
|
||||
return cls.get_testing_data_from_logreader(lr)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Route is not in CI bucket, assume either user has access (private), or it is public
|
||||
# test_route_on_ci_bucket will fail when running in CI
|
||||
if not is_internal:
|
||||
cls.test_route_on_bucket = False
|
||||
|
||||
for seg in test_segs:
|
||||
segment_range = f"{cls.test_route.route}/{seg}"
|
||||
try:
|
||||
lr = LogReader(segment_range)
|
||||
return cls.get_testing_data_from_logreader(lr)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded and public?")
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'):
|
||||
raise unittest.SkipTest
|
||||
|
||||
if 'FILTER' in os.environ:
|
||||
if not cls.car_model.startswith(tuple(os.environ.get('FILTER').split(','))):
|
||||
raise unittest.SkipTest
|
||||
|
||||
if cls.test_route is None:
|
||||
if cls.car_model in non_tested_cars:
|
||||
print(f"Skipping tests for {cls.car_model}: missing route")
|
||||
raise unittest.SkipTest
|
||||
raise Exception(f"missing test route for {cls.car_model}")
|
||||
|
||||
car_fw, can_msgs, experimental_long = cls.get_testing_data()
|
||||
|
||||
# if relay is expected to be open in the route
|
||||
cls.openpilot_enabled = cls.car_safety_mode_frame is not None
|
||||
|
||||
cls.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime)
|
||||
|
||||
cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.car_model]
|
||||
cls.CP = cls.CarInterface.get_params(cls.car_model, cls.fingerprint, car_fw, experimental_long, docs=False)
|
||||
assert cls.CP
|
||||
assert cls.CP.carFingerprint == cls.car_model
|
||||
|
||||
os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.can_msgs
|
||||
|
||||
def setUp(self):
|
||||
self.CI = self.CarInterface(self.CP.copy(), self.CarController, self.CarState)
|
||||
assert self.CI
|
||||
|
||||
Params().put_bool("OpenpilotEnabledToggle", self.openpilot_enabled)
|
||||
|
||||
# TODO: check safetyModel is in release panda build
|
||||
self.safety = libpanda_py.libpanda
|
||||
|
||||
cfg = self.CP.safetyConfigs[-1]
|
||||
set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam)
|
||||
self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}")
|
||||
self.safety.init_tests()
|
||||
|
||||
def test_car_params(self):
|
||||
if self.CP.dashcamOnly:
|
||||
self.skipTest("no need to check carParams for dashcamOnly")
|
||||
|
||||
# make sure car params are within a valid range
|
||||
self.assertGreater(self.CP.mass, 1)
|
||||
|
||||
if self.CP.steerControlType != car.CarParams.SteerControlType.angle:
|
||||
tuning = self.CP.lateralTuning.which()
|
||||
if tuning == 'pid':
|
||||
self.assertTrue(len(self.CP.lateralTuning.pid.kpV))
|
||||
elif tuning == 'torque':
|
||||
self.assertTrue(self.CP.lateralTuning.torque.kf > 0)
|
||||
else:
|
||||
raise Exception("unknown tuning")
|
||||
|
||||
def test_car_interface(self):
|
||||
# TODO: also check for checksum violations from can parser
|
||||
can_invalid_cnt = 0
|
||||
can_valid = False
|
||||
CC = car.CarControl.new_message()
|
||||
|
||||
for i, msg in enumerate(self.can_msgs):
|
||||
CS = self.CI.update(CC, (msg.as_builder().to_bytes(),))
|
||||
self.CI.apply(CC, msg.logMonoTime)
|
||||
|
||||
if CS.canValid:
|
||||
can_valid = True
|
||||
|
||||
# wait max of 2s for low frequency msgs to be seen
|
||||
if i > 200 or can_valid:
|
||||
can_invalid_cnt += not CS.canValid
|
||||
|
||||
self.assertEqual(can_invalid_cnt, 0)
|
||||
|
||||
def test_radar_interface(self):
|
||||
RadarInterface = importlib.import_module(f'selfdrive.car.{self.CP.carName}.radar_interface').RadarInterface
|
||||
RI = RadarInterface(self.CP)
|
||||
assert RI
|
||||
|
||||
# Since OBD port is multiplexed to bus 1 (commonly radar bus) while fingerprinting,
|
||||
# start parsing CAN messages after we've left ELM mode and can expect CAN traffic
|
||||
error_cnt = 0
|
||||
for i, msg in enumerate(self.can_msgs[self.elm_frame:]):
|
||||
rr = RI.update((msg.as_builder().to_bytes(),))
|
||||
if rr is not None and i > 50:
|
||||
error_cnt += car.RadarData.Error.canError in rr.errors
|
||||
self.assertEqual(error_cnt, 0)
|
||||
|
||||
def test_panda_safety_rx_checks(self):
|
||||
if self.CP.dashcamOnly:
|
||||
self.skipTest("no need to check panda safety for dashcamOnly")
|
||||
|
||||
start_ts = self.can_msgs[0].logMonoTime
|
||||
|
||||
failed_addrs = Counter()
|
||||
for can in self.can_msgs:
|
||||
# update panda timer
|
||||
t = (can.logMonoTime - start_ts) / 1e3
|
||||
self.safety.set_timer(int(t))
|
||||
|
||||
# run all msgs through the safety RX hook
|
||||
for msg in can.can:
|
||||
if msg.src >= 64:
|
||||
continue
|
||||
|
||||
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
if self.safety.safety_rx_hook(to_send) != 1:
|
||||
failed_addrs[hex(msg.address)] += 1
|
||||
|
||||
# ensure all msgs defined in the addr checks are valid
|
||||
self.safety.safety_tick_current_safety_config()
|
||||
if t > 1e6:
|
||||
self.assertTrue(self.safety.safety_config_valid())
|
||||
|
||||
# Don't check relay malfunction on disabled routes (relay closed),
|
||||
# or before fingerprinting is done (elm327 and noOutput)
|
||||
if self.openpilot_enabled and t / 1e4 > self.car_safety_mode_frame:
|
||||
self.assertFalse(self.safety.get_relay_malfunction())
|
||||
else:
|
||||
self.safety.set_relay_malfunction(False)
|
||||
|
||||
self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}")
|
||||
|
||||
# ensure RX checks go invalid after small time with no traffic
|
||||
self.safety.set_timer(int(t + (2*1e6)))
|
||||
self.safety.safety_tick_current_safety_config()
|
||||
self.assertFalse(self.safety.safety_config_valid())
|
||||
|
||||
def test_panda_safety_tx_cases(self, data=None):
|
||||
"""Asserts we can tx common messages"""
|
||||
if self.CP.notCar:
|
||||
self.skipTest("Skipping test for notCar")
|
||||
|
||||
def test_car_controller(car_control):
|
||||
now_nanos = 0
|
||||
msgs_sent = 0
|
||||
CI = self.CarInterface(self.CP, self.CarController, self.CarState)
|
||||
for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages
|
||||
CI.update(car_control, [])
|
||||
_, sendcan = CI.apply(car_control, now_nanos)
|
||||
|
||||
now_nanos += DT_CTRL * 1e9
|
||||
msgs_sent += len(sendcan)
|
||||
for addr, _, dat, bus in sendcan:
|
||||
to_send = libpanda_py.make_CANPacket(addr, bus % 4, dat)
|
||||
self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus))
|
||||
|
||||
# Make sure we attempted to send messages
|
||||
self.assertGreater(msgs_sent, 50)
|
||||
|
||||
# Make sure we can send all messages while inactive
|
||||
CC = car.CarControl.new_message()
|
||||
test_car_controller(CC)
|
||||
|
||||
# Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
|
||||
self.safety.set_cruise_engaged_prev(True)
|
||||
CC = car.CarControl.new_message(cruiseControl={'cancel': True})
|
||||
test_car_controller(CC)
|
||||
|
||||
# Test resume + general messages (controls_allowed=True & cruise_engaged=True)
|
||||
self.safety.set_controls_allowed(True)
|
||||
CC = car.CarControl.new_message(cruiseControl={'resume': True})
|
||||
test_car_controller(CC)
|
||||
|
||||
# Skip stdout/stderr capture with pytest, causes elevated memory usage
|
||||
@pytest.mark.nocapture
|
||||
@settings(max_examples=MAX_EXAMPLES, deadline=None,
|
||||
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
||||
@given(data=st.data())
|
||||
def test_panda_safety_carstate_fuzzy(self, data):
|
||||
"""
|
||||
For each example, pick a random CAN message on the bus and fuzz its data,
|
||||
checking for panda state mismatches.
|
||||
"""
|
||||
|
||||
if self.CP.dashcamOnly:
|
||||
self.skipTest("no need to check panda safety for dashcamOnly")
|
||||
|
||||
valid_addrs = [(addr, bus, size) for bus, addrs in self.fingerprint.items() for addr, size in addrs.items()]
|
||||
address, bus, size = data.draw(st.sampled_from(valid_addrs))
|
||||
|
||||
msg_strategy = st.binary(min_size=size, max_size=size)
|
||||
msgs = data.draw(st.lists(msg_strategy, min_size=20))
|
||||
|
||||
CC = car.CarControl.new_message()
|
||||
|
||||
for dat in msgs:
|
||||
# due to panda updating state selectively, only edges are expected to match
|
||||
# TODO: warm up CarState with real CAN messages to check edge of both sources
|
||||
# (eg. toyota's gasPressed is the inverse of a signal being set)
|
||||
prev_panda_gas = self.safety.get_gas_pressed_prev()
|
||||
prev_panda_brake = self.safety.get_brake_pressed_prev()
|
||||
prev_panda_regen_braking = self.safety.get_regen_braking_prev()
|
||||
prev_panda_vehicle_moving = self.safety.get_vehicle_moving()
|
||||
prev_panda_cruise_engaged = self.safety.get_cruise_engaged_prev()
|
||||
prev_panda_acc_main_on = self.safety.get_acc_main_on()
|
||||
|
||||
to_send = libpanda_py.make_CANPacket(address, bus, dat)
|
||||
self.safety.safety_rx_hook(to_send)
|
||||
|
||||
can = messaging.new_message('can', 1)
|
||||
can.can = [log.CanData(address=address, dat=dat, src=bus)]
|
||||
|
||||
CS = self.CI.update(CC, (can.to_bytes(),))
|
||||
|
||||
if self.safety.get_gas_pressed_prev() != prev_panda_gas:
|
||||
self.assertEqual(CS.gasPressed, self.safety.get_gas_pressed_prev())
|
||||
|
||||
if self.safety.get_brake_pressed_prev() != prev_panda_brake:
|
||||
# TODO: remove this exception once this mismatch is resolved
|
||||
brake_pressed = CS.brakePressed
|
||||
if CS.brakePressed and not self.safety.get_brake_pressed_prev():
|
||||
if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05:
|
||||
brake_pressed = False
|
||||
|
||||
self.assertEqual(brake_pressed, self.safety.get_brake_pressed_prev())
|
||||
|
||||
if self.safety.get_regen_braking_prev() != prev_panda_regen_braking:
|
||||
self.assertEqual(CS.regenBraking, self.safety.get_regen_braking_prev())
|
||||
|
||||
if self.safety.get_vehicle_moving() != prev_panda_vehicle_moving:
|
||||
self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving())
|
||||
|
||||
if not (self.CP.carName == "honda" and not (self.CP.flags & HondaFlags.BOSCH)):
|
||||
if self.safety.get_cruise_engaged_prev() != prev_panda_cruise_engaged:
|
||||
self.assertEqual(CS.cruiseState.enabled, self.safety.get_cruise_engaged_prev())
|
||||
|
||||
if self.CP.carName == "honda":
|
||||
if self.safety.get_acc_main_on() != prev_panda_acc_main_on:
|
||||
self.assertEqual(CS.cruiseState.available, self.safety.get_acc_main_on())
|
||||
|
||||
def test_panda_safety_carstate(self):
|
||||
"""
|
||||
Assert that panda safety matches openpilot's carState
|
||||
"""
|
||||
if self.CP.dashcamOnly:
|
||||
self.skipTest("no need to check panda safety for dashcamOnly")
|
||||
|
||||
CC = car.CarControl.new_message()
|
||||
|
||||
# warm up pass, as initial states may be different
|
||||
for can in self.can_msgs[:300]:
|
||||
self.CI.update(CC, (can.as_builder().to_bytes(), ))
|
||||
for msg in filter(lambda m: m.src in range(64), can.can):
|
||||
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
self.safety.safety_rx_hook(to_send)
|
||||
|
||||
controls_allowed_prev = False
|
||||
CS_prev = car.CarState.new_message()
|
||||
checks = defaultdict(int)
|
||||
controlsd = Controls(CI=self.CI)
|
||||
controlsd.initialized = True
|
||||
for idx, can in enumerate(self.can_msgs):
|
||||
CS = self.CI.update(CC, (can.as_builder().to_bytes(), ))
|
||||
for msg in filter(lambda m: m.src in range(64), can.can):
|
||||
to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
ret = self.safety.safety_rx_hook(to_send)
|
||||
self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}")
|
||||
|
||||
# Skip first frame so CS_prev is properly initialized
|
||||
if idx == 0:
|
||||
CS_prev = CS
|
||||
# Button may be left pressed in warm up period
|
||||
if not self.CP.pcmCruise:
|
||||
self.safety.set_controls_allowed(0)
|
||||
continue
|
||||
|
||||
# TODO: check rest of panda's carstate (steering, ACC main on, etc.)
|
||||
|
||||
checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev()
|
||||
checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving()
|
||||
|
||||
# TODO: remove this exception once this mismatch is resolved
|
||||
brake_pressed = CS.brakePressed
|
||||
if CS.brakePressed and not self.safety.get_brake_pressed_prev():
|
||||
if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05:
|
||||
brake_pressed = False
|
||||
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
|
||||
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
|
||||
|
||||
if self.CP.pcmCruise:
|
||||
# On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state.
|
||||
# On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but
|
||||
# openpilot brakes to zero even if the min ACC speed is non-zero (i.e. the PCM disengages).
|
||||
if self.CP.carName == "honda" and not (self.CP.flags & HondaFlags.BOSCH):
|
||||
# only the rising edges are expected to match
|
||||
if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled:
|
||||
checks['controlsAllowed'] += not self.safety.get_controls_allowed()
|
||||
else:
|
||||
checks['controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed()
|
||||
|
||||
# TODO: fix notCar mismatch
|
||||
if not self.CP.notCar:
|
||||
checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev()
|
||||
else:
|
||||
# Check for enable events on rising edge of controls allowed
|
||||
controlsd.update_events(CS)
|
||||
controlsd.CS_prev = CS
|
||||
button_enable = (any(evt.enable for evt in CS.events) and
|
||||
not any(evt == EventName.pedalPressed for evt in controlsd.events.names))
|
||||
mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev)
|
||||
checks['controlsAllowed'] += mismatch
|
||||
controls_allowed_prev = self.safety.get_controls_allowed()
|
||||
if button_enable and not mismatch:
|
||||
self.safety.set_controls_allowed(False)
|
||||
|
||||
if self.CP.carName == "honda":
|
||||
checks['mainOn'] += CS.cruiseState.available != self.safety.get_acc_main_on()
|
||||
|
||||
CS_prev = CS
|
||||
|
||||
failed_checks = {k: v for k, v in checks.items() if v > 0}
|
||||
self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}")
|
||||
|
||||
@unittest.skipIf(not CI, "Accessing non CI-bucket routes is allowed only when not in CI")
|
||||
def test_route_on_ci_bucket(self):
|
||||
self.assertTrue(self.test_route_on_bucket, "Route not on CI bucket. " +
|
||||
"This is fine to fail for WIP car ports, just let us know and we can upload your routes to the CI bucket.")
|
||||
|
||||
|
||||
@parameterized_class(('car_model', 'test_route'), get_test_cases())
|
||||
@pytest.mark.xdist_group_class_property('test_route')
|
||||
class TestCarModel(TestCarModelBase):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
3884
selfdrive/car/tests/test_models_segs.txt
Normal file
3884
selfdrive/car/tests/test_models_segs.txt
Normal file
File diff suppressed because it is too large
Load Diff
23
selfdrive/car/tests/test_platform_configs.py
Normal file
23
selfdrive/car/tests/test_platform_configs.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
|
||||
from openpilot.selfdrive.car.values import PLATFORMS
|
||||
|
||||
|
||||
class TestPlatformConfigs(unittest.TestCase):
|
||||
def test_configs(self):
|
||||
|
||||
for platform in PLATFORMS.values():
|
||||
with self.subTest(platform=str(platform)):
|
||||
self.assertTrue(platform.config._frozen)
|
||||
|
||||
if platform != "mock":
|
||||
self.assertIn("pt", platform.config.dbc_dict)
|
||||
self.assertTrue(len(platform.config.platform_str) > 0)
|
||||
|
||||
self.assertIsNotNone(platform.config.specs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user