diff --git a/.vscode/settings.json b/.vscode/settings.json index daf74ca777..a7c9b2ac44 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,15 @@ "**/.git": true, "**/.venv": true, "**/__pycache__": true - } + }, + "python.analysis.exclude": [ + "**/.git", + "**/.venv", + "**/__pycache__", + // exclude directories should be using the symlinked version + "common/**", + "selfdrive/**", + "system/**", + "tools/**", + ] } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8894d5eca2..10b99a9ddc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,6 +177,8 @@ lint.ignore = ["E741", "E402", "C408", "ISC003", "B027", "B024"] line-length = 160 target-version="py311" exclude = [ + "body", + "cereal", "panda", "opendbc", "rednose_repo", diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 1f784a4ab2..525394590d 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -318,3 +318,8 @@ class Platforms(str, ReprEnum): for flag, platforms in platforms_with_flag.items(): print(f"{flag:32s}: {', '.join(p.name for p in platforms)}") + + +class PlatformFlags(IntFlag): + def __init__(self, value: int): + assert value < 2**32, "undefined behaviour with >32 bit flags" diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 9231794a6c..82335500d8 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -70,7 +70,7 @@ class CarD: if prev_cp is not None: self.params.put("CarParamsPrevRoute", prev_cp) - # Write CarParams for radard + # Write CarParams for controls and radard cp_bytes = self.CP.to_bytes() self.params.put("CarParams", cp_bytes) self.params.put_nonblocking("CarParamsCache", cp_bytes) diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 7dcfa3749e..72972fcf4f 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -1,16 +1,15 @@ -from enum import IntFlag from dataclasses import dataclass, field from cereal import car from panda.python import uds -from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, PlatformFlags, Platforms, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu -class ChryslerFlags(IntFlag): +class ChryslerFlags(PlatformFlags): # Detected flags HIGHER_MIN_STEERING_SPEED = 1 diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index add40368be..72131b650b 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,9 +1,9 @@ from dataclasses import dataclass, field -from enum import Enum, IntFlag +from enum import Enum import panda.python.uds as uds from cereal import car -from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, dbc_dict, DbcDict, PlatformConfig, Platforms +from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, PlatformFlags, dbc_dict, DbcDict, PlatformConfig, Platforms from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ Device from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 @@ -39,7 +39,7 @@ class CarControllerParams: pass -class FordFlags(IntFlag): +class FordFlags(PlatformFlags): # Static flags CANFD = 1 diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 4960380bbc..77aa8f6750 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -1,10 +1,10 @@ from dataclasses import dataclass -from enum import Enum, IntFlag +from enum import Enum from cereal import car from openpilot.common.conversions import Conversions as CV from panda.python import uds -from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car import CarSpecs, PlatformConfig, PlatformFlags, Platforms, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 @@ -45,7 +45,7 @@ class CarControllerParams: self.STEER_LOOKUP_V = [v * -1 for v in CP.lateralParams.torqueV][1:][::-1] + list(CP.lateralParams.torqueV) -class HondaFlags(IntFlag): +class HondaFlags(PlatformFlags): # Detected flags # Bosch models with alternate set of LKAS_HUD messages BOSCH_EXT_HUD = 1 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 199e0bba4d..dfd642e3c0 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1,11 +1,11 @@ import re from dataclasses import dataclass, field -from enum import Enum, IntFlag +from enum import Enum from cereal import car from panda.python import uds from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, PlatformFlags, Platforms, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 @@ -51,7 +51,7 @@ class CarControllerParams: self.STEER_MAX = 384 -class HyundaiFlags(IntFlag): +class HyundaiFlags(PlatformFlags): # Dynamic Flags CANFD_HDA2 = 1 CANFD_ALT_BUTTONS = 2 diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index b55690f39a..3a2a93ad09 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -1,9 +1,8 @@ from dataclasses import dataclass, field -from enum import IntFlag from cereal import car from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, PlatformFlags, Platforms, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries @@ -37,7 +36,7 @@ class MazdaCarSpecs(CarSpecs): tireStiffnessFactor: float = 0.7 # not optimized yet -class MazdaFlags(IntFlag): +class MazdaFlags(PlatformFlags): # Static flags # Gen 1 hardware: same CAN messages and same camera GEN1 = 1 diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index a94b97de20..60cc3a0090 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -30,11 +30,6 @@ class CarInterface(CarInterfaceBase): def _update(self, c): ret = self.CS.update(self.cp, self.cp_adas, self.cp_cam) - buttonEvents = [] - be = car.CarState.ButtonEvent.new_message() - be.type = car.CarState.ButtonEvent.Type.accelCruise - buttonEvents.append(be) - events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.brake]) if self.CS.lkas_enabled: diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index cc963404b7..77b30f89be 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -1,9 +1,9 @@ from dataclasses import dataclass, field -from enum import Enum, IntFlag +from enum import Enum from cereal import car from panda.python import uds -from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict +from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, PlatformFlags, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Tool, Column from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 @@ -52,7 +52,7 @@ class CarControllerParams: BRAKE_LOOKUP_V = [BRAKE_MAX, BRAKE_MIN] -class SubaruFlags(IntFlag): +class SubaruFlags(PlatformFlags): # Detected flags SEND_INFOTAINMENT = 1 DISABLE_EYESIGHT = 2 diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 92ee7fa923..265f052b16 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -10,6 +10,7 @@ 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.values import Platform 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 @@ -29,7 +30,7 @@ non_tested_cars = [ class CarTestRoute(NamedTuple): route: str - car_model: str | None + car_model: Platform | None segment: int | None = None diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index b7d20e5a83..1ef8c5b676 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -19,6 +19,7 @@ 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.car.values import PLATFORMS, Platform 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 @@ -64,7 +65,7 @@ def get_test_cases() -> list[tuple[str, CarTestRoute | None]]: @pytest.mark.slow @pytest.mark.shared_download_cache class TestCarModelBase(unittest.TestCase): - car_model: str | None = None + platform: Platform | None = None test_route: CarTestRoute | None = None test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket @@ -93,8 +94,8 @@ class TestCarModelBase(unittest.TestCase): 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 + if cls.platform is None and not cls.ci: + cls.platform = PLATFORMS.get(msg.carParams.carFingerprint) # Log which can frame the panda safety mode left ELM327, for CAN validity checks elif msg.which() == 'pandaStates': @@ -155,15 +156,11 @@ class TestCarModelBase(unittest.TestCase): 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") + if cls.platform in non_tested_cars: + print(f"Skipping tests for {cls.platform}: missing route") raise unittest.SkipTest - raise Exception(f"missing test route for {cls.car_model}") + raise Exception(f"missing test route for {cls.platform}") car_fw, can_msgs, experimental_long = cls.get_testing_data() @@ -172,10 +169,10 @@ class TestCarModelBase(unittest.TestCase): 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) + cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.platform] + cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, experimental_long, docs=False) assert cls.CP - assert cls.CP.carFingerprint == cls.car_model + assert cls.CP.carFingerprint == cls.platform os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT @@ -478,7 +475,7 @@ class TestCarModelBase(unittest.TestCase): "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()) +@parameterized_class(('platform', 'test_route'), get_test_cases()) @pytest.mark.xdist_group_class_property('test_route') class TestCarModel(TestCarModelBase): pass diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 2be7ca1865..50a4195153 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1,11 +1,11 @@ import re from collections import defaultdict from dataclasses import dataclass, field -from enum import Enum, IntFlag +from enum import Enum from cereal import car from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car import CarSpecs, PlatformConfig, Platforms +from openpilot.selfdrive.car import CarSpecs, PlatformConfig, PlatformFlags, Platforms from openpilot.selfdrive.car import AngleRateLimit, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, CarParts, CarHarness from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries @@ -41,7 +41,7 @@ class CarControllerParams: self.STEER_DELTA_DOWN = 25 # always lower than 45 otherwise the Rav4 faults (Prius seems ok with 50) -class ToyotaFlags(IntFlag): +class ToyotaFlags(PlatformFlags): # Detected flags HYBRID = 1 SMART_DSU = 2 diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index a45ddf431f..5f658fb9eb 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,12 +1,12 @@ from collections import namedtuple from dataclasses import dataclass, field -from enum import Enum, IntFlag +from enum import Enum from cereal import car from panda.python import uds from opendbc.can.can_define import CANDefine from openpilot.common.conversions import Conversions as CV -from openpilot.selfdrive.car import dbc_dict, CarSpecs, DbcDict, PlatformConfig, Platforms +from openpilot.selfdrive.car import PlatformFlags, dbc_dict, CarSpecs, DbcDict, PlatformConfig, Platforms from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ Device from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 @@ -109,7 +109,7 @@ class CANBUS: cam = 2 -class VolkswagenFlags(IntFlag): +class VolkswagenFlags(PlatformFlags): # Detected flags STOCK_HCA_PRESENT = 1 diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index b345b929ec..c157b98b62 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -88,23 +88,28 @@ def read_segment_list(segment_list_path): return [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] +@contextlib.contextmanager +def http_server_context(handler, setup=None): + host = '127.0.0.1' + server = http.server.HTTPServer((host, 0), handler) + port = server.server_port + t = threading.Thread(target=server.serve_forever) + t.start() + + if setup is not None: + setup(host, port) + + try: + yield (host, port) + finally: + server.shutdown() + server.server_close() + t.join() + + def with_http_server(func, handler=http.server.BaseHTTPRequestHandler, setup=None): @wraps(func) def inner(*args, **kwargs): - host = '127.0.0.1' - server = http.server.HTTPServer((host, 0), handler) - port = server.server_port - t = threading.Thread(target=server.serve_forever) - t.start() - - if setup is not None: - setup(host, port) - - try: - return func(*args, f'http://{host}:{port}', **kwargs) - finally: - server.shutdown() - server.server_close() - t.join() - + with http_server_context(handler, setup) as (host, port): + return func(*args, f"http://{host}:{port}", **kwargs) return inner diff --git a/selfdrive/updated/tests/test_updated.py b/selfdrive/updated/tests/test_updated.py index d8ce9f3394..93b6e11383 100755 --- a/selfdrive/updated/tests/test_updated.py +++ b/selfdrive/updated/tests/test_updated.py @@ -21,7 +21,7 @@ def run(args, **kwargs): return subprocess.run(args, **kwargs, check=True) -def update_release(directory, name, version, release_notes): +def update_release(directory, name, version, agnos_version, release_notes): with open(directory / "RELEASES.md", "w") as f: f.write(release_notes) @@ -30,6 +30,9 @@ def update_release(directory, name, version, release_notes): with open(directory / "common" / "version.h", "w") as f: f.write(f'#define COMMA_VERSION "{version}"') + with open(directory / "launch_env.sh", "w") as f: + f.write(f'export AGNOS_VERSION="{agnos_version}"') + run(["git", "add", "."], cwd=directory) run(["git", "commit", "-m", f"openpilot release {version}"], cwd=directory) @@ -60,8 +63,8 @@ class TestUpdateD(unittest.TestCase): os.environ["UPDATER_LOCK_FILE"] = str(self.mock_update_path / "safe_staging_overlay.lock") self.MOCK_RELEASES = { - "release3": ("0.1.2", "0.1.2 release notes"), - "master": ("0.1.3", "0.1.3 release notes"), + "release3": ("0.1.2", "1.2", "0.1.2 release notes"), + "master": ("0.1.3", "1.2", "0.1.3 release notes"), } def set_target_branch(self, branch): @@ -97,7 +100,7 @@ class TestUpdateD(unittest.TestCase): self.assertEqual(self.params.get_bool("UpdaterFetchAvailable"), fetch_available) self.assertEqual(self.params.get_bool("UpdateAvailable"), update_available) - def _test_update_params(self, branch, version, release_notes): + def _test_update_params(self, branch, version, agnos_version, release_notes): self.assertTrue(self.params.get("UpdaterNewDescription", encoding="utf-8").startswith(f"{version} / {branch}")) self.assertEqual(self.params.get("UpdaterNewReleaseNotes", encoding="utf-8"), f"

{release_notes}

\n") @@ -116,6 +119,22 @@ class TestUpdateD(unittest.TestCase): time.sleep(1) + def test_no_update(self): + # Start on release3, ensure we don't fetch any updates + self.setup_remote_release("release3") + self.setup_basedir_release("release3") + + with processes_context(["updated"]) as [updated]: + self._test_params("release3", False, False) + time.sleep(1) + self._test_params("release3", False, False) + + self.send_check_for_updates_signal(updated) + + self.wait_for_idle() + + self._test_params("release3", False, False) + def test_new_release(self): # Start on release3, simulate a release3 commit, ensure we fetch that update properly self.setup_remote_release("release3") @@ -126,7 +145,7 @@ class TestUpdateD(unittest.TestCase): time.sleep(1) self._test_params("release3", False, False) - self.MOCK_RELEASES["release3"] = ("0.1.3", "0.1.3 release notes") + self.MOCK_RELEASES["release3"] = ("0.1.3", "1.2", "0.1.3 release notes") self.update_remote_release("release3") self.send_check_for_updates_signal(updated) @@ -167,6 +186,37 @@ class TestUpdateD(unittest.TestCase): self._test_params("master", False, True) self._test_update_params("master", *self.MOCK_RELEASES["master"]) + def test_agnos_update(self): + # Start on release3, push an update with an agnos change + self.setup_remote_release("release3") + self.setup_basedir_release("release3") + + with mock.patch("openpilot.system.hardware.AGNOS", "True"), \ + mock.patch("openpilot.system.hardware.tici.hardware.Tici.get_os_version", "1.2"), \ + mock.patch("openpilot.system.hardware.tici.agnos.get_target_slot_number"), \ + mock.patch("openpilot.system.hardware.tici.agnos.flash_agnos_update"), \ + processes_context(["updated"]) as [updated]: + + self._test_params("release3", False, False) + time.sleep(1) + self._test_params("release3", False, False) + + self.MOCK_RELEASES["release3"] = ("0.1.3", "1.3", "0.1.3 release notes") + self.update_remote_release("release3") + + self.send_check_for_updates_signal(updated) + + self.wait_for_idle() + + self._test_params("release3", True, False) + + self.send_download_signal(updated) + + self.wait_for_idle() + + self._test_params("release3", False, True) + self._test_update_params("release3", *self.MOCK_RELEASES["release3"]) + if __name__ == "__main__": unittest.main() diff --git a/tools/car_porting/test_car_model.py b/tools/car_porting/test_car_model.py index 86980b054b..1dfac7dcf3 100755 --- a/tools/car_porting/test_car_model.py +++ b/tools/car_porting/test_car_model.py @@ -5,6 +5,7 @@ import unittest from openpilot.selfdrive.car.tests.routes import CarTestRoute from openpilot.selfdrive.car.tests.test_models import TestCarModel +from openpilot.selfdrive.car.values import PLATFORMS from openpilot.tools.lib.route import SegmentName @@ -31,7 +32,10 @@ if __name__ == "__main__": route_or_segment_name = SegmentName(args.route_or_segment_name.strip(), allow_route_name=True) segment_num = route_or_segment_name.segment_num if route_or_segment_name.segment_num != -1 else None - test_route = CarTestRoute(route_or_segment_name.route_name.canonical_name, args.car, segment=segment_num) + + platform = PLATFORMS.get(args.car) + + test_route = CarTestRoute(route_or_segment_name.route_name.canonical_name, platform, segment=segment_num) test_suite = create_test_models_suite([test_route], ci=args.ci) unittest.TextTestRunner().run(test_suite)