From f18e42f9a8947ecee10941cae3274f2be7e6a65d Mon Sep 17 00:00:00 2001 From: 1okko <15377594951@189.cn> Date: Tue, 15 Apr 2025 22:05:32 +0800 Subject: [PATCH] MEB888 --- opendbc_repo/opendbc/can/dbc.cc | 4 +- opendbc_repo/opendbc/can/libdbc.so | Bin 3562432 -> 3562416 bytes .../opendbc/can/tests/test_checksums.py | 2 +- opendbc_repo/opendbc/car/__init__.py | 13 +- opendbc_repo/opendbc/car/car.capnp | 3 +- opendbc_repo/opendbc/car/docs_definitions.py | 25 +-- opendbc_repo/opendbc/car/ford/interface.py | 1 + .../opendbc/car/fw_query_definitions.py | 5 + opendbc_repo/opendbc/car/gm/carcontroller.py | 2 +- opendbc_repo/opendbc/car/gm/gmcan.py | 8 +- opendbc_repo/opendbc/car/gm/interface.py | 5 +- .../opendbc/car/gm/radar_interface.py | 3 +- opendbc_repo/opendbc/car/gm/values.py | 17 +- opendbc_repo/opendbc/car/honda/values.py | 2 +- .../opendbc/car/hyundai/fingerprints.py | 5 + opendbc_repo/opendbc/car/hyundai/values.py | 2 +- .../opendbc/car/rivian/carcontroller.py | 14 +- opendbc_repo/opendbc/car/rivian/interface.py | 5 +- opendbc_repo/opendbc/car/rivian/riviancan.py | 54 ++--- opendbc_repo/opendbc/car/rivian/values.py | 38 +++- .../opendbc/car/subaru/fingerprints.py | 5 + opendbc_repo/opendbc/car/tesla/carstate.py | 8 +- .../opendbc/car/tesla/fingerprints.py | 2 + opendbc_repo/opendbc/car/tesla/interface.py | 2 +- opendbc_repo/opendbc/car/tests/routes.py | 2 + .../opendbc/car/tests/test_fw_fingerprint.py | 4 +- .../opendbc/car/torque_data/override.toml | 2 + .../opendbc/car/torque_data/params.toml | 1 + .../opendbc/car/torque_data/substitute.toml | 1 + .../opendbc/car/toyota/fingerprints.py | 17 ++ opendbc_repo/opendbc/car/toyota/interface.py | 1 - opendbc_repo/opendbc/car/toyota/values.py | 7 +- .../opendbc/car/volkswagen/carcontroller.py | 118 ++++++---- .../opendbc/car/volkswagen/carstate.py | 110 +++++++++- .../opendbc/car/volkswagen/fingerprints.py | 13 ++ .../opendbc/car/volkswagen/interface.py | 16 +- opendbc_repo/opendbc/car/volkswagen/values.py | 77 ++++++- .../generator/gm/gm_global_a_powertrain.dbc | 9 +- .../dbc/generator/nissan/_nissan_common.dbc | 2 +- .../dbc/gm_global_a_powertrain_generated.dbc | 9 +- .../dbc/nissan_leaf_2018_generated.dbc | 2 +- .../dbc/nissan_x_trail_2017_generated.dbc | 2 +- .../opendbc/dbc/rivian_primary_actuator.dbc | 34 +-- .../opendbc/dbc/tesla_model3_party.dbc | 38 +++- opendbc_repo/opendbc/dbc/vw_meb.dbc | 16 +- opendbc_repo/opendbc/safety/safety.h | 18 +- .../opendbc/safety/safety/safety_chrysler.h | 9 +- .../opendbc/safety/safety/safety_ford.h | 2 +- .../opendbc/safety/safety/safety_gm.h | 21 +- .../opendbc/safety/safety/safety_hyundai.h | 3 +- .../safety/safety/safety_hyundai_canfd.h | 3 +- .../opendbc/safety/safety/safety_mazda.h | 3 +- .../opendbc/safety/safety/safety_rivian.h | 109 +++++++++- .../opendbc/safety/safety/safety_subaru.h | 3 +- .../safety/safety/safety_subaru_preglobal.h | 3 +- .../opendbc/safety/safety/safety_toyota.h | 5 +- .../safety/safety/safety_volkswagen_common.h | 23 ++ .../safety/safety/safety_volkswagen_mqb.h | 3 +- .../safety/safety/safety_volkswagen_pq.h | 3 +- .../opendbc/safety/safety_declarations.h | 10 +- opendbc_repo/opendbc/safety/tests/common.py | 203 +++++++++++------- .../safety/tests/safety_replay/helpers.py | 4 +- .../opendbc/safety/tests/test_chrysler.py | 7 +- opendbc_repo/opendbc/safety/tests/test_gm.py | 18 +- .../opendbc/safety/tests/test_hyundai.py | 8 +- .../safety/tests/test_hyundai_canfd.py | 4 +- .../opendbc/safety/tests/test_mazda.py | 3 +- .../opendbc/safety/tests/test_rivian.py | 85 ++++++-- .../opendbc/safety/tests/test_subaru.py | 7 +- .../safety/tests/test_subaru_preglobal.py | 3 +- .../opendbc/safety/tests/test_toyota.py | 4 +- .../safety/tests/test_volkswagen_mqb.py | 7 +- .../safety/tests/test_volkswagen_pq.py | 7 +- 73 files changed, 919 insertions(+), 365 deletions(-) diff --git a/opendbc_repo/opendbc/can/dbc.cc b/opendbc_repo/opendbc/can/dbc.cc index 09c93b89..9b81a6ca 100644 --- a/opendbc_repo/opendbc/can/dbc.cc +++ b/opendbc_repo/opendbc/can/dbc.cc @@ -57,9 +57,9 @@ ChecksumState* get_checksum(const std::string& dbc_name) { s = new ChecksumState({8, -1, 7, -1, false, TOYOTA_CHECKSUM, &toyota_checksum}); } else if (startswith(dbc_name, "hyundai_canfd_generated")) { s = new ChecksumState({16, -1, 0, -1, true, HKG_CAN_FD_CHECKSUM, &hkg_can_fd_checksum}); - } else if (startswith(dbc_name, {"vw_mqb_2010", "vw_mqbevo", "vw_meb"})) { + } else if (startswith(dbc_name, {"vw_mqb", "vw_mqbevo", "vw_meb"})) { s = new ChecksumState({8, 4, 0, 0, true, VOLKSWAGEN_MQB_MEB_CHECKSUM, &volkswagen_mqb_meb_checksum}); - } else if (startswith(dbc_name, "vw_golf_mk4")) { + } else if (startswith(dbc_name, "vw_pq")) { s = new ChecksumState({8, 4, 0, -1, true, XOR_CHECKSUM, &xor_checksum}); } else if (startswith(dbc_name, "subaru_global_")) { s = new ChecksumState({8, -1, 0, -1, true, SUBARU_CHECKSUM, &subaru_checksum}); diff --git a/opendbc_repo/opendbc/can/libdbc.so b/opendbc_repo/opendbc/can/libdbc.so index 64c1be5c1219c23e56e7819b6aa0a273f711ea86..6bb7f2d7b063fbec388df72c3fcec4153585ca86 100755 GIT binary patch delta 70199 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 >Q^B)=O-vO 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

JC`<*1zTjo7PZK8%`Iep7I`ke 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 H*Q@b=jsaX? delta 70451 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 SeIBHvew@fsJ?Qz`RsSEdfTOhl diff --git a/opendbc_repo/opendbc/can/tests/test_checksums.py b/opendbc_repo/opendbc/can/tests/test_checksums.py index b9a0a9ad..006bd12e 100644 --- a/opendbc_repo/opendbc/can/tests/test_checksums.py +++ b/opendbc_repo/opendbc/can/tests/test_checksums.py @@ -93,7 +93,7 @@ class TestCanChecksums: def verify_volkswagen_mqb_crc(self, subtests, msg_name: str, msg_addr: int, test_messages: list[bytes], counter_field: str = 'COUNTER'): """Test AUTOSAR E2E Profile 2 CRCs""" assert len(test_messages) == 16 # All counter values must be tested - self.verify_checksum(subtests, "vw_mqb_2010", msg_name, msg_addr, test_messages, counter_field=counter_field) + self.verify_checksum(subtests, "vw_mqb", msg_name, msg_addr, test_messages, counter_field=counter_field) def test_volkswagen_mqb_crc_lwi_01(self, subtests): self.verify_volkswagen_mqb_crc(subtests, "LWI_01", 0x86, [ diff --git a/opendbc_repo/opendbc/car/__init__.py b/opendbc_repo/opendbc/car/__init__.py index dde58dd8..b8308532 100644 --- a/opendbc_repo/opendbc/car/__init__.py +++ b/opendbc_repo/opendbc/car/__init__.py @@ -98,13 +98,16 @@ class Bus(StrEnum): ap_party = auto() -def apply_driver_steer_torque_limits(apply_torque, apply_torque_last, driver_torque, LIMITS): +def apply_driver_steer_torque_limits(apply_torque: int, apply_torque_last: int, driver_torque: float, LIMITS, steer_max: int = None): + # some safety modes utilize a dynamic max steer + if steer_max is None: + steer_max = LIMITS.STEER_MAX # limits due to driver torque - driver_max_torque = LIMITS.STEER_MAX + (LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER - driver_min_torque = -LIMITS.STEER_MAX + (-LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER - max_steer_allowed = max(min(LIMITS.STEER_MAX, driver_max_torque), 0) - min_steer_allowed = min(max(-LIMITS.STEER_MAX, driver_min_torque), 0) + driver_max_torque = steer_max + (LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER + driver_min_torque = -steer_max + (-LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER + max_steer_allowed = max(min(steer_max, driver_max_torque), 0) + min_steer_allowed = min(max(-steer_max, driver_min_torque), 0) apply_torque = np.clip(apply_torque, min_steer_allowed, max_steer_allowed) # slow rate if steer torque increases in magnitude diff --git a/opendbc_repo/opendbc/car/car.capnp b/opendbc_repo/opendbc/car/car.capnp index 28d1dcea..5c5a7d68 100644 --- a/opendbc_repo/opendbc/car/car.capnp +++ b/opendbc_repo/opendbc/car/car.capnp @@ -470,10 +470,11 @@ struct CarParams { minEnableSpeed @7 :Float32; minSteerSpeed @8 :Float32; + steerAtStandstill @77 :Bool; # is steering available at standstill? just check if it faults safetyConfigs @62 :List(SafetyConfig); alternativeExperience @65 :Int16; # panda flag for features like no disengage on gas - # Car docs fields + # Car docs fields, not used for control maxLateralAccel @68 :Float32; autoResumeSng @69 :Bool; # describes whether car can resume from a stop automatically diff --git a/opendbc_repo/opendbc/car/docs_definitions.py b/opendbc_repo/opendbc/car/docs_definitions.py index bf44cd3d..846c2c3d 100644 --- a/opendbc_repo/opendbc/car/docs_definitions.py +++ b/opendbc_repo/opendbc/car/docs_definitions.py @@ -75,7 +75,7 @@ class Mount(EnumBase): class Cable(EnumBase): - long_obdc_cable = BasePart("long OBD-C cable") + long_obdc_cable = BasePart("long OBD-C cable (9.5 ft)") usb_a_2_a_cable = BasePart("USB A-A cable") usbc_otg_cable = BasePart("USB C OTG cable") usbc_coupler = BasePart("USB-C coupler") @@ -85,12 +85,12 @@ class Cable(EnumBase): class Accessory(EnumBase): harness_box = BasePart("harness box") - comma_power_v2 = BasePart("comma power v2") + comma_power = BasePart("comma power v3") @dataclass class BaseCarHarness(BasePart): - parts: list[Enum] = field(default_factory=lambda: [Accessory.harness_box, Accessory.comma_power_v2]) + parts: list[Enum] = field(default_factory=lambda: [Accessory.harness_box, Accessory.comma_power]) has_connector: bool = True # without are hidden on the harness connector page @@ -108,7 +108,8 @@ class CarHarness(EnumBase): fca = BaseCarHarness("FCA connector") ram = BaseCarHarness("Ram connector") vw_a = BaseCarHarness("VW A connector") - vw_j533 = BaseCarHarness("VW J533 connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler, Accessory.comma_power_v2]) + vw_c = BaseCarHarness("VW C connector") + vw_j533 = BaseCarHarness("VW J533 connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler]) hyundai_a = BaseCarHarness("Hyundai A connector") hyundai_b = BaseCarHarness("Hyundai B connector") hyundai_c = BaseCarHarness("Hyundai C connector") @@ -128,17 +129,17 @@ class CarHarness(EnumBase): hyundai_q = BaseCarHarness("Hyundai Q connector") hyundai_r = BaseCarHarness("Hyundai R connector") custom = BaseCarHarness("Developer connector") - obd_ii = BaseCarHarness("OBD-II connector", parts=[Cable.long_obdc_cable], has_connector=False) + obd_ii = BaseCarHarness("OBD-II connector", parts=[Cable.long_obdc_cable, Cable.usbc_coupler], has_connector=False) gm = BaseCarHarness("GM connector", parts=[Accessory.harness_box]) - gmsdgm = BaseCarHarness("GM SDGM connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler, Accessory.comma_power_v2]) - nissan_a = BaseCarHarness("Nissan A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler]) - nissan_b = BaseCarHarness("Nissan B connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler]) + gmsdgm = BaseCarHarness("GM SDGM connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler]) + nissan_a = BaseCarHarness("Nissan A connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler]) + nissan_b = BaseCarHarness("Nissan B connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler]) mazda = BaseCarHarness("Mazda connector") ford_q3 = BaseCarHarness("Ford Q3 connector") - ford_q4 = BaseCarHarness("Ford Q4 connector", parts=[Accessory.harness_box, Accessory.comma_power_v2, Cable.long_obdc_cable, Cable.usbc_coupler]) - rivian = BaseCarHarness("Rivian A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler]) - tesla_a = BaseCarHarness("Tesla A connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler]) - tesla_b = BaseCarHarness("Tesla B connector", parts=[Accessory.harness_box, Cable.long_obdc_cable, Cable.usbc_coupler]) + ford_q4 = BaseCarHarness("Ford Q4 connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler]) + rivian = BaseCarHarness("Rivian A connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler]) + tesla_a = BaseCarHarness("Tesla A connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler]) + tesla_b = BaseCarHarness("Tesla B connector", parts=[Accessory.harness_box, Accessory.comma_power, Cable.long_obdc_cable, Cable.usbc_coupler]) class Device(EnumBase): diff --git a/opendbc_repo/opendbc/car/ford/interface.py b/opendbc_repo/opendbc/car/ford/interface.py index f1e1bfe2..6f08c10a 100644 --- a/opendbc_repo/opendbc/car/ford/interface.py +++ b/opendbc_repo/opendbc/car/ford/interface.py @@ -33,6 +33,7 @@ class CarInterface(CarInterfaceBase): ret.steerControlType = structs.CarParams.SteerControlType.angle ret.steerActuatorDelay = 0.2 ret.steerLimitTimer = 1.0 + ret.steerAtStandstill = True ret.longitudinalTuning.kiBP = [0.] ret.longitudinalTuning.kiV = [0.5] diff --git a/opendbc_repo/opendbc/car/fw_query_definitions.py b/opendbc_repo/opendbc/car/fw_query_definitions.py index 16e569b0..5c48fed6 100644 --- a/opendbc_repo/opendbc/car/fw_query_definitions.py +++ b/opendbc_repo/opendbc/car/fw_query_definitions.py @@ -56,6 +56,11 @@ class StdQueries: SUPPLIER_SOFTWARE_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ p16(uds.DATA_IDENTIFIER_TYPE.SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER) + MANUFACTURER_ECU_HARDWARE_NUMBER_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER) + MANUFACTURER_ECU_HARDWARE_NUMBER_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER) + UDS_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) UDS_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ diff --git a/opendbc_repo/opendbc/car/gm/carcontroller.py b/opendbc_repo/opendbc/car/gm/carcontroller.py index bbfd7056..ce5655c9 100644 --- a/opendbc_repo/opendbc/car/gm/carcontroller.py +++ b/opendbc_repo/opendbc/car/gm/carcontroller.py @@ -89,7 +89,7 @@ class CarController(CarControllerBase): self.apply_gas = self.params.INACTIVE_REGEN self.apply_brake = 0 else: - self.apply_gas = int(round(np.interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) + self.apply_gas = float(np.interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)) self.apply_brake = int(round(np.interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) # Don't allow any gas above inactive regen while stopping # FIXME: brakes aren't applied immediately when enabling at a stop diff --git a/opendbc_repo/opendbc/car/gm/gmcan.py b/opendbc_repo/opendbc/car/gm/gmcan.py index c9d1effc..cb74b609 100644 --- a/opendbc_repo/opendbc/car/gm/gmcan.py +++ b/opendbc_repo/opendbc/car/gm/gmcan.py @@ -56,16 +56,14 @@ def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop): values = { "GasRegenCmdActive": enabled, "RollingCounter": idx, - "GasRegenCmdActiveInv": 1 - enabled, "GasRegenCmd": throttle, "GasRegenFullStopActive": at_full_stop, - "GasRegenAlwaysOne": 1, - "GasRegenAlwaysOne2": 1, - "GasRegenAlwaysOne3": 1, + "GasRegenAccType": 1, } dat = packer.make_can_msg("ASCMGasRegenCmd", bus, values)[1] - values["GasRegenChecksum"] = (((0xff - dat[1]) & 0xff) << 16) | \ + values["GasRegenChecksum"] = ((1 - enabled) << 24) | \ + (((0xff - dat[1]) & 0xff) << 16) | \ (((0xff - dat[2]) & 0xff) << 8) | \ ((0x100 - dat[3] - idx) & 0xff) diff --git a/opendbc_repo/opendbc/car/gm/interface.py b/opendbc_repo/opendbc/car/gm/interface.py index 8059ab12..2f8c0979 100755 --- a/opendbc_repo/opendbc/car/gm/interface.py +++ b/opendbc_repo/opendbc/car/gm/interface.py @@ -7,7 +7,7 @@ from opendbc.car.common.basedir import BASEDIR from opendbc.car.common.conversions import Conversions as CV from opendbc.car.gm.carcontroller import CarController from opendbc.car.gm.carstate import CarState -from opendbc.car.gm.radar_interface import RadarInterface, RADAR_HEADER_MSG +from opendbc.car.gm.radar_interface import RadarInterface, RADAR_HEADER_MSG, CAMERA_DATA_HEADER_MSG from opendbc.car.gm.values import CAR, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, SDGM_CAR, ALT_ACCS, CanBus, GMSafetyFlags from opendbc.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel @@ -127,7 +127,8 @@ class CarInterface(CarInterfaceBase): else: # ASCM, OBD-II harness ret.openpilotLongitudinalControl = True ret.networkLocation = NetworkLocation.gateway - ret.radarUnavailable = RADAR_HEADER_MSG not in fingerprint[CanBus.OBSTACLE] and not docs + # LRR messages can take up to a few seconds to start sending after ignition, check camera data as well which starts earlier + ret.radarUnavailable = RADAR_HEADER_MSG not in fingerprint[CanBus.OBSTACLE] and CAMERA_DATA_HEADER_MSG not in fingerprint[CanBus.OBSTACLE] and not docs ret.pcmCruise = False # stock non-adaptive cruise control is kept off # supports stop and go, but initial engage must (conservatively) be above 18mph ret.minEnableSpeed = 18 * CV.MPH_TO_MS diff --git a/opendbc_repo/opendbc/car/gm/radar_interface.py b/opendbc_repo/opendbc/car/gm/radar_interface.py index 2daffb85..3bbfc9ca 100755 --- a/opendbc_repo/opendbc/car/gm/radar_interface.py +++ b/opendbc_repo/opendbc/car/gm/radar_interface.py @@ -6,7 +6,8 @@ from opendbc.car.common.conversions import Conversions as CV from opendbc.car.gm.values import DBC, CanBus from opendbc.car.interfaces import RadarInterfaceBase -RADAR_HEADER_MSG = 1120 +RADAR_HEADER_MSG = 1120 # F_LRR_Obj_Header +CAMERA_DATA_HEADER_MSG = 1056 # F_Vision_Obj_Header SLOT_1_MSG = RADAR_HEADER_MSG + 1 NUM_SLOTS = 20 diff --git a/opendbc_repo/opendbc/car/gm/values.py b/opendbc_repo/opendbc/car/gm/values.py index b2b4e538..a32c3aa4 100644 --- a/opendbc_repo/opendbc/car/gm/values.py +++ b/opendbc_repo/opendbc/car/gm/values.py @@ -34,27 +34,26 @@ class CarControllerParams: def __init__(self, CP): # Gas/brake lookups - self.ZERO_GAS = 2048 # Coasting self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen if CP.carFingerprint in (CAMERA_ACC_CAR | SDGM_CAR): - self.MAX_GAS = 3400 - self.MAX_ACC_REGEN = 1514 - self.INACTIVE_REGEN = 1554 + self.MAX_GAS = 1346.0 + self.MAX_ACC_REGEN = -540.0 + self.INACTIVE_REGEN = -500.0 # Camera ACC vehicles have no regen while enabled. - # Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly + # Camera transitions to MAX_ACC_REGEN from zero gas and uses friction brakes instantly max_regen_acceleration = 0. else: - self.MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill. - self.MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen - self.INACTIVE_REGEN = 1404 + self.MAX_GAS = 1018.0 # Safety limit, not ACC max. Stock ACC >2042 from standstill. + self.MAX_ACC_REGEN = -650.0 # Max ACC regen is slightly less than max paddle regen + self.INACTIVE_REGEN = -650.0 # ICE has much less engine braking force compared to regen in EVs, # lower threshold removes some braking deadzone max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1 self.GAS_LOOKUP_BP = [max_regen_acceleration, 0., self.ACCEL_MAX] - self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS] + self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, 0., self.MAX_GAS] self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, max_regen_acceleration] self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.] diff --git a/opendbc_repo/opendbc/car/honda/values.py b/opendbc_repo/opendbc/car/honda/values.py index 9233f750..04ffb3f8 100644 --- a/opendbc_repo/opendbc/car/honda/values.py +++ b/opendbc_repo/opendbc/car/honda/values.py @@ -255,7 +255,7 @@ class CAR(Platforms): HONDA_PILOT = HondaNidecPlatformConfig( [ HondaCarDocs("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS), - HondaCarDocs("Honda Passport 2019-23", "All", min_steer_speed=12. * CV.MPH_TO_MS), + HondaCarDocs("Honda Passport 2019-25", "All", min_steer_speed=12. * CV.MPH_TO_MS), ], CarSpecs(mass=4278 * CV.LB_TO_KG, wheelbase=2.86, centerToFrontRatio=0.428, steerRatio=16.0, tireStiffnessFactor=0.444), # as spec radar_dbc_dict('acura_ilx_2016_can_generated'), diff --git a/opendbc_repo/opendbc/car/hyundai/fingerprints.py b/opendbc_repo/opendbc/car/hyundai/fingerprints.py index 9d7c7831..9812bdde 100644 --- a/opendbc_repo/opendbc/car/hyundai/fingerprints.py +++ b/opendbc_repo/opendbc/car/hyundai/fingerprints.py @@ -53,11 +53,13 @@ FW_VERSIONS = { b'\xf1\x00AE MDPS C 1.00 1.05 56310/G2501 4AEHC105', b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2301 4AEHC107', b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2501 4AEHC107', + b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2551 4AEHC107', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G7200 160418', b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2400 180222', + b'\xf1\x00AEH MFC AT EUR RHD 1.00 1.00 95740-G2400 180222', ], }, CAR.HYUNDAI_IONIQ_PHEV_2019: { @@ -728,9 +730,11 @@ FW_VERSIONS = { }, CAR.KIA_NIRO_EV_2ND_GEN: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00SG__ RDR ----- 1.00 1.00 99110-AT200 ', b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00SG2EMFC AT EUR LHD 1.00 1.00 99211-AT200 240315', b'\xf1\x00SG2EMFC AT EUR LHD 1.01 1.09 99211-AT000 220801', b'\xf1\x00SG2EMFC AT USA LHD 1.01 1.09 99211-AT000 220801', ], @@ -1043,6 +1047,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.00 99211-N9260 14Y', b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.01 99211-N9100 14A', + b'\xf1\x00NX4 FR_CMR AT CAN LHD 1.00 1.00 99211-N9220 14K', b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 1.00 99211-N9220 14K', b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', diff --git a/opendbc_repo/opendbc/car/hyundai/values.py b/opendbc_repo/opendbc/car/hyundai/values.py index 8772fb4a..f72abaf1 100644 --- a/opendbc_repo/opendbc/car/hyundai/values.py +++ b/opendbc_repo/opendbc/car/hyundai/values.py @@ -413,7 +413,7 @@ class CAR(Platforms): flags=HyundaiFlags.MANDO_RADAR | HyundaiFlags.EV, ) KIA_NIRO_EV_2ND_GEN = HyundaiCanFDPlatformConfig( - [HyundaiCarDocs("Kia Niro EV 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_a]))], + [HyundaiCarDocs("Kia Niro EV 2023-24", "All", car_parts=CarParts.common([CarHarness.hyundai_a]))], KIA_NIRO_EV.specs, flags=HyundaiFlags.EV, ) diff --git a/opendbc_repo/opendbc/car/rivian/carcontroller.py b/opendbc_repo/opendbc/car/rivian/carcontroller.py index 88289582..c1d0d308 100644 --- a/opendbc_repo/opendbc/car/rivian/carcontroller.py +++ b/opendbc_repo/opendbc/car/rivian/carcontroller.py @@ -1,3 +1,4 @@ +import numpy as np from opendbc.can.packer import CANPacker from opendbc.car import Bus, apply_driver_steer_torque_limits from opendbc.car.interfaces import CarControllerBase @@ -18,21 +19,24 @@ class CarController(CarControllerBase): can_sends = [] apply_torque = 0 + steer_max = round(float(np.interp(CS.out.vEgoRaw, CarControllerParams.STEER_MAX_LOOKUP[0], + CarControllerParams.STEER_MAX_LOOKUP[1]))) if CC.latActive: - new_torque = int(round(CC.actuators.torque * CarControllerParams.STEER_MAX)) + new_torque = int(round(CC.actuators.torque * steer_max)) apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, - CS.out.steeringTorque, CarControllerParams) + CS.out.steeringTorque, CarControllerParams, steer_max) # send steering command self.apply_torque_last = apply_torque - can_sends.append(create_lka_steering(self.packer, CS.acm_lka_hba_cmd, apply_torque, CC.latActive)) + can_sends.append(create_lka_steering(self.packer, self.frame, CS.acm_lka_hba_cmd, apply_torque, CC.enabled, CC.latActive)) if self.frame % 5 == 0: can_sends.append(create_wheel_touch(self.packer, CS.sccm_wheel_touch, CC.enabled)) # Longitudinal control if self.CP.openpilotLongitudinalControl: - can_sends.append(create_longitudinal(self.packer, self.frame % 15, actuators.accel, CC.enabled)) + accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)) + can_sends.append(create_longitudinal(self.packer, self.frame, accel, CC.enabled)) else: interface_status = None if CC.cruiseControl.cancel: @@ -46,7 +50,7 @@ class CarController(CarControllerBase): can_sends.append(create_adas_status(self.packer, CS.vdm_adas_status, interface_status)) new_actuators = actuators.as_builder() - new_actuators.torque = apply_torque / CarControllerParams.STEER_MAX + new_actuators.torque = apply_torque / steer_max new_actuators.torqueOutputCan = apply_torque self.frame += 1 diff --git a/opendbc_repo/opendbc/car/rivian/interface.py b/opendbc_repo/opendbc/car/rivian/interface.py index bc6f66e5..509889b5 100644 --- a/opendbc_repo/opendbc/car/rivian/interface.py +++ b/opendbc_repo/opendbc/car/rivian/interface.py @@ -3,6 +3,7 @@ from opendbc.car.interfaces import CarInterfaceBase from opendbc.car.rivian.carcontroller import CarController from opendbc.car.rivian.carstate import CarState from opendbc.car.rivian.radar_interface import RadarInterface +from opendbc.car.rivian.values import RivianSafetyFlags class CarInterface(CarInterfaceBase): @@ -16,7 +17,7 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.rivian)] - ret.steerActuatorDelay = 0.25 + ret.steerActuatorDelay = 0.15 ret.steerLimitTimer = 0.4 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) @@ -27,7 +28,7 @@ class CarInterface(CarInterfaceBase): ret.experimentalLongitudinalAvailable = False if experimental_long: ret.openpilotLongitudinalControl = True - #ret.safetyConfigs[0].safetyParam |= Panda.FLAG_RIVIAN_LONG_CONTROL + ret.safetyConfigs[0].safetyParam |= RivianSafetyFlags.LONG_CONTROL.value ret.longitudinalActuatorDelay = 0.35 ret.vEgoStopping = 0.25 diff --git a/opendbc_repo/opendbc/car/rivian/riviancan.py b/opendbc_repo/opendbc/car/rivian/riviancan.py index 9961306c..f22b5917 100644 --- a/opendbc_repo/opendbc/car/rivian/riviancan.py +++ b/opendbc_repo/opendbc/car/rivian/riviancan.py @@ -11,40 +11,32 @@ def checksum(data, poly, xor_output): return crc ^ xor_output -def create_lka_steering(packer, acm_lka_hba_cmd, apply_torque, enabled): - values = {s: acm_lka_hba_cmd[s] for s in [ - "ACM_lkaHbaCmd_Counter", - "ACM_lkaHbaCmd_Checksum", - "ACM_HapticRequest", - "ACM_lkaStrToqReq", - "ACM_lkaSymbolState", - "ACM_lkaToiFlt", - "ACM_lkaActToi", +def create_lka_steering(packer, frame, acm_lka_hba_cmd, apply_torque, enabled, active): + # forward auto high beam and speed limit status and nothing else + values = {s: acm_lka_hba_cmd[s] for s in ( "ACM_hbaSysState", - "ACM_FailinfoAeb", - "ACM_lkaRHWarning", - "ACM_lkaLHWarning", - "ACM_lkaLaneRecogState", - "ACM_hbaOpt", "ACM_hbaLamp", - "ACM_lkaHandsoffSoundWarning", - "ACM_lkaHandsoffDisplayWarning", - "ACM_unkown1", - "ACM_unkown2", - "ACM_unkown3", - "ACM_unkown4", - "ACM_unkown6", - ]} + "ACM_hbaOnOffState", + "ACM_slifOnOffState", + )} - if enabled: - values["ACM_lkaActToi"] = 1 - values["ACM_lkaSymbolState"] = 3 - values["ACM_lkaLaneRecogState"] = 3 - values["ACM_lkaStrToqReq"] = apply_torque - values["ACM_unkown2"] = 1 - values["ACM_unkown3"] = 4 - values["ACM_unkown4"] = 160 - values["ACM_unkown6"] = 1 + values |= { + "ACM_lkaHbaCmd_Counter": frame % 15, + "ACM_lkaStrToqReq": apply_torque, + "ACM_lkaActToi": active, + + "ACM_lkaLaneRecogState": 3 if enabled else 0, + "ACM_lkaSymbolState": 3 if enabled else 0, + + # static values + "ACM_lkaElkRequest": 0, + "ACM_ldwlkaOnOffState": 2, # 2=LKAS+LDW on + "ACM_elkOnOffState": 1, # 1=LKAS on + # TODO: what are these used for? + "ACM_ldwWarnTypeState": 2, # always 2 + "ACM_ldwWarnTimingState": 1, # always 1 + #"ACM_lkaHandsoffDisplayWarning": 1, # TODO: we can send this when openpilot wants you to pay attention + } data = packer.make_can_msg("ACM_lkaHbaCmd", 0, values)[1] values["ACM_lkaHbaCmd_Checksum"] = checksum(data[1:], 0x1D, 0x63) diff --git a/opendbc_repo/opendbc/car/rivian/values.py b/opendbc_repo/opendbc/car/rivian/values.py index c0fdb8bb..a488f324 100644 --- a/opendbc_repo/opendbc/car/rivian/values.py +++ b/opendbc_repo/opendbc/car/rivian/values.py @@ -1,9 +1,9 @@ from dataclasses import dataclass, field from enum import StrEnum, IntFlag -from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, structs +from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, structs, uds from opendbc.car.docs_definitions import CarHarness, CarDocs, CarParts, Device -from opendbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from opendbc.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 from opendbc.car.vin import Vin @@ -66,6 +66,10 @@ def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str return {str(c) for c in candidates} +RIVIAN_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(0xf1a0) +RIVIAN_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + FW_QUERY_CONFIG = FwQueryConfig( requests=[ Request( @@ -73,7 +77,21 @@ FW_QUERY_CONFIG = FwQueryConfig( [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.SUPPLIER_SOFTWARE_VERSION_RESPONSE], rx_offset=0x40, bus=0, - ) + ), + Request( + [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_ECU_HARDWARE_NUMBER_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_ECU_HARDWARE_NUMBER_RESPONSE], + rx_offset=0x40, + bus=0, + logging=True, + ), + Request( + [StdQueries.TESTER_PRESENT_REQUEST, RIVIAN_VERSION_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, RIVIAN_VERSION_RESPONSE], + rx_offset=0x40, + bus=0, + logging=True, + ), ], match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, ) @@ -88,10 +106,16 @@ GEAR_MAP = { class CarControllerParams: - # The Rivian R1T we tested on achieves slightly more lateral acceleration going left vs. right - # and lateral acceleration rises as speed increases. This value is set conservatively to - # reach a maximum of 2.5-3.0 m/s^2 turning left at 80 mph, but is less at lower speeds - STEER_MAX = 250 # ~2.5 m/s^2 + # The R1T 2023 and R1S 2023 we tested on achieves slightly more lateral acceleration going left vs. right + # and lateral acceleration falls linearly as speed decreases from 38 mph to 20 mph. These values are set + # conservatively to reach a maximum of 3.0 m/s^2 turning left at 80 mph + + # These refer to turning left: + # 250 is ~2.8 m/s^2 above 17 m/s, then linearly ramps to ~1.6 m/s^2 from 17 m/s to 9 m/s + # TODO: it is theorized older models have different steering racks and achieve down to half the + # lateral acceleration referenced here at all speeds. detect this and ship a torque increase for those models + STEER_MAX = 250 # 350 is intended to maintain lateral accel, not increase it + STEER_MAX_LOOKUP = [9, 17], [350, 250] STEER_STEP = 1 STEER_DELTA_UP = 3 # torque increase per refresh STEER_DELTA_DOWN = 5 # torque decrease per refresh diff --git a/opendbc_repo/opendbc/car/subaru/fingerprints.py b/opendbc_repo/opendbc/car/subaru/fingerprints.py index 53f1e6f0..51c01e10 100644 --- a/opendbc_repo/opendbc/car/subaru/fingerprints.py +++ b/opendbc_repo/opendbc/car/subaru/fingerprints.py @@ -54,6 +54,7 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'\xa1 \x02\x01', b'\xa1 \x02\x02', + b'\xa1 \x03\x02', b'\xa1 \x03\x03', b'\xa1 \x04\x01', ], @@ -67,6 +68,7 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'\xde"a0\x07', + b'\xe2"a0\x07', b'\xde,\xa0@\x07', b'\xe2"aq\x07', b'\xe2,\xa0@\x07', @@ -463,6 +465,7 @@ FW_VERSIONS = { b'\xa1 \x06\x00', b'\xa1 \x06\x01', b'\xa1 \x06\x02', + b'\xa1 \x06\x03', b'\xa1 \x07\x00', b'\xa1 \x07\x02', b'\xa1 \x07\x03', @@ -496,6 +499,7 @@ FW_VERSIONS = { b'\xe2"`p\x07', b'\xe2"`q\x07', b'\xe3,\xa0@\x07', + b'\xe2,\xa0p\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xa5\xf6D@\x00', @@ -505,6 +509,7 @@ FW_VERSIONS = { b'\xa7\x8e\xf40\x00', b'\xa7\xf6D@\x00', b'\xa7\xfe\xf4@\x00', + b'\xa7\xfe\xf6@\x00', ], }, CAR.SUBARU_FORESTER_2022: { diff --git a/opendbc_repo/opendbc/car/tesla/carstate.py b/opendbc_repo/opendbc/car/tesla/carstate.py index a374090d..d476484a 100644 --- a/opendbc_repo/opendbc/car/tesla/carstate.py +++ b/opendbc_repo/opendbc/car/tesla/carstate.py @@ -71,8 +71,8 @@ class CarState(CarStateBase): ret.doorOpen = cp_party.vl["UI_warning"]["anyDoorOpen"] == 1 # Blinkers - ret.leftBlinker = cp_party.vl["UI_warning"]["leftBlinkerOn"] != 0 - ret.rightBlinker = cp_party.vl["UI_warning"]["rightBlinkerOn"] != 0 + ret.leftBlinker = cp_party.vl["UI_warning"]["leftBlinkerBlinking"] in (1, 2) + ret.rightBlinker = cp_party.vl["UI_warning"]["rightBlinkerBlinking"] in (1, 2) # Seatbelt ret.seatbeltUnlatched = cp_party.vl["UI_warning"]["buckleStatus"] != 1 @@ -84,6 +84,9 @@ class CarState(CarStateBase): # AEB ret.stockAeb = cp_ap_party.vl["DAS_control"]["DAS_aebEvent"] == 1 + # Stock Autosteer should be off (includes FSD) + ret.invalidLkasSetting = cp_ap_party.vl["DAS_settings"]["DAS_autosteerEnabled"] != 0 + # Buttons # ToDo: add Gap adjust button # Messages needed by carcontroller @@ -106,6 +109,7 @@ class CarState(CarStateBase): ap_party_messages = [ ("DAS_control", 25), ("DAS_status", 2), + ("DAS_settings", 2), ("SCCM_steeringAngleSensor", 100), ] diff --git a/opendbc_repo/opendbc/car/tesla/fingerprints.py b/opendbc_repo/opendbc/car/tesla/fingerprints.py index b39c3f63..ea48be52 100644 --- a/opendbc_repo/opendbc/car/tesla/fingerprints.py +++ b/opendbc_repo/opendbc/car/tesla/fingerprints.py @@ -8,6 +8,7 @@ FW_VERSIONS = { (Ecu.eps, 0x730, None): [ b'TeM3_E014p10_0.0.0 (16),E014.17.00', b'TeM3_E014p10_0.0.0 (16),EL014.17.00', + b'TeM3_ES014p11_0.0.0 (25),ES014.19.0', b'TeMYG4_DCS_Update_0.0.0 (13),E4014.28.1', b'TeMYG4_DCS_Update_0.0.0 (9),E4014.26.0', b'TeMYG4_Main_0.0.0 (59),E4H014.29.0', @@ -25,6 +26,7 @@ FW_VERSIONS = { b'TeMYG4_DCS_Update_0.0.0 (13),Y4P002.27.1', b'TeMYG4_DCS_Update_0.0.0 (9),Y4P002.25.0', b'TeMYG4_Legacy3Y_0.0.0 (2),Y4003.02.0', + b'TeMYG4_Legacy3Y_0.0.0 (5),Y4003.03.2', b'TeMYG4_Legacy3Y_0.0.0 (2),Y4P003.02.0', b'TeMYG4_SingleECU_0.0.0 (33),Y4S002.26', ], diff --git a/opendbc_repo/opendbc/car/tesla/interface.py b/opendbc_repo/opendbc/car/tesla/interface.py index 6b882806..1796bdba 100644 --- a/opendbc_repo/opendbc/car/tesla/interface.py +++ b/opendbc_repo/opendbc/car/tesla/interface.py @@ -12,12 +12,12 @@ class CarInterface(CarInterfaceBase): @staticmethod def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, experimental_long, docs) -> structs.CarParams: ret.brand = "tesla" - ret.dashcamOnly = True ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)] ret.steerLimitTimer = 1.0 ret.steerActuatorDelay = 0.1 + ret.steerAtStandstill = True ret.steerControlType = structs.CarParams.SteerControlType.angle ret.radarUnavailable = True diff --git a/opendbc_repo/opendbc/car/tests/routes.py b/opendbc_repo/opendbc/car/tests/routes.py index d582ec6e..85cea325 100644 --- a/opendbc_repo/opendbc/car/tests/routes.py +++ b/opendbc_repo/opendbc/car/tests/routes.py @@ -214,6 +214,7 @@ routes = [ CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.TOYOTA_RAV4_TSS2_2022), # hybrid CarTestRoute("20ba9ade056a8c7b|2021-02-08--21-57-35", TOYOTA.TOYOTA_RAV4_PRIME), # SecOC CarTestRoute("8bfb000e03b2a257/00000004--f9eee5f52e", TOYOTA.TOYOTA_SIENNA_4TH_GEN), # SecOC + CarTestRoute("0b54d0594d924cd9/00000057--b6206a3205", TOYOTA.TOYOTA_YARIS), # SecOC 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), @@ -251,6 +252,7 @@ routes = [ CarTestRoute("ffcd23abbbd02219|2024-02-28--14-59-38", VOLKSWAGEN.VOLKSWAGEN_CADDY_MK3), CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.VOLKSWAGEN_GOLF_MK7), # Stock ACC CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.VOLKSWAGEN_GOLF_MK7), # openpilot longitudinal + CarTestRoute("17fbdc1cd49dc9af/00000027--ee57555e5b", VOLKSWAGEN.VOLKSWAGEN_ID4_MK1), # FIXME: temporary, replace later CarTestRoute("578742b26807f756|00000010--41ee3e5bec", VOLKSWAGEN.VOLKSWAGEN_JETTA_MK6), CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.VOLKSWAGEN_JETTA_MK7), CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.VOLKSWAGEN_PASSAT_MK8), diff --git a/opendbc_repo/opendbc/car/tests/test_fw_fingerprint.py b/opendbc_repo/opendbc/car/tests/test_fw_fingerprint.py index 417ce3d2..58d2ba65 100644 --- a/opendbc_repo/opendbc/car/tests/test_fw_fingerprint.py +++ b/opendbc_repo/opendbc/car/tests/test_fw_fingerprint.py @@ -260,7 +260,7 @@ class TestFwFingerprintTiming: print(f'get_vin {name} case, query time={self.total_time / self.N} seconds') def test_fw_query_timing(self, subtests, mocker): - total_ref_time = {1: 7.1, 2: 7.7} + total_ref_time = {1: 7.3, 2: 7.9} brand_ref_times = { 1: { 'gm': 1.0, @@ -275,7 +275,7 @@ class TestFwFingerprintTiming: 'tesla': 0.1, 'toyota': 0.7, 'volkswagen': 0.65, - 'rivian': 0.1, + 'rivian': 0.3, }, 2: { 'ford': 1.6, diff --git a/opendbc_repo/opendbc/car/torque_data/override.toml b/opendbc_repo/opendbc/car/torque_data/override.toml index 16e07c2a..c04e6c0a 100644 --- a/opendbc_repo/opendbc/car/torque_data/override.toml +++ b/opendbc_repo/opendbc/car/torque_data/override.toml @@ -48,6 +48,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "CHEVROLET_EQUINOX" = [2.5, 2.5, 0.05] "CHEVROLET_VOLT_2019" = [1.4, 1.4, 0.16] "VOLKSWAGEN_CADDY_MK3" = [1.2, 1.2, 0.1] +"VOLKSWAGEN_ID4_MK1" = [nan, 2.5, nan] "VOLKSWAGEN_PASSAT_NMS" = [2.5, 2.5, 0.1] "VOLKSWAGEN_SHARAN_MK2" = [2.5, 2.5, 0.1] "HYUNDAI_SANTA_CRUZ_1ST_GEN" = [2.7, 2.7, 0.1] @@ -73,6 +74,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "HYUNDAI_STARIA_4TH_GEN" = [1.8, 2.0, 0.15] "GENESIS_GV70_ELECTRIFIED_1ST_GEN" = [1.9, 1.9, 0.09] "GENESIS_G80_2ND_GEN_FL" = [2.5819356441497803, 2.5, 0.11244568973779678] +# Note that some Rivians achieve significantly less lateral acceleration than this "RIVIAN_R1_GEN1" = [2.8, 2.5, 0.07] "HYUNDAI_NEXO_1ST_GEN" = [2.5, 2.5, 0.1] diff --git a/opendbc_repo/opendbc/car/torque_data/params.toml b/opendbc_repo/opendbc/car/torque_data/params.toml index 4bb8d45c..fde2ca22 100644 --- a/opendbc_repo/opendbc/car/torque_data/params.toml +++ b/opendbc_repo/opendbc/car/torque_data/params.toml @@ -73,6 +73,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "TOYOTA_RAV4H" = [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] "TOYOTA_RAV4_TSS2_2022" = [2.241883248393209, 1.9304407208090029, 0.112174] "TOYOTA_SIENNA" = [1.689726, 1.3208264576110418, 0.140456] +"TOYOTA_YARIS" = [2.22984, 1.86145, 0.168189] "VOLKSWAGEN_ARTEON_MK1" = [1.45136518053819, 1.3639364049316804, 0.23806361745695032] "VOLKSWAGEN_ATLAS_MK1" = [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] "VOLKSWAGEN_GOLF_MK7" = [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] diff --git a/opendbc_repo/opendbc/car/torque_data/substitute.toml b/opendbc_repo/opendbc/car/torque_data/substitute.toml index a91ac358..911dd4bb 100644 --- a/opendbc_repo/opendbc/car/torque_data/substitute.toml +++ b/opendbc_repo/opendbc/car/torque_data/substitute.toml @@ -59,6 +59,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "CHEVROLET_MALIBU" = "CHEVROLET_VOLT" "HOLDEN_ASTRA" = "CHEVROLET_VOLT" +"SKODA_ENYAQ_MK1" = "VOLKSWAGEN_ID4_MK1" "SKODA_FABIA_MK4" = "VOLKSWAGEN_GOLF_MK7" "SKODA_OCTAVIA_MK3" = "SKODA_SUPERB_MK3" "SKODA_KODIAQ_MK1" = "SKODA_SUPERB_MK3" diff --git a/opendbc_repo/opendbc/car/toyota/fingerprints.py b/opendbc_repo/opendbc/car/toyota/fingerprints.py index 3452c4cb..2b6e1b47 100644 --- a/opendbc_repo/opendbc/car/toyota/fingerprints.py +++ b/opendbc_repo/opendbc/car/toyota/fingerprints.py @@ -1783,4 +1783,21 @@ FW_VERSIONS = { b'\x028646FV201000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, + CAR.TOYOTA_YARIS: { + (Ecu.engine, 0x700, None): [ + b'\x0189663K015300\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'\x018965BK003200\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F1526K007500\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F0D05300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F5205200\x00\x00\x00\x008646G5202200\x00\x00\x00\x00', + ], + }, } diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py index 4fa1eeea..1515ebd9 100644 --- a/opendbc_repo/opendbc/car/toyota/interface.py +++ b/opendbc_repo/opendbc/car/toyota/interface.py @@ -31,7 +31,6 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.ALT_BRAKE.value if ret.flags & ToyotaFlags.SECOC.value: - ret.dashcamOnly = True ret.secOcRequired = True ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.SECOC.value diff --git a/opendbc_repo/opendbc/car/toyota/values.py b/opendbc_repo/opendbc/car/toyota/values.py index 889b9d0c..5d43cf4e 100644 --- a/opendbc_repo/opendbc/car/toyota/values.py +++ b/opendbc_repo/opendbc/car/toyota/values.py @@ -271,7 +271,7 @@ class CAR(Platforms): ) TOYOTA_RAV4_TSS2_2023 = ToyotaTSS2PlatformConfig( [ - ToyotaCarDocs("Toyota RAV4 2023-24"), + ToyotaCarDocs("Toyota RAV4 2023-25"), ToyotaCarDocs("Toyota RAV4 Hybrid 2023-25", video_link="https://youtu.be/4eIsEq4L4Ng"), ], TOYOTA_RAV4_TSS2.specs, @@ -281,6 +281,11 @@ class CAR(Platforms): [ToyotaCarDocs("Toyota RAV4 Prime 2021-23", min_enable_speed=MIN_ACC_SPEED)], CarSpecs(mass=4372. * CV.LB_TO_KG, wheelbase=2.68, steerRatio=16.88, tireStiffnessFactor=0.5533), ) + TOYOTA_YARIS = ToyotaSecOCPlatformConfig( + [ToyotaCarDocs("Toyota Yaris 2023 (Non-US only)", min_enable_speed=MIN_ACC_SPEED)], + CarSpecs(mass=1170, wheelbase=2.55, steerRatio=14.80, tireStiffnessFactor=0.5533), + flags=ToyotaFlags.RADAR_ACC, + ) TOYOTA_MIRAI = ToyotaTSS2PlatformConfig( # TSS 2.5 [ToyotaCarDocs("Toyota Mirai 2021")], CarSpecs(mass=4300. * CV.LB_TO_KG, wheelbase=2.91, steerRatio=14.8, tireStiffnessFactor=0.8), diff --git a/opendbc_repo/opendbc/car/volkswagen/carcontroller.py b/opendbc_repo/opendbc/car/volkswagen/carcontroller.py index e2a4fe86..048b283c 100644 --- a/opendbc_repo/opendbc/car/volkswagen/carcontroller.py +++ b/opendbc_repo/opendbc/car/volkswagen/carcontroller.py @@ -3,7 +3,7 @@ from opendbc.can.packer import CANPacker from opendbc.car import Bus, DT_CTRL, apply_driver_steer_torque_limits, structs from opendbc.car.common.conversions import Conversions as CV from opendbc.car.interfaces import CarControllerBase -from opendbc.car.volkswagen import mqbcan, pqcan +from opendbc.car.volkswagen import mqbcan, pqcan, mebcan from opendbc.car.volkswagen.values import CANBUS, CarControllerParams, VolkswagenFlags VisualAlert = structs.CarControl.HUDControl.VisualAlert @@ -14,12 +14,20 @@ class CarController(CarControllerBase): def __init__(self, dbc_names, CP): super().__init__(dbc_names, CP) self.CCP = CarControllerParams(CP) - self.CCS = pqcan if CP.flags & VolkswagenFlags.PQ else mqbcan + if CP.flags & VolkswagenFlags.PQ: + self.CCS = pqcan + elif CP.flags & VolkswagenFlags.MEB: + self.CCS = mebcan + else: + self.CCS = mqbcan self.packer_pt = CANPacker(dbc_names[Bus.pt]) self.ext_bus = CANBUS.pt if CP.networkLocation == structs.CarParams.NetworkLocation.fwdCamera else CANBUS.cam self.aeb_available = not CP.flags & VolkswagenFlags.PQ + self.openpilot_longitudinal = self.CP.openpilotLongitudinalControl and not self.CP.flags & VolkswagenFlags.MEB self.apply_torque_last = 0 + self.apply_curvature_last = 0 + self.apply_steer_power_last = 0 self.gra_acc_counter_last = None self.eps_timer_soft_disable_alert = False self.hca_frame_timer_running = 0 @@ -33,50 +41,83 @@ class CarController(CarControllerBase): # **** Steering Controls ************************************************ # if self.frame % self.CCP.STEER_STEP == 0: - # Logic to avoid HCA state 4 "refused": - # * Don't steer unless HCA is in state 3 "ready" or 5 "active" - # * Don't steer at standstill - # * Don't send > 3.00 Newton-meters torque - # * Don't send the same torque for > 6 seconds - # * Don't send uninterrupted steering for > 360 seconds - # MQB racks reset the uninterrupted steering timer after a single frame - # of HCA disabled; this is done whenever output happens to be zero. + if self.CP.flags & VolkswagenFlags.MEB: + # The QFK (lateral control coordinator) control loop compares actuation curvature to its own current curvature + # Calibrate our actuator command by the offset between openpilot's vehicle model and QFK perceived curvatures + apply_curvature = actuators.curvature + (CS.qfk_curvature - CC.currentCurvature) - if CC.latActive: - new_torque = int(round(actuators.torque * self.CCP.STEER_MAX)) - apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.CCP) - self.hca_frame_timer_running += self.CCP.STEER_STEP - if self.apply_torque_last == apply_torque: - self.hca_frame_same_torque += self.CCP.STEER_STEP - if self.hca_frame_same_torque > self.CCP.STEER_TIME_STUCK_TORQUE / DT_CTRL: - apply_torque -= (1, -1)[apply_torque < 0] - self.hca_frame_same_torque = 0 + # Progressive QFK power control: smooth engage and disengage, reduce power when the driver is overriding + qfk_enable = True + if CC.latActive: + min_power = max(self.apply_steer_power_last - self.CCP.STEERING_POWER_STEP, self.CCP.STEERING_POWER_MIN) + max_power = max(self.apply_steer_power_last + self.CCP.STEERING_POWER_STEP, self.CCP.STEERING_POWER_MAX) + target_power = int(np.interp(CS.out.steeringTorque, [self.CCP.STEER_DRIVER_ALLOWANCE, self.CCP.STEER_DRIVER_MAX], + [self.CCP.STEERING_POWER_MAX, self.CCP.STEERING_POWER_MIN])) + apply_steer_power = min(max(target_power, min_power), max_power) + elif self.apply_steer_power_last > 0: + apply_steer_power = max(self.apply_steer_power_last - self.CCP.STEERING_POWER_STEP, 0) else: - self.hca_frame_same_torque = 0 - hca_enabled = abs(apply_torque) > 0 + qfk_enable = False + apply_curvature = 0 + apply_steer_power = 0 + + can_sends.append(mebcan.create_steering_control(self.packer_pt, CANBUS.pt, apply_curvature, qfk_enable, apply_steer_power)) + + self.apply_curvature_last = apply_curvature + self.apply_steer_power_last = apply_steer_power + else: - hca_enabled = False - apply_torque = 0 + # Logic to avoid HCA state 4 "refused": + # * Don't steer unless HCA is in state 3 "ready" or 5 "active" + # * Don't steer at standstill + # * Don't send > 3.00 Newton-meters torque + # * Don't send the same torque for > 6 seconds + # * Don't send uninterrupted steering for > 360 seconds + # MQB racks reset the uninterrupted steering timer after a single frame + # of HCA disabled; this is done whenever output happens to be zero. - if not hca_enabled: - self.hca_frame_timer_running = 0 + if CC.latActive: + new_torque = int(round(actuators.torque * self.CCP.STEER_MAX)) + apply_torque = apply_driver_steer_torque_limits(new_torque, self.apply_torque_last, CS.out.steeringTorque, self.CCP) + self.hca_frame_timer_running += self.CCP.STEER_STEP + if self.apply_torque_last == apply_torque: + self.hca_frame_same_torque += self.CCP.STEER_STEP + if self.hca_frame_same_torque > self.CCP.STEER_TIME_STUCK_TORQUE / DT_CTRL: + apply_torque -= (1, -1)[apply_torque < 0] + self.hca_frame_same_torque = 0 + else: + self.hca_frame_same_torque = 0 + hca_enabled = abs(apply_torque) > 0 + else: + hca_enabled = False + apply_torque = 0 - self.eps_timer_soft_disable_alert = self.hca_frame_timer_running > self.CCP.STEER_TIME_ALERT / DT_CTRL - self.apply_torque_last = apply_torque - can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_torque, hca_enabled)) + if not hca_enabled: + self.hca_frame_timer_running = 0 - if self.CP.flags & VolkswagenFlags.STOCK_HCA_PRESENT: - # Pacify VW Emergency Assist driver inactivity detection by changing its view of driver steering input torque - # to the greatest of actual driver input or 2x openpilot's output (1x openpilot output is not enough to - # consistently reset inactivity detection on straight level roads). See commaai/openpilot#23274 for background. - ea_simulated_torque = float(np.clip(apply_torque * 2, -self.CCP.STEER_MAX, self.CCP.STEER_MAX)) - if abs(CS.out.steeringTorque) > abs(ea_simulated_torque): - ea_simulated_torque = CS.out.steeringTorque - can_sends.append(self.CCS.create_eps_update(self.packer_pt, CANBUS.cam, CS.eps_stock_values, ea_simulated_torque)) + self.eps_timer_soft_disable_alert = self.hca_frame_timer_running > self.CCP.STEER_TIME_ALERT / DT_CTRL + self.apply_torque_last = apply_torque + can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_torque, hca_enabled)) + + if self.CP.flags & VolkswagenFlags.STOCK_HCA_PRESENT: + # Pacify VW Emergency Assist driver inactivity detection by changing its view of driver steering input torque + # to the greatest of actual driver input or 2x openpilot's output (1x openpilot output is not enough to + # consistently reset inactivity detection on straight level roads). See commaai/openpilot#23274 for background. + ea_simulated_torque = float(np.clip(apply_torque * 2, -self.CCP.STEER_MAX, self.CCP.STEER_MAX)) + if abs(CS.out.steeringTorque) > abs(ea_simulated_torque): + ea_simulated_torque = CS.out.steeringTorque + can_sends.append(self.CCS.create_eps_update(self.packer_pt, CANBUS.cam, CS.eps_stock_values, ea_simulated_torque)) + + # TODO: refactor a bit + if self.CP.flags & VolkswagenFlags.MEB: + if self.frame % 2 == 0: + can_sends.append(mebcan.create_ea_control(self.packer_pt, CANBUS.pt)) + if self.frame % 50 == 0: + can_sends.append(mebcan.create_ea_hud(self.packer_pt, CANBUS.pt)) # **** Acceleration Controls ******************************************** # - if self.CP.openpilotLongitudinalControl: + if self.openpilot_longitudinal: if self.frame % self.CCP.ACC_CONTROL_STEP == 0: acc_control = self.CCS.acc_control_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive) accel = float(np.clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0) @@ -100,7 +141,7 @@ class CarController(CarControllerBase): can_sends.append(self.CCS.create_lka_hud_control(self.packer_pt, CANBUS.pt, CS.ldw_stock_values, CC.latActive, CS.out.steeringPressed, hud_alert, hud_control)) - if self.frame % self.CCP.ACC_HUD_STEP == 0 and self.CP.openpilotLongitudinalControl: + if self.frame % self.CCP.ACC_HUD_STEP == 0 and self.openpilot_longitudinal: lead_distance = 0 if hud_control.leadVisible and self.frame * DT_CTRL > 1.0: # Don't display lead until we know the scaling factor lead_distance = 512 if CS.upscale_lead_car_signal else 8 @@ -120,6 +161,7 @@ class CarController(CarControllerBase): new_actuators = actuators.as_builder() new_actuators.torque = self.apply_torque_last / self.CCP.STEER_MAX new_actuators.torqueOutputCan = self.apply_torque_last + new_actuators.curvature = float(self.apply_curvature_last) self.gra_acc_counter_last = CS.gra_stock_values["COUNTER"] self.frame += 1 diff --git a/opendbc_repo/opendbc/car/volkswagen/carstate.py b/opendbc_repo/opendbc/car/volkswagen/carstate.py index 2e346222..b51965b0 100644 --- a/opendbc_repo/opendbc/car/volkswagen/carstate.py +++ b/opendbc_repo/opendbc/car/volkswagen/carstate.py @@ -19,6 +19,7 @@ class CarState(CarStateBase): self.esp_hold_confirmation = False self.upscale_lead_car_signal = False self.eps_stock_values = False + self.qfk_curvature = 0. def update_button_enable(self, buttonEvents: list[structs.CarState.ButtonEvent]): if not self.CP.pcmCruise: @@ -63,7 +64,55 @@ class CarState(CarStateBase): else: ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["Gateway_73"]["GE_Fahrstufe"], None)) - if True: + if self.CP.flags & VolkswagenFlags.MEB: + # MEB-specific + self.qfk_curvature = -pt_cp.vl["QFK_01"]["Curvature"] * (1, -1)[int(pt_cp.vl["QFK_01"]["Curvature_VZ"])] + ret.fuelGauge = pt_cp.vl["Motor_16"]["MO_Energieinhalt_BMS"] # TODO: is this available on MQB as well? + + ret.wheelSpeeds = self.get_wheel_speeds( + pt_cp.vl["ESC_51"]["VL_Radgeschw"], + pt_cp.vl["ESC_51"]["VR_Radgeschw"], + pt_cp.vl["ESC_51"]["HL_Radgeschw"], + pt_cp.vl["ESC_51"]["HR_Radgeschw"], + ) + + ret.yawRate = pt_cp.vl["ESC_50"]["Yaw_Rate"] * (1, -1)[int(pt_cp.vl["ESC_50"]["Yaw_Rate_Sign"])] * CV.DEG_TO_RAD + hca_status = self.CCP.hca_status_values.get(pt_cp.vl["QFK_01"]["LatCon_HCA_Status"]) + + drive_mode = ret.gearShifter == GearShifter.drive + ret.gas = pt_cp.vl["Motor_54"]["Accelerator_Pressure"] + ret.brake = pt_cp.vl["ESC_51"]["Brake_Pressure"] + ret.brakePressed = bool(pt_cp.vl["Motor_14"]["MO_Fahrer_bremst"]) # includes regen braking by user + ret.parkingBrake = pt_cp.vl["Gateway_73"]["EPB_Status"] in (1, 4) # EPB closing or closed + + ret.doorOpen = any([pt_cp.vl["ZV_02"]["ZV_FT_offen"], + pt_cp.vl["ZV_02"]["ZV_BT_offen"], + pt_cp.vl["ZV_02"]["ZV_HFS_offen"], + pt_cp.vl["ZV_02"]["ZV_HBFS_offen"], + pt_cp.vl["ZV_02"]["ZV_HD_offen"]]) + + if self.CP.enableBsm: + ret.leftBlindspot = bool(ext_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Info_Left"]) or bool(ext_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Warn_Left"]) + ret.rightBlindspot = bool(ext_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Info_Right"]) or bool(ext_cp.vl["MEB_Side_Assist_01"]["Blind_Spot_Warn_Right"]) + + ret.stockFcw = bool(pt_cp.vl["VMM_02"]["FCW_Active"]) or bool(ext_cp.vl["AWV_03"]["FCW_Active"]) + ret.stockAeb = bool(pt_cp.vl["VMM_02"]["AEB_Active"]) + + self.travel_assist_available = bool(cam_cp.vl["TA_01"]["Travel_Assist_Available"]) + self.acc_type = ext_cp.vl["ACC_18"]["ACC_Typ"] + self.esp_hold_confirmation = bool(pt_cp.vl["VMM_02"]["ESP_Hold"]) + acc_limiter_mode = bool(ext_cp.vl["MEB_ACC_01"]["ACC_Limiter_Mode"]) + speed_limiter_mode = bool(pt_cp.vl["Motor_51"]["TSK_Limiter_ausgewaehlt"]) + + ret.cruiseState.available = pt_cp.vl["Motor_51"]["TSK_Status"] in (2, 3, 4, 5) + ret.cruiseState.enabled = pt_cp.vl["Motor_51"]["TSK_Status"] in (3, 4, 5) + ret.cruiseState.speed = int(round(ext_cp.vl["MEB_ACC_01"]["ACC_Wunschgeschw_02"])) * CV.KPH_TO_MS if self.CP.pcmCruise else 0 + ret.accFaulted = drive_mode and pt_cp.vl["Motor_51"]["TSK_Status"] in (6, 7) + + ret.leftBlinker = bool(pt_cp.vl["Blinkmodi_02"]["BM_links"]) + ret.rightBlinker = bool(pt_cp.vl["Blinkmodi_02"]["BM_rechts"]) + + else: # MQB-specific self.upscale_lead_car_signal = bool(pt_cp.vl["Kombi_03"]["KBI_Variante"]) # Analog vs digital instrument cluster @@ -256,6 +305,8 @@ class CarState(CarStateBase): def get_can_parsers(CP): if CP.flags & VolkswagenFlags.PQ: return CarState.get_can_parsers_pq(CP) + elif CP.flags & VolkswagenFlags.MEB: + return CarState.get_can_parsers_meb(CP) pt_messages = [ # sig_address, frequency @@ -356,6 +407,51 @@ class CarState(CarStateBase): Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CANBUS.cam), } + @staticmethod + def get_can_parsers_meb(CP): + pt_messages = [ + # sig_address, frequency + ("LWI_01", 100), # From J500 Steering Assist with integrated sensors + ("GRA_ACC_01", 33), # From J533 CAN gateway (via LIN from steering wheel controls) + ("Airbag_02", 5), # From J234 Airbag control module + ("Motor_14", 10), # From J623 Engine control module + ("Motor_16", 2), # From J623 Engine control module + ("Blinkmodi_02", 1), # From J519 BCM (sent at 1Hz when no lights active, 50Hz when active) + ("LH_EPS_03", 100), # From J500 Steering Assist with integrated sensors + ("ZV_02", 5), # From ZV + ("QFK_01", 100), # From Steering + ("ESP_21", 50), # + ("ESC_51", 100), # + ("Motor_54", 10), # + ("ESC_50", 50), # + ("VMM_02", 50), # + ("Gateway_73", 20), # + ("Motor_51", 50), # + ] + + if CP.networkLocation == NetworkLocation.fwdCamera: + # Radars are here on CANBUS.pt + pt_messages += MebExtraSignals.fwd_radar_messages + if CP.enableBsm: + pt_messages += MebExtraSignals.bsm_radar_messages + + cam_messages = [ + # sig_address, frequency + ("LDW_02", 10), # From R242 Driver assistance camera + ("TA_01", 10), # From R242 Driver assistance camera (Travel Assist) + ] + + if CP.networkLocation == NetworkLocation.gateway: + # Radars are here on CANBUS.cam + cam_messages += MebExtraSignals.fwd_radar_messages + if CP.enableBsm: + cam_messages += MebExtraSignals.bsm_radar_messages + + return { + Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CANBUS.pt), + Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CANBUS.cam), + } + class MqbExtraSignals: # Additional signal and message lists for optional or bus-portable controllers @@ -378,3 +474,15 @@ class PqExtraSignals: bsm_radar_messages = [ ("SWA_1", 20), # From J1086 Lane Change Assist ] + + +class MebExtraSignals: + # Additional signal and message lists for optional or bus-portable controllers + fwd_radar_messages = [ + ("MEB_ACC_01", 17), # + ("ACC_18", 50), # + ("AWV_03", 1), # Front Collision Detection (1 Hz when inactive, 50 Hz when active) + ] + bsm_radar_messages = [ + ("MEB_Side_Assist_01", 20), + ] diff --git a/opendbc_repo/opendbc/car/volkswagen/fingerprints.py b/opendbc_repo/opendbc/car/volkswagen/fingerprints.py index 1162e3f9..f9ae9bba 100644 --- a/opendbc_repo/opendbc/car/volkswagen/fingerprints.py +++ b/opendbc_repo/opendbc/car/volkswagen/fingerprints.py @@ -977,6 +977,19 @@ FW_VERSIONS = { b'\xf1\x875Q0907572R \xf1\x890771', ], }, + CAR.VOLKSWAGEN_ID4_MK1: { + (Ecu.srs, 0x715, None): [ + b'\xf1\x875WA959655R \xf1\x890717', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x871EA907572H \xf1\x890234', + ], + }, + CAR.SKODA_ENYAQ_MK1: { + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x871EA907567B \xf1\x890232', + ], + }, CAR.SKODA_FABIA_MK4: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705E906018CF\xf1\x891905', diff --git a/opendbc_repo/opendbc/car/volkswagen/interface.py b/opendbc_repo/opendbc/car/volkswagen/interface.py index 70a67857..780fa61f 100644 --- a/opendbc_repo/opendbc/car/volkswagen/interface.py +++ b/opendbc_repo/opendbc/car/volkswagen/interface.py @@ -37,6 +37,18 @@ class CarInterface(CarInterfaceBase): # Panda ALLOW_DEBUG firmware required. ret.dashcamOnly = True + elif ret.flags & VolkswagenFlags.MEB: + # Set global MEB parameters + ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.noOutput),get_safety_config(structs.CarParams.SafetyModel.volkswagenMeb)] + ret.enableBsm = 0x24C in fingerprint[0] # MEB_Side_Assist_01 + ret.steerControlType = structs.CarParams.SteerControlType.angle + ret.transmissionType = TransmissionType.automatic + + if any(msg in fingerprint[1] for msg in (0x520, 0x86, 0xFD, 0x13D)): # Airbag_02, LWI_01, ESP_21, QFK_01 + ret.networkLocation = NetworkLocation.gateway + else: + ret.networkLocation = NetworkLocation.fwdCamera + else: # Set global MQB parameters ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.volkswagen)] @@ -63,6 +75,8 @@ class CarInterface(CarInterfaceBase): if ret.flags & VolkswagenFlags.PQ: ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif ret.flags & VolkswagenFlags.MEB: + ret.steerActuatorDelay = 0.2 else: ret.steerActuatorDelay = 0.1 ret.lateralTuning.pid.kpBP = [0.] @@ -73,7 +87,7 @@ class CarInterface(CarInterfaceBase): # Global longitudinal tuning defaults, can be overridden per-vehicle - ret.experimentalLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway or docs + ret.experimentalLongitudinalAvailable = not ret.flags & VolkswagenFlags.MEB and (ret.networkLocation == NetworkLocation.gateway or docs) if experimental_long: # Proof-of-concept, prep for E2E only. No radar points available. Panda ALLOW_DEBUG firmware required. ret.openpilotLongitudinalControl = True diff --git a/opendbc_repo/opendbc/car/volkswagen/values.py b/opendbc_repo/opendbc/car/volkswagen/values.py index 750a99e1..f6e6b4f1 100644 --- a/opendbc_repo/opendbc/car/volkswagen/values.py +++ b/opendbc_repo/opendbc/car/volkswagen/values.py @@ -72,6 +72,38 @@ class CarControllerParams: "laneAssistDeactivTrailer": 5, # "Lane Assist: no function with trailer" } + elif CP.flags & VolkswagenFlags.MEB: + self.LDW_STEP = 10 # LDW_02 message frequency 10Hz + self.ACC_HUD_STEP = 6 # MEB_ACC_01 message frequency 16Hz + self.STEER_DRIVER_ALLOWANCE = 60 # Driver torque 0.6 Nm, begin steering reduction from MAX + self.STEER_DRIVER_MAX = 300 # Driver torque 3.0 Nm, stop steering reduction at MIN + self.STEERING_POWER_MAX = 50 # HCA_03 maximum steering power, percentage + self.STEERING_POWER_MIN = 20 # HCA_03 minimum steering power, percentage + self.STEERING_POWER_STEP = 2 # HCA_03 steering power step increments + + self.shifter_values = can_define.dv["Gateway_73"]["GE_Fahrstufe"] + self.hca_status_values = can_define.dv["QFK_01"]["LatCon_HCA_Status"] + + self.BUTTONS = [ + Button(structs.CarState.ButtonEvent.Type.setCruise, "GRA_ACC_01", "GRA_Tip_Setzen", [1]), + Button(structs.CarState.ButtonEvent.Type.resumeCruise, "GRA_ACC_01", "GRA_Tip_Wiederaufnahme", [1]), + Button(structs.CarState.ButtonEvent.Type.accelCruise, "GRA_ACC_01", "GRA_Tip_Hoch", [1]), + Button(structs.CarState.ButtonEvent.Type.decelCruise, "GRA_ACC_01", "GRA_Tip_Runter", [1]), + Button(structs.CarState.ButtonEvent.Type.cancel, "GRA_ACC_01", "GRA_Hauptschalter", [1]), + Button(structs.CarState.ButtonEvent.Type.gapAdjustCruise, "GRA_ACC_01", "GRA_Verstellung_Zeitluecke", [3]), + ] + + self.LDW_MESSAGES = { + "none": 0, # Nothing to display + "laneAssistTakeOverUrgent": 4, # "Lane Assist: Please Take Over Steering" (red) + "laneAssistTakeOver": 8, # "Lane Assist: Please Take Over Steering" (white) + } + self.LDW_SOUNDS = { + "None": 0, # No sound + "Chime": 1, # Play a chime + "Beep": 2, # Play a loud beep + } + else: self.LDW_STEP = 10 # LDW_02 message frequency 10Hz self.ACC_HUD_STEP = 6 # ACC_02 message frequency 16Hz @@ -108,8 +140,8 @@ class CarControllerParams: class CANBUS: - pt = 0 - cam = 2 + pt = 4 + cam = 6 class WMI(StrEnum): @@ -143,20 +175,33 @@ class VolkswagenFlags(IntFlag): # Static flags PQ = 2 + MEB = 4 @dataclass class VolkswagenMQBPlatformConfig(PlatformConfig): - dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_mqb_2010'}) + dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_mqb'}) # Volkswagen uses the VIN WMI and chassis code to match in the absence of the comma power # on camera-integrated cars, as we lose too many ECUs to reliably identify the vehicle chassis_codes: set[str] = field(default_factory=set) wmis: set[WMI] = field(default_factory=set) +@dataclass +class VolkswagenMEBPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_meb', Bus.radar: 'vw_meb'}) + # Volkswagen uses the VIN WMI and chassis code to match in the absence of the comma power + # on camera-integrated cars, as we lose too many ECUs to reliably identify the vehicle + chassis_codes: set[str] = field(default_factory=set) + wmis: set[WMI] = field(default_factory=set) + + def init(self): + self.flags |= VolkswagenFlags.MEB + + @dataclass class VolkswagenPQPlatformConfig(VolkswagenMQBPlatformConfig): - dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_golf_mk4'}) + dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_pq'}) def init(self): self.flags |= VolkswagenFlags.PQ @@ -188,6 +233,9 @@ class Footnote(Enum): "Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot " + "in software, but doesn't yet have a harness available from the comma store.", Column.HARDWARE) + VW_MEB = CarFootnote( + "Volkswagen MEB plattform is using CAN-FD, which is supported by comma 3x or red panda.", + Column.HARDWARE) @dataclass @@ -200,7 +248,9 @@ class VWCarDocs(CarDocs): if "SKODA" in CP.carFingerprint: self.footnotes.append(Footnote.SKODA_HEATED_WINDSHIELD) - if CP.carFingerprint in (CAR.VOLKSWAGEN_CRAFTER_MK2, CAR.VOLKSWAGEN_TRANSPORTER_T61): + if CP.flags & VolkswagenFlags.MEB: + self.car_parts = CarParts.common([CarHarness.vw_c]) + elif CP.carFingerprint in (CAR.VOLKSWAGEN_CRAFTER_MK2, CAR.VOLKSWAGEN_TRANSPORTER_T61): self.car_parts = CarParts([Device.threex_angled_mount, CarHarness.vw_j533]) if abs(CP.minSteerSpeed - CarControllerParams.DEFAULT_MIN_STEER_SPEED) < 1e-3: @@ -212,7 +262,7 @@ class VWCarDocs(CarDocs): # FW_VERSIONS for that existing CAR. class CAR(Platforms): - config: VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig + config: VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig | VolkswagenMEBPlatformConfig VOLKSWAGEN_ARTEON_MK1 = VolkswagenMQBPlatformConfig( [ @@ -274,6 +324,12 @@ class CAR(Platforms): chassis_codes={"5G", "AU", "BA", "BE"}, wmis={WMI.VOLKSWAGEN_MEXICO_CAR, WMI.VOLKSWAGEN_EUROPE_CAR}, ) + VOLKSWAGEN_ID4_MK1 = VolkswagenMEBPlatformConfig( + [VWCarDocs("Volkswagen ID.4 2021-25", footnotes=[Footnote.VW_MEB])], + VolkswagenCarSpecs(mass=2099, wheelbase=2.77), + chassis_codes={"E2", "E8"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV, WMI.VOLKSWAGEN_USA_SUV}, + ) VOLKSWAGEN_JETTA_MK6 = VolkswagenPQPlatformConfig( [VWCarDocs("Volkswagen Jetta 2015-18")], VolkswagenCarSpecs(mass=1518, wheelbase=2.65, minSteerSpeed=50 * CV.KPH_TO_MS, minEnableSpeed=20 * CV.KPH_TO_MS), @@ -398,6 +454,15 @@ class CAR(Platforms): chassis_codes={"5F"}, wmis={WMI.SEAT}, ) + SKODA_ENYAQ_MK1 = VolkswagenMEBPlatformConfig( + [ + VWCarDocs("Å koda Enyaq 2021-25"), + VWCarDocs("Å koda Enyaq Coupe 2023-25"), + ], + VolkswagenCarSpecs(mass=2137, wheelbase=2.77), + chassis_codes={"NY"}, + wmis={WMI.SKODA}, + ) SKODA_FABIA_MK4 = VolkswagenMQBPlatformConfig( [VWCarDocs("Å koda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0])], VolkswagenCarSpecs(mass=1266, wheelbase=2.56), diff --git a/opendbc_repo/opendbc/dbc/generator/gm/gm_global_a_powertrain.dbc b/opendbc_repo/opendbc/dbc/generator/gm/gm_global_a_powertrain.dbc index 2dd72d36..adfb96b7 100644 --- a/opendbc_repo/opendbc/dbc/generator/gm/gm_global_a_powertrain.dbc +++ b/opendbc_repo/opendbc/dbc/generator/gm/gm_global_a_powertrain.dbc @@ -194,15 +194,12 @@ BO_ 711 BECMBatteryVoltageCurrent: 6 K17_EBCM SG_ HVBatteryCurrent : 12|13@0- (0.15,0) [-614.4|614.25] "A" NEO BO_ 715 ASCMGasRegenCmd: 8 K124_ASCM - SG_ GasRegenAlwaysOne2 : 9|1@0+ (1,0) [0|1] "" NEO - SG_ GasRegenAlwaysOne : 14|1@0+ (1,0) [0|1] "" NEO - SG_ GasRegenChecksum : 47|24@0+ (1,0) [0|0] "" NEO - SG_ GasRegenCmdActiveInv : 32|1@0+ (1,0) [0|0] "" NEO + SG_ GasRegenAccType : 15|2@0+ (1,0) [0|3] "" NEO + SG_ GasRegenChecksum : 32|25@0+ (1,0) [0|0] "" NEO SG_ GasRegenFullStopActive : 13|1@0+ (1,0) [0|0] "" NEO SG_ GasRegenCmdActive : 0|1@0+ (1,0) [0|0] "" NEO SG_ RollingCounter : 7|2@0+ (1,0) [0|0] "" NEO - SG_ GasRegenAlwaysOne3 : 23|1@0+ (1,0) [0|1] "" NEO - SG_ GasRegenCmd : 22|12@0+ (1,0) [0|0] "" NEO + SG_ GasRegenCmd : 10|19@0+ (0.125,-22534) [-22534|43001.875] "Nm" NEO BO_ 717 ASCM_2CD: 5 K124_ASCM diff --git a/opendbc_repo/opendbc/dbc/generator/nissan/_nissan_common.dbc b/opendbc_repo/opendbc/dbc/generator/nissan/_nissan_common.dbc index 6300ea94..13da6361 100644 --- a/opendbc_repo/opendbc/dbc/generator/nissan/_nissan_common.dbc +++ b/opendbc_repo/opendbc/dbc/generator/nissan/_nissan_common.dbc @@ -107,7 +107,7 @@ BO_ 1227 LKAS_SETTINGS: 8 XXX VAL_ 1228 PROPILOT_NA_MSGS 0 "NO_MSG" 1 "NA_FRONT_CAMERA_IMPARED" 2 "STEERING_ASSIST_ON_STANDBY" 3 "NA_PARKING_ASSIST_ENABLED" 4 "STEER_ASSIST_CURRENTLY_NA" 5 "NA_BAD_WEATHER" 6 "NA_PARK_BRAKE_ON" 7 "NA_SEATBELT_NOT_FASTENED" ; VAL_ 1228 BOTTOM_MSG 0 "OK_STEER_ASSIST_SETTINGS" 1 "NO_MSG" 2 "PRESS_SET_TO_SET_SPEED" 3 "PRESS_RES_SET_TO_CHANGE_SPEED" 4 "PRESS_RES_TO_RESTART" 5 "NO_MSG" 6 "CRUISE_NOT_AVAIL" 7 "NO_MSG" ; -VAL_ 689 FOLLOW_DISTANCE 0 "NO_FOLLOW_DISTANCE" 1 "FOLLOW_DISTANCE_1" 2 "FOLLOW_DISTANCE_2" 3 "FOLLOW_DISANCE_3" ; +VAL_ 689 FOLLOW_DISTANCE 0 "NO_FOLLOW_DISTANCE" 1 "FOLLOW_DISTANCE_1" 2 "FOLLOW_DISTANCE_2" 3 "FOLLOW_DISTANCE_3" ; VAL_ 689 AUDIBLE_TONE 0 "NO_TONE" 1 "CONT" 2 "FAST_BEEP_CONT" 3 "TRIPLE_FAST_BEEP_CONT" 4 "SLOW_BEEP_CONT" 5 "QUAD_SLOW_BEEP_CONT" 6 "SINGLE_BEEP_ONCE" 7 "DOUBLE_BEEP_ONCE" ; VAL_ 689 SMALL_STEERING_WHEEL_ICON 0 "NO_ICON" 1 "GRAY_ICON" 2 "GRAY_ICON_FLASHING" 3 "GREEN_ICON" 4 "GREEN_ICON_FLASHING" 5 "RED_ICON" 6 "RED_ICON_FLASHING" 7 "YELLOW_ICON" ; VAL_ 689 LARGE_STEERING_WHEEL_ICON 0 "NO_STEERINGWHEEL" 1 "GRAY_STEERINGWHEEL" 2 "GREEN_STEERINGWHEEL" 3 "GREEN_STEERINGWHEEL_FLASHING" ; diff --git a/opendbc_repo/opendbc/dbc/gm_global_a_powertrain_generated.dbc b/opendbc_repo/opendbc/dbc/gm_global_a_powertrain_generated.dbc index 429665cc..507de8c7 100644 --- a/opendbc_repo/opendbc/dbc/gm_global_a_powertrain_generated.dbc +++ b/opendbc_repo/opendbc/dbc/gm_global_a_powertrain_generated.dbc @@ -197,15 +197,12 @@ BO_ 711 BECMBatteryVoltageCurrent: 6 K17_EBCM SG_ HVBatteryCurrent : 12|13@0- (0.15,0) [-614.4|614.25] "A" NEO BO_ 715 ASCMGasRegenCmd: 8 K124_ASCM - SG_ GasRegenAlwaysOne2 : 9|1@0+ (1,0) [0|1] "" NEO - SG_ GasRegenAlwaysOne : 14|1@0+ (1,0) [0|1] "" NEO - SG_ GasRegenChecksum : 47|24@0+ (1,0) [0|0] "" NEO - SG_ GasRegenCmdActiveInv : 32|1@0+ (1,0) [0|0] "" NEO + SG_ GasRegenAccType : 15|2@0+ (1,0) [0|3] "" NEO + SG_ GasRegenChecksum : 32|25@0+ (1,0) [0|0] "" NEO SG_ GasRegenFullStopActive : 13|1@0+ (1,0) [0|0] "" NEO SG_ GasRegenCmdActive : 0|1@0+ (1,0) [0|0] "" NEO SG_ RollingCounter : 7|2@0+ (1,0) [0|0] "" NEO - SG_ GasRegenAlwaysOne3 : 23|1@0+ (1,0) [0|1] "" NEO - SG_ GasRegenCmd : 22|12@0+ (1,0) [0|0] "" NEO + SG_ GasRegenCmd : 10|19@0+ (0.125,-22534) [-22534|43001.875] "Nm" NEO BO_ 717 ASCM_2CD: 5 K124_ASCM diff --git a/opendbc_repo/opendbc/dbc/nissan_leaf_2018_generated.dbc b/opendbc_repo/opendbc/dbc/nissan_leaf_2018_generated.dbc index bfd30e48..7c638d08 100644 --- a/opendbc_repo/opendbc/dbc/nissan_leaf_2018_generated.dbc +++ b/opendbc_repo/opendbc/dbc/nissan_leaf_2018_generated.dbc @@ -111,7 +111,7 @@ BO_ 1227 LKAS_SETTINGS: 8 XXX VAL_ 1228 PROPILOT_NA_MSGS 0 "NO_MSG" 1 "NA_FRONT_CAMERA_IMPARED" 2 "STEERING_ASSIST_ON_STANDBY" 3 "NA_PARKING_ASSIST_ENABLED" 4 "STEER_ASSIST_CURRENTLY_NA" 5 "NA_BAD_WEATHER" 6 "NA_PARK_BRAKE_ON" 7 "NA_SEATBELT_NOT_FASTENED" ; VAL_ 1228 BOTTOM_MSG 0 "OK_STEER_ASSIST_SETTINGS" 1 "NO_MSG" 2 "PRESS_SET_TO_SET_SPEED" 3 "PRESS_RES_SET_TO_CHANGE_SPEED" 4 "PRESS_RES_TO_RESTART" 5 "NO_MSG" 6 "CRUISE_NOT_AVAIL" 7 "NO_MSG" ; -VAL_ 689 FOLLOW_DISTANCE 0 "NO_FOLLOW_DISTANCE" 1 "FOLLOW_DISTANCE_1" 2 "FOLLOW_DISTANCE_2" 3 "FOLLOW_DISANCE_3" ; +VAL_ 689 FOLLOW_DISTANCE 0 "NO_FOLLOW_DISTANCE" 1 "FOLLOW_DISTANCE_1" 2 "FOLLOW_DISTANCE_2" 3 "FOLLOW_DISTANCE_3" ; VAL_ 689 AUDIBLE_TONE 0 "NO_TONE" 1 "CONT" 2 "FAST_BEEP_CONT" 3 "TRIPLE_FAST_BEEP_CONT" 4 "SLOW_BEEP_CONT" 5 "QUAD_SLOW_BEEP_CONT" 6 "SINGLE_BEEP_ONCE" 7 "DOUBLE_BEEP_ONCE" ; VAL_ 689 SMALL_STEERING_WHEEL_ICON 0 "NO_ICON" 1 "GRAY_ICON" 2 "GRAY_ICON_FLASHING" 3 "GREEN_ICON" 4 "GREEN_ICON_FLASHING" 5 "RED_ICON" 6 "RED_ICON_FLASHING" 7 "YELLOW_ICON" ; VAL_ 689 LARGE_STEERING_WHEEL_ICON 0 "NO_STEERINGWHEEL" 1 "GRAY_STEERINGWHEEL" 2 "GREEN_STEERINGWHEEL" 3 "GREEN_STEERINGWHEEL_FLASHING" ; diff --git a/opendbc_repo/opendbc/dbc/nissan_x_trail_2017_generated.dbc b/opendbc_repo/opendbc/dbc/nissan_x_trail_2017_generated.dbc index c6a3ef7a..66d6bf07 100644 --- a/opendbc_repo/opendbc/dbc/nissan_x_trail_2017_generated.dbc +++ b/opendbc_repo/opendbc/dbc/nissan_x_trail_2017_generated.dbc @@ -111,7 +111,7 @@ BO_ 1227 LKAS_SETTINGS: 8 XXX VAL_ 1228 PROPILOT_NA_MSGS 0 "NO_MSG" 1 "NA_FRONT_CAMERA_IMPARED" 2 "STEERING_ASSIST_ON_STANDBY" 3 "NA_PARKING_ASSIST_ENABLED" 4 "STEER_ASSIST_CURRENTLY_NA" 5 "NA_BAD_WEATHER" 6 "NA_PARK_BRAKE_ON" 7 "NA_SEATBELT_NOT_FASTENED" ; VAL_ 1228 BOTTOM_MSG 0 "OK_STEER_ASSIST_SETTINGS" 1 "NO_MSG" 2 "PRESS_SET_TO_SET_SPEED" 3 "PRESS_RES_SET_TO_CHANGE_SPEED" 4 "PRESS_RES_TO_RESTART" 5 "NO_MSG" 6 "CRUISE_NOT_AVAIL" 7 "NO_MSG" ; -VAL_ 689 FOLLOW_DISTANCE 0 "NO_FOLLOW_DISTANCE" 1 "FOLLOW_DISTANCE_1" 2 "FOLLOW_DISTANCE_2" 3 "FOLLOW_DISANCE_3" ; +VAL_ 689 FOLLOW_DISTANCE 0 "NO_FOLLOW_DISTANCE" 1 "FOLLOW_DISTANCE_1" 2 "FOLLOW_DISTANCE_2" 3 "FOLLOW_DISTANCE_3" ; VAL_ 689 AUDIBLE_TONE 0 "NO_TONE" 1 "CONT" 2 "FAST_BEEP_CONT" 3 "TRIPLE_FAST_BEEP_CONT" 4 "SLOW_BEEP_CONT" 5 "QUAD_SLOW_BEEP_CONT" 6 "SINGLE_BEEP_ONCE" 7 "DOUBLE_BEEP_ONCE" ; VAL_ 689 SMALL_STEERING_WHEEL_ICON 0 "NO_ICON" 1 "GRAY_ICON" 2 "GRAY_ICON_FLASHING" 3 "GREEN_ICON" 4 "GREEN_ICON_FLASHING" 5 "RED_ICON" 6 "RED_ICON_FLASHING" 7 "YELLOW_ICON" ; VAL_ 689 LARGE_STEERING_WHEEL_ICON 0 "NO_STEERINGWHEEL" 1 "GRAY_STEERINGWHEEL" 2 "GREEN_STEERINGWHEEL" 3 "GREEN_STEERINGWHEEL_FLASHING" ; diff --git a/opendbc_repo/opendbc/dbc/rivian_primary_actuator.dbc b/opendbc_repo/opendbc/dbc/rivian_primary_actuator.dbc index 686ec6fe..6422562d 100644 --- a/opendbc_repo/opendbc/dbc/rivian_primary_actuator.dbc +++ b/opendbc_repo/opendbc/dbc/rivian_primary_actuator.dbc @@ -85,28 +85,27 @@ BO_ 272 ACM_SteeringControl: 8 ACM SG_ ACM_SteeringAngleRequest : 23|15@0+ (0.1,-1638.4) [-1638.4|1638.3] "deg" EPAS_P BO_ 288 ACM_lkaHbaCmd: 8 ACM - SG_ ACM_lkaHbaCmd_Checksum : 7|8@0+ (1,0) [0|0] "" EPAS_P - SG_ ACM_lkaHbaCmd_Counter : 11|4@0+ (1,0) [0|0] "" EPAS_P - SG_ ACM_unkown1 : 12|1@0+ (1,0) [0|1] "" XXX - SG_ ACM_unkown6 : 13|1@0+ (1,0) [0|1] "" XXX - SG_ ACM_unkown5 : 14|1@0+ (1,0) [0|1] "" XXX + SG_ ACM_lkaHbaCmd_Checksum : 7|8@0+ (1,0) [0|255] "" EPAS_P + SG_ ACM_lkaHbaCmd_Counter : 11|4@0+ (1,0) [0|15] "" EPAS_P + SG_ ACM_lkaElkRequest : 14|3@0+ (1,0) [0|7] "" EPAS_P SG_ ACM_HapticRequest : 15|1@0+ (1,0) [0|1] "" EPAS_P SG_ ACM_lkaStrToqReq : 23|11@0+ (1,-1024) [-1024|1024] "" EPAS_P SG_ ACM_lkaSymbolState : 26|3@0+ (1,0) [0|7] "" EPAS_P SG_ ACM_lkaToiFlt : 27|1@0+ (1,0) [0|1] "" EPAS_P SG_ ACM_lkaActToi : 28|1@0+ (1,0) [0|1] "" EPAS_P SG_ ACM_hbaSysState : 34|3@0+ (1,0) [0|7] "" EPAS_P - SG_ ACM_FailinfoAeb : 37|3@0+ (1,0) [0|7] "" EPAS_P - SG_ ACM_unkown2 : 38|2@1+ (1,0) [0|3] "" XXX - SG_ ACM_lkaRHWarning : 41|2@0+ (1,0) [0|3] "" EPAS_P - SG_ ACM_lkaLHWarning : 43|2@0+ (1,0) [0|3] "" EPAS_P + SG_ ACM_hbaLamp : 35|1@0+ (1,0) [0|1] "" EPAS_P + SG_ ACM_slifOnOffState : 37|2@0+ (1,0) [0|3] "" EPAS_P + SG_ ACM_elkOnOffState : 39|2@0+ (1,0) [0|3] "" EPAS_P + SG_ ACM_ldwLHWarning : 43|3@0+ (1,0) [0|7] "" EPAS_P SG_ ACM_lkaLaneRecogState : 45|2@0+ (1,0) [0|3] "" EPAS_P - SG_ ACM_hbaOpt : 46|1@0+ (1,0) [0|1] "" EPAS_P - SG_ ACM_hbaLamp : 47|1@0+ (1,0) [0|1] "" EPAS_P - SG_ ACM_unkown3 : 51|4@0+ (1,0) [0|15] "" XXX + SG_ ACM_hbaOnOffState : 47|2@0+ (1,0) [0|3] "" EPAS_P + SG_ ACM_ldwlkaOnOffState : 48|3@0+ (1,0) [0|7] "" EPAS_P + SG_ ACM_ldwWarnTimingState : 51|3@0+ (1,0) [0|7] "" EPAS_P SG_ ACM_lkaHandsoffSoundWarning : 53|2@0+ (1,0) [0|3] "" EPAS_P SG_ ACM_lkaHandsoffDisplayWarning : 55|2@0+ (1,0) [0|3] "" EPAS_P - SG_ ACM_unkown4 : 56|8@1+ (1,0) [0|255] "" XXX + SG_ ACM_ldwRHWarning : 58|3@0+ (1,0) [0|7] "" EPAS_P + SG_ ACM_ldwWarnTypeState : 61|3@0+ (1,0) [0|7] "" EPAS_P BO_ 304 RCM_IMU_LatAccYaw: 8 RCM SG_ RCM_LateralAccelYaw_Checksum : 7|8@0+ (1,0) [0|25] "" ACM,ESP,VDM @@ -828,14 +827,15 @@ VAL_ 288 ACM_lkaSymbolState 0 "ACM_LKASYMBOLSTATE_OFF" 1 "ACM_LKASYMBOLSTATE_WHI VAL_ 288 ACM_lkaToiFlt 0 "ACM_LKATOIFLT_NO_FAULT" 1 "ACM_LKATOIFLT_FAULT_PRESENT"; VAL_ 288 ACM_lkaActToi 0 "ACM_LKAACTTOI_DE_ACTIVATE_TOI" 1 "ACM_LKAACTTOI_ACTIVATE_TOI"; VAL_ 288 ACM_hbaSysState 0 "ACM_HBASYSSTATE_DEFAULT_DISABLE" 1 "ACM_HBASYSSTATE_HBA_ENABLE_HIGH_BEAM_OFF" 2 "ACM_HBASYSSTATE_HBA_ENABLE_HIGH_BEAM_ON" 7 "ACM_HBASYSSTATE_SYSTEM_FAIL"; -VAL_ 288 ACM_FailinfoAeb 0 "ACM_FAILINFOAEB_NORMAL" 1 "ACM_FAILINFOAEB_CAMERA_FAILURE" 2 "ACM_FAILINFOAEB_FRONT_RADAR_COM_ERR" 3 "ACM_FAILINFOAEB_CAMERA_BLOCKAGE" 4 "ACM_FAILINFOAEB_RESERVED_4" 5 "ACM_FAILINFOAEB_RESERVED_5" 6 "ACM_FAILINFOAEB_RESERVED_6"; -VAL_ 288 ACM_lkaRHWarning 0 "ACM_LKARHWARNING_NO_WARNING" 1 "ACM_LKARHWARNING_HAPTIC_WARNING_AND_DISPLAY" 2 "ACM_LKARHWARNING_ACOUSTIC_WARNING_AND_DISPLAY" 3 "ACM_LKARHWARNING_HAPTIC_ACOUSTIC_AND_DISPLAY"; -VAL_ 288 ACM_lkaLHWarning 0 "ACM_LKALHWARNING_NO_WARNING" 1 "ACM_LKALHWARNING_HAPTIC_WARNING_AND_DISPLAY" 2 "ACM_LKALHWARNING_ACOUSTIC_WARNING_AND_DISPLAY" 3 "ACM_LKALHWARNING_HAPTIC_ACOUSTIC_AND_DISPLAY"; +VAL_ 288 ACM_ldwRHWarning 0 "ACM_LKARHWARNING_NO_WARNING" 1 "ACM_LKARHWARNING_HAPTIC_WARNING_AND_DISPLAY" 2 "ACM_LKARHWARNING_ACOUSTIC_WARNING_AND_DISPLAY" 3 "ACM_LKARHWARNING_HAPTIC_ACOUSTIC_AND_DISPLAY"; +VAL_ 288 ACM_ldwLHWarning 0 "ACM_LKALHWARNING_NO_WARNING" 1 "ACM_LKALHWARNING_HAPTIC_WARNING_AND_DISPLAY" 2 "ACM_LKALHWARNING_ACOUSTIC_WARNING_AND_DISPLAY" 3 "ACM_LKALHWARNING_HAPTIC_ACOUSTIC_AND_DISPLAY"; VAL_ 288 ACM_lkaLaneRecogState 0 "ACM_LKALANERECOGSTATE_NOT_RECOGNITION" 1 "ACM_LKALANERECOGSTATE_LEFT_LANE_RECOGNITION" 2 "ACM_LKALANERECOGSTATE_RIGHT_LANE_RECOGNITION" 3 "ACM_LKALANERECOGSTATE_FULL_LANE_RECOGNITION"; -VAL_ 288 ACM_hbaOpt 0 "ACM_HBAOPT_NONE_HBA_OPTION_DEFAULT" 1 "ACM_HBAOPT_HBA_SYSTEM_ENABLE"; VAL_ 288 ACM_hbaLamp 0 "ACM_HBALAMP_HBA_INDICATOR_LAMP_OFF" 1 "ACM_HBALAMP_HBA_INDICATOR_LAMP_ON"; VAL_ 288 ACM_lkaHandsoffSoundWarning 0 "ACM_LKAHANDSOFFSOUNDWARNING_NO_INFO" 1 "ACM_LKAHANDSOFFSOUNDWARNING_WARNING" 2 "ACM_LKAHANDSOFFSOUNDWARNING_RESERVED_2" 3 "ACM_LKAHANDSOFFSOUNDWARNING_RESERVED_3"; VAL_ 288 ACM_lkaHandsoffDisplayWarning 0 "ACM_LKAHANDSOFFDISPLAYWARNING_NO_INFO" 1 "ACM_LKAHANDSOFFDISPLAYWARNING_WARNING" 2 "ACM_LKAHANDSOFFDISPLAYWARNING_RESERVED_2" 3 "ACM_LKAHANDSOFFDISPLAYWARNING_RESERVED_3"; +VAL_ 288 ACM_lkaElkRequest 0 "off" 1 "applying torque right for left departure" 2 "applying torque left for right departure"; +VAL_ 288 ACM_ldwlkaOnOffState 1 "LDW on" 2 "LKAS+LDW on" 3 "all off"; +VAL_ 288 ACM_elkOnOffState 1 "LKAS toggled on" 2 "LKAS toggled off"; VAL_ 304 RCM_IMU_LatAcc_Stat_SensAvail 0 "RCM_IMU_LatAcc_Stat_SensAvail_InSpec" 1 "RCM_IMU_LatAcc_Stat_SensAvail_NotInSpec"; VAL_ 304 RCM_IMU_LatAcc_Stat_Fail 0 "RCM_IMU_LatAcc_Stat_Fail_NotFailed" 1 "RCM_IMU_LatAcc_Stat_Fail_Failed"; VAL_ 304 RCM_IMU_LatAcc_Stat_Init 0 "RCM_IMU_LatAcc_Stat_Init_Finished" 1 "RCM_IMU_LatAcc_Stat_Init_Running"; diff --git a/opendbc_repo/opendbc/dbc/tesla_model3_party.dbc b/opendbc_repo/opendbc/dbc/tesla_model3_party.dbc index b8dcf0ff..2f7b67a0 100644 --- a/opendbc_repo/opendbc/dbc/tesla_model3_party.dbc +++ b/opendbc_repo/opendbc/dbc/tesla_model3_party.dbc @@ -189,6 +189,8 @@ BO_ 599 DI_speed: 8 PARTY SG_ DI_speedCounter : 8|4@1+ (1,0) [0|15] "" park SG_ DI_speedChecksum : 0|8@1+ (1,0) [0|255] "" park +BO_ 605 XXX_longitudinalRelated: 6 XXX + BO_ 1160 DAS_steeringControl: 4 PARTY SG_ DAS_steeringControlChecksum : 31|8@0+ (1,0) [0|255] "" aps SG_ DAS_steeringControlCounter : 19|4@0+ (1,0) [0|15] "" aps @@ -224,15 +226,28 @@ BO_ 646 DI_state: 8 ETH SG_ DI_locStatusChecksum : 0|8@1+ (1,0) [0|0] "" X BO_ 659 DAS_settings: 8 XXX - SG_ DAS_autopilotEnabled : 38|1@0+ (1,0) [0|1] "" XXX + SG_ DAS_driverSteeringWeight : 1|2@0+ (1,0) [0|255] "" XXX + SG_ DAS_slipStart : 2|1@0+ (1,0) [0|1] "" XXX + SG_ DAS_offRoadAssist : 3|2@1+ (1,0) [0|63] "" XXX + SG_ DAS_distanceUnits : 13|1@1+ (1,0) [0|255] "" XXX + SG_ DAS_aebEnabled : 18|1@0+ (1,0) [0|255] "" XXX + SG_ DAS_adaptiveHeadlights : 22|1@1+ (1,0) [0|31] "" XXX + SG_ DAS_autosteerEnabled2 : 24|1@0+ (1,0) [0|1] "" XXX + SG_ DAS_fcwEnabled : 34|1@0+ (1,0) [0|1] "" XXX + SG_ DAS_fcwSensitivity : 37|2@0+ (1,0) [0|63] "" XXX + SG_ DAS_autosteerEnabled : 38|1@0+ (1,0) [0|1] "" XXX + SG_ DAS_obstacleAwareAcceleration : 42|1@0+ (1,0) [0|1] "" XXX + SG_ DAS_driverAccelerationMode : 44|1@1+ (1,0) [0|127] "" XXX SG_ DAS_settingCounter : 52|4@1+ (1,0) [0|15] "" XXX SG_ DAS_settingChecksum : 63|8@0+ (1,0) [0|255] "" XXX BO_ 785 UI_warning: 7 XXX SG_ buckleStatus : 13|1@0+ (1,0) [0|1] "" XXX - SG_ scrollWheelRightTilt : 21|1@0+ (1,0) [0|1] "" XXX + SG_ scrollWheelPressed : 21|1@0+ (1,0) [0|1] "" XXX SG_ leftBlinkerOn : 22|1@0+ (1,0) [0|1] "" XXX SG_ rightBlinkerOn : 23|1@0+ (1,0) [0|1] "" XXX + SG_ leftBlinkerBlinking : 25|2@0+ (1,0) [0|3] "" XXX + SG_ rightBlinkerBlinking : 26|2@1+ (1,0) [0|15] "" XXX SG_ anyDoorOpen : 28|1@0+ (1,0) [0|1] "" XXX SG_ wiperSettings : 39|8@0+ (1,0) [0|255] "" XXX SG_ highBeam : 50|1@0+ (1,0) [0|1] "" XXX @@ -266,11 +281,12 @@ BO_ 923 DAS_status: 8 PARTY SG_ DAS_autopilotState : 0|4@1+ (1,0) [0|15] "" aps +CM_ BO_ 605 "Bytes change when toggling between FSD and AP, as well as Traffic Light and Stop Sign Control in TACC"; - - - - +CM_ SG_ 659 DAS_autosteerEnabled "1 if Autosteer or FSD is enabled, 0 otherwise"; +CM_ SG_ 785 leftBlinkerOn "only describes stalk position if half pressed without auto-cancel blinkers. otherwise acts as expected"; +CM_ SG_ 785 rightBlinkerOn "only describes stalk position if half pressed without auto-cancel blinkers. otherwise acts as expected"; +CM_ SG_ 785 scrollWheelPressed "captures either scroll wheel left, right or down press"; VAL_ 545 VCFRONT_uiAudioLVState 1 "LV_ON" 2 "LV_GOING_DOWN" 3 "LV_FAULT" 0 "LV_OFF" ; VAL_ 545 VCFRONT_uiHiCurrentLVState 1 "LV_ON" 2 "LV_GOING_DOWN" 3 "LV_FAULT" 0 "LV_OFF" ; @@ -385,8 +401,15 @@ VAL_ 646 DI_parkBrakeState 0 "UNAVAILABLE" 1 "RELEASED" 2 "REQUESTED" 3 "APPLIED VAL_ 646 DI_autoparkState 0 "UNAVAILABLE" 1 "STANDBY" 2 "STARTED" 3 "ACTIVE" 4 "COMPLETE" 5 "PAUSED" 6 "ABORTED" 7 "RESUMED" 8 "UNPARK_COMPLETE" 9 "SELFPARK_STARTED" 15 "SNA" ; VAL_ 646 DI_speedUnits 0 "MPH" 1 "KPH" ; VAL_ 646 DI_cruiseState 0 "UNAVAILABLE" 1 "STANDBY" 2 "ENABLED" 3 "STANDSTILL" 4 "OVERRIDE" 5 "FAULT" 6 "PRE_FAULT" 7 "PRE_CANCEL" ; +VAL_ 659 DAS_driverSteeringWeight 0 "light" 1 "standard" 2 "heavy"; +VAL_ 659 DAS_offRoadAssist 0 "disabled" 3 "enabled"; +VAL_ 659 DAS_distanceUnits 1 "miles" 0 "kilometers"; +VAL_ 659 DAS_fcwSensitivity 0 "early" 1 "medium" 2 "late" 3 "off"; +VAL_ 659 DAS_driverAccelerationMode 0 "chill" 1 "standard"; VAL_ 785 buckleStatus 1 "LATCHED" 0 "UNLATCHED" ; VAL_ 785 anyDoorOpen 1 "OPEN" 0 "CLOSED" ; +VAL_ 785 leftBlinkerBlinking 0 "off" 1 "blinking, off" 2 "blinking, on"; +VAL_ 785 rightBlinkerBlinking 0 "off" 1 "blinking, off" 2 "blinking, on"; VAL_ 923 DAS_autoLaneChangeState 5 "ALC_UNAVAILABLE_VEHICLE_SPEED" 17 "ALC_ABORT_POOR_VIEW_RANGE" 23 "ALC_BLOCKED_VEH_TTC_AND_USS_L" 0 "ALC_UNAVAILABLE_DISABLED" 26 "ALC_BLOCKED_LANE_TYPE_L" 29 "ALC_ABORT_TIMEOUT" 9 "ALC_IN_PROGRESS_L" 4 "ALC_UNAVAILABLE_EXITING_HIGHWAY" 22 "ALC_BLOCKED_VEH_TTC_L" 12 "ALC_WAITING_FOR_SIDE_OBST_TO_PASS_R" 18 "ALC_ABORT_LC_HEALTH_BAD" 28 "ALC_WAITING_HANDS_ON" 8 "ALC_AVAILABLE_BOTH" 11 "ALC_WAITING_FOR_SIDE_OBST_TO_PASS_L" 3 "ALC_UNAVAILABLE_TP_FOLLOW" 2 "ALC_UNAVAILABLE_SONICS_INVALID" 21 "ALC_UNAVAILABLE_SOLID_LANE_LINE" 24 "ALC_BLOCKED_VEH_TTC_R" 1 "ALC_UNAVAILABLE_NO_LANES" 25 "ALC_BLOCKED_VEH_TTC_AND_USS_R" 30 "ALC_ABORT_MISSION_PLAN_INVALID" 27 "ALC_BLOCKED_LANE_TYPE_R" 19 "ALC_ABORT_BLINKER_TURNED_OFF" 31 "ALC_SNA" 13 "ALC_WAITING_FOR_FWD_OBST_TO_PASS_L" 16 "ALC_ABORT_SIDE_OBSTACLE_PRESENT_R" 6 "ALC_AVAILABLE_ONLY_L" 20 "ALC_ABORT_OTHER_REASON" 15 "ALC_ABORT_SIDE_OBSTACLE_PRESENT_L" 7 "ALC_AVAILABLE_ONLY_R" 14 "ALC_WAITING_FOR_FWD_OBST_TO_PASS_R" 10 "ALC_IN_PROGRESS_R" ; VAL_ 923 DAS_autopilotHandsOnState 8 "LC_HANDS_ON_SUSPENDED" 15 "LC_HANDS_ON_SNA" 7 "LC_HANDS_ON_REQD_STRUCK_OUT" 3 "LC_HANDS_ON_REQD_VISUAL" 4 "LC_HANDS_ON_REQD_CHIME_1" 6 "LC_HANDS_ON_REQD_SLOWING" 1 "LC_HANDS_ON_REQD_DETECTED" 2 "LC_HANDS_ON_REQD_NOT_DETECTED" 5 "LC_HANDS_ON_REQD_CHIME_2" 0 "LC_HANDS_ON_NOT_REQD" ; VAL_ 923 DAS_fleetSpeedState 0 "FLEETSPEED_UNAVAILABLE" 1 "FLEETSPEED_AVAILABLE" 2 "FLEETSPEED_ACTIVE" 3 "FLEETSPEED_HOLD" ; @@ -404,6 +427,3 @@ VAL_ 923 DAS_fusedSpeedLimit 31 "NONE" 0 "UNKNOWN_SNA" ; VAL_ 923 DAS_blindSpotRearRight 3 "SNA" 0 "NO_WARNING" 1 "WARNING_LEVEL_1" 2 "WARNING_LEVEL_2" ; VAL_ 923 DAS_blindSpotRearLeft 3 "SNA" 0 "NO_WARNING" 1 "WARNING_LEVEL_1" 2 "WARNING_LEVEL_2" ; VAL_ 923 DAS_autopilotState 15 "SNA" 8 "ABORTING" 3 "ACTIVE_NOMINAL" 0 "DISABLED" 4 "ACTIVE_RESTRICTED" 5 "ACTIVE_NAV" 14 "FAULT" 1 "UNAVAILABLE" 9 "ABORTED" 2 "AVAILABLE" ; - - - diff --git a/opendbc_repo/opendbc/dbc/vw_meb.dbc b/opendbc_repo/opendbc/dbc/vw_meb.dbc index 98125aaa..1833d7c5 100644 --- a/opendbc_repo/opendbc/dbc/vw_meb.dbc +++ b/opendbc_repo/opendbc/dbc/vw_meb.dbc @@ -169,7 +169,7 @@ BO_ 192 EM1_01: 32 XXX SG_ Schubbetrieb : 79|1@0+ (1,0) [0|1] "" XXX BO_ 219 AWV_03: 48 XXX - SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX + SG_ XCHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ FCW_Active : 64|1@0+ (1,0) [0|1] "" XXX SG_ Pre_Brake_Fill : 76|1@0+ (1,0) [0|1] "" XXX @@ -180,8 +180,8 @@ BO_ 247 MEB_HVEM_02: 8 XXX SG_ NEW_SIGNAL_3 : 44|10@1+ (1,0) [0|7] "" XXX SG_ NEW_SIGNAL_2 : 54|7@1+ (1,0) [0|3] "" XXX -BO_ 252 ESC_51: 48 XXX - SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX +BO_ 252 ESC_51: 64 XXX + SG_ XCHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ AEB_Breaking_01 : 24|8@1+ (1,0) [0|255] "" XXX SG_ AEB_Breaking_02 : 32|8@1+ (1,0) [0|255] "" XXX @@ -223,7 +223,7 @@ BO_ 253 ESP_21: 8 Gateway SG_ ESC_Neutralschaltung : 63|1@1+ (1,0) [0|1] "" Vector__XXX BO_ 258 ESC_50: 48 XXX - SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX + SG_ XCHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ Lateral_Accel : 16|8@1+ (0.15,-18.9) [0|255] "Unit_MeterPerSquareSecond" XXX SG_ Longitudinal_Accel : 24|10@1+ (0.03125,-16) [0|255] "Unit_MeterPerSquareSecond" XXX @@ -242,8 +242,8 @@ BO_ 261 VMM_01: 8 XXX SG_ NEW_SIGNAL_1 : 40|2@1+ (1,0) [0|3] "" XXX SG_ Brake : 53|7@1+ (1,0) [0|3] "" XXX -BO_ 267 Motor_51: 32 XXX - SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX +BO_ 267 Motor_51: 48 XXX + SG_ XCHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ Accel_Pedal_Pressure : 12|9@1+ (0.4,0) [0|255] "" XXX SG_ Accel_Low_Pressed_Support : 21|1@1+ (1,0) [0|7] "" XXX @@ -319,7 +319,7 @@ BO_ 299 GRA_ACC_01: 8 Gateway BO_ 312 IPA_01: 32 XXX BO_ 313 VMM_02: 32 XXX - SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX + SG_ XCHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ Brake_Pressed_1 : 16|1@0+ (1,0) [0|1] "" XXX SG_ Brake_Pressed_2 : 27|1@0+ (1,0) [0|1] "" XXX @@ -330,7 +330,7 @@ BO_ 313 VMM_02: 32 XXX SG_ Brake_Pressure : 76|11@1+ (1,0) [0|100] "" XXX BO_ 317 QFK_01: 32 XXX - SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX + SG_ XCHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ NEW_SIGNAL_5 : 12|1@0+ (1,0) [0|1] "" XXX SG_ NEW_SIGNAL_9 : 14|1@0+ (1,0) [0|1] "" XXX diff --git a/opendbc_repo/opendbc/safety/safety.h b/opendbc_repo/opendbc/safety/safety.h index ced9f3b8..99859796 100644 --- a/opendbc_repo/opendbc/safety/safety.h +++ b/opendbc_repo/opendbc/safety/safety.h @@ -25,6 +25,7 @@ // CAN-FD only safety modes #ifdef CANFD #include "safety/safety_hyundai_canfd.h" +#include "safety/safety_volkswagen_meb.h" #endif // from cereal.car.CarParams.SafetyModel @@ -417,6 +418,9 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { {SAFETY_RIVIAN, &rivian_hooks}, #ifdef CANFD {SAFETY_HYUNDAI_CANFD, &hyundai_canfd_hooks}, +#ifdef ALLOW_DEBUG + {SAFETY_VOLKSWAGEN_MEB, &volkswagen_meb_hooks}, +#endif #endif #ifdef ALLOW_DEBUG {SAFETY_TESLA, &tesla_hooks}, @@ -643,13 +647,21 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueStee uint32_t ts = microsecond_timer_get(); if (controls_allowed) { + // Some safety models support variable torque limit based on vehicle speed + int max_torque = limits.max_torque; + if (limits.dynamic_max_torque) { + const float fudged_speed = (vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.; + max_torque = interpolate(limits.max_torque_lookup, fudged_speed) + 1; + max_torque = CLAMP(max_torque, -limits.max_torque, limits.max_torque); + } + // *** global torque limit check *** - violation |= max_limit_check(desired_torque, limits.max_steer, -limits.max_steer); + violation |= max_limit_check(desired_torque, max_torque, -max_torque); // *** torque rate limit check *** if (limits.type == TorqueDriverLimited) { violation |= driver_limit_check(desired_torque, desired_torque_last, &torque_driver, - limits.max_steer, limits.max_rate_up, limits.max_rate_down, + max_torque, limits.max_rate_up, limits.max_rate_down, limits.driver_torque_allowance, limits.driver_torque_multiplier); } else { violation |= dist_to_meas_check(desired_torque, desired_torque_last, &torque_meas, @@ -662,7 +674,7 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueStee // every RT_INTERVAL set the new limits uint32_t ts_elapsed = get_ts_elapsed(ts, ts_torque_check_last); - if (ts_elapsed > limits.max_rt_interval) { + if (ts_elapsed > MAX_TORQUE_RT_INTERVAL) { rt_torque_last = desired_torque; ts_torque_check_last = ts; } diff --git a/opendbc_repo/opendbc/safety/safety/safety_chrysler.h b/opendbc_repo/opendbc/safety/safety/safety_chrysler.h index c581fce8..67bda562 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_chrysler.h +++ b/opendbc_repo/opendbc/safety/safety/safety_chrysler.h @@ -104,9 +104,8 @@ static void chrysler_rx_hook(const CANPacket_t *to_push) { static bool chrysler_tx_hook(const CANPacket_t *to_send) { const TorqueSteeringLimits CHRYSLER_STEERING_LIMITS = { - .max_steer = 261, + .max_torque = 261, .max_rt_delta = 112, - .max_rt_interval = 250000, .max_rate_up = 3, .max_rate_down = 3, .max_torque_error = 80, @@ -114,9 +113,8 @@ static bool chrysler_tx_hook(const CANPacket_t *to_send) { }; const TorqueSteeringLimits CHRYSLER_RAM_DT_STEERING_LIMITS = { - .max_steer = 350, + .max_torque = 350, .max_rt_delta = 112, - .max_rt_interval = 250000, .max_rate_up = 6, .max_rate_down = 6, .max_torque_error = 80, @@ -124,9 +122,8 @@ static bool chrysler_tx_hook(const CANPacket_t *to_send) { }; const TorqueSteeringLimits CHRYSLER_RAM_HD_STEERING_LIMITS = { - .max_steer = 361, + .max_torque = 361, .max_rt_delta = 182, - .max_rt_interval = 250000, .max_rate_up = 14, .max_rate_down = 14, .max_torque_error = 80, diff --git a/opendbc_repo/opendbc/safety/safety/safety_ford.h b/opendbc_repo/opendbc/safety/safety/safety_ford.h index b4a4808d..f7943562 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_ford.h +++ b/opendbc_repo/opendbc/safety/safety/safety_ford.h @@ -69,7 +69,6 @@ static uint32_t ford_compute_checksum(const CANPacket_t *to_push) { chksum = 0xFFU - chksum; } else { } - return chksum; } @@ -154,6 +153,7 @@ static void ford_rx_hook(const CANPacket_t *to_push) { // Signal: Veh_V_ActlEng float filtered_pcm_speed = ((GET_BYTE(to_push, 6) << 8) | GET_BYTE(to_push, 7)) * 0.01 / 3.6; bool is_invalid_speed = ABS(filtered_pcm_speed - ((float)vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR)) > FORD_MAX_SPEED_DELTA; + // TODO: this should generically cause rx valid to fall until re-enable if (is_invalid_speed) { controls_allowed = false; } diff --git a/opendbc_repo/opendbc/safety/safety/safety_gm.h b/opendbc_repo/opendbc/safety/safety/safety_gm.h index fb12c49b..17e08995 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_gm.h +++ b/opendbc_repo/opendbc/safety/safety/safety_gm.h @@ -98,13 +98,12 @@ static void gm_rx_hook(const CANPacket_t *to_push) { static bool gm_tx_hook(const CANPacket_t *to_send) { const TorqueSteeringLimits GM_STEERING_LIMITS = { - .max_steer = 300, + .max_torque = 300, .max_rate_up = 10, .max_rate_down = 15, .driver_torque_allowance = 65, .driver_torque_multiplier = 4, .max_rt_delta = 128, - .max_rt_interval = 250000, .type = TorqueDriverLimited, }; @@ -135,7 +134,8 @@ static bool gm_tx_hook(const CANPacket_t *to_send) { // GAS/REGEN: safety check if (addr == 0x2CB) { bool apply = GET_BIT(to_send, 0U); - int gas_regen = ((GET_BYTE(to_send, 2) & 0x7FU) << 5) + ((GET_BYTE(to_send, 3) & 0xF8U) >> 3); + // convert float CAN signal to an int for gas checks: 22534 / 0.125 = 180272 + int gas_regen = (((GET_BYTE(to_send, 1) & 0x7U) << 16) | (GET_BYTE(to_send, 2) << 8) | GET_BYTE(to_send, 3)) - 180272U; bool violation = false; // Allow apply bit in pre-enabled and overriding states @@ -189,10 +189,13 @@ static safety_config gm_init(uint16_t param) { const uint16_t GM_PARAM_HW_CAM = 1; const uint16_t GM_PARAM_EV = 4; + // common safety checks assume unscaled integer values + static const int GM_GAS_TO_CAN = 8; // 1 / 0.125 + static const LongitudinalLimits GM_ASCM_LONG_LIMITS = { - .max_gas = 3072, - .min_gas = 1404, - .inactive_gas = 1404, + .max_gas = 1018 * GM_GAS_TO_CAN, + .min_gas = -650 * GM_GAS_TO_CAN, + .inactive_gas = -650 * GM_GAS_TO_CAN, .max_brake = 400, }; @@ -202,9 +205,9 @@ static safety_config gm_init(uint16_t param) { static const LongitudinalLimits GM_CAM_LONG_LIMITS = { - .max_gas = 3400, - .min_gas = 1514, - .inactive_gas = 1554, + .max_gas = 1346 * GM_GAS_TO_CAN, + .min_gas = -540 * GM_GAS_TO_CAN, + .inactive_gas = -500 * GM_GAS_TO_CAN, .max_brake = 400, }; diff --git a/opendbc_repo/opendbc/safety/safety/safety_hyundai.h b/opendbc_repo/opendbc/safety/safety/safety_hyundai.h index e56c123f..3b656488 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_hyundai.h +++ b/opendbc_repo/opendbc/safety/safety/safety_hyundai.h @@ -4,11 +4,10 @@ #include "safety_hyundai_common.h" #define HYUNDAI_LIMITS(steer, rate_up, rate_down) { \ - .max_steer = (steer), \ + .max_torque = (steer), \ .max_rate_up = (rate_up), \ .max_rate_down = (rate_down), \ .max_rt_delta = 112, \ - .max_rt_interval = 250000, \ .driver_torque_allowance = 50, \ .driver_torque_multiplier = 2, \ .type = TorqueDriverLimited, \ diff --git a/opendbc_repo/opendbc/safety/safety/safety_hyundai_canfd.h b/opendbc_repo/opendbc/safety/safety/safety_hyundai_canfd.h index 7e0cdead..73192bd6 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_hyundai_canfd.h +++ b/opendbc_repo/opendbc/safety/safety/safety_hyundai_canfd.h @@ -139,9 +139,8 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *to_push) { static bool hyundai_canfd_tx_hook(const CANPacket_t *to_send) { const TorqueSteeringLimits HYUNDAI_CANFD_STEERING_LIMITS = { - .max_steer = 270, + .max_torque = 270, .max_rt_delta = 112, - .max_rt_interval = 250000, .max_rate_up = 2, .max_rate_down = 3, .driver_torque_allowance = 250, diff --git a/opendbc_repo/opendbc/safety/safety/safety_mazda.h b/opendbc_repo/opendbc/safety/safety/safety_mazda.h index 7159729a..440b3948 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_mazda.h +++ b/opendbc_repo/opendbc/safety/safety/safety_mazda.h @@ -50,11 +50,10 @@ static void mazda_rx_hook(const CANPacket_t *to_push) { static bool mazda_tx_hook(const CANPacket_t *to_send) { const TorqueSteeringLimits MAZDA_STEERING_LIMITS = { - .max_steer = 800, + .max_torque = 800, .max_rate_up = 10, .max_rate_down = 25, .max_rt_delta = 300, - .max_rt_interval = 250000, .driver_torque_multiplier = 1, .driver_torque_allowance = 15, .type = TorqueDriverLimited, diff --git a/opendbc_repo/opendbc/safety/safety/safety_rivian.h b/opendbc_repo/opendbc/safety/safety/safety_rivian.h index 0afb3090..3a125904 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_rivian.h +++ b/opendbc_repo/opendbc/safety/safety/safety_rivian.h @@ -2,8 +2,78 @@ #include "safety_declarations.h" +#define RIVIAN_MAX_SPEED_DELTA 2.0 // m/s + static bool rivian_longitudinal = false; +static uint8_t rivian_get_counter(const CANPacket_t *to_push) { + int addr = GET_ADDR(to_push); + + uint8_t cnt = 0; + if ((addr == 0x208) || (addr == 0x150)) { + // Signal: ESP_Status_Counter, VDM_PropStatus_Counter + cnt = GET_BYTE(to_push, 1) & 0xFU; + } else { + } + return cnt; +} + +static uint32_t rivian_get_checksum(const CANPacket_t *to_push) { + int addr = GET_ADDR(to_push); + + uint8_t chksum = 0; + if ((addr == 0x208) || (addr == 0x150)) { + // Signal: ESP_Status_Checksum, VDM_PropStatus_Checksum + chksum = GET_BYTE(to_push, 0); + } else { + } + return chksum; +} + +static uint8_t _rivian_compute_checksum(const CANPacket_t *to_push, uint8_t poly, uint8_t xor_output) { + int len = GET_LEN(to_push); + + uint8_t crc = 0; + // Skip the checksum byte + for (int i = 1; i < len; i++) { + crc ^= GET_BYTE(to_push, i); + for (int j = 0; j < 8; j++) { + if ((crc & 0x80U) != 0U) { + crc = (crc << 1) ^ poly; + } else { + crc <<= 1; + } + } + } + return crc ^ xor_output; +} + +static uint32_t rivian_compute_checksum(const CANPacket_t *to_push) { + int addr = GET_ADDR(to_push); + + uint8_t chksum = 0; + if (addr == 0x208) { + chksum = _rivian_compute_checksum(to_push, 0x1D, 0xB1); + } else if (addr == 0x150) { + chksum = _rivian_compute_checksum(to_push, 0x1D, 0x9A); + } else { + } + return chksum; +} + +static bool rivian_get_quality_flag_valid(const CANPacket_t *to_push) { + int addr = GET_ADDR(to_push); + + bool valid = false; + if (addr == 0x208) { + valid = ((GET_BYTE(to_push, 3) >> 3) & 0x3U) == 0x1U; // ESP_Vehicle_Speed_Q + } else if (addr == 0x150) { + valid = (GET_BYTE(to_push, 1) >> 6) == 0x1U; // VDM_VehicleSpeedQ + } else { + } + return valid; +} + static void rivian_rx_hook(const CANPacket_t *to_push) { int bus = GET_BUS(to_push); int addr = GET_ADDR(to_push); @@ -11,7 +81,22 @@ static void rivian_rx_hook(const CANPacket_t *to_push) { if (bus == 0) { // Vehicle speed if (addr == 0x208) { - vehicle_moving = GET_BYTE(to_push, 6) | GET_BYTE(to_push, 7); + float speed = ((GET_BYTE(to_push, 6) << 8) | GET_BYTE(to_push, 7)) * 0.01; + vehicle_moving = speed > 0.0; + UPDATE_VEHICLE_SPEED(speed / 3.6); + } + + // Gas pressed and second speed source for variable torque limit + if (addr == 0x150) { + gas_pressed = GET_BYTE(to_push, 3) | (GET_BYTE(to_push, 4) & 0xC0U); + + // Disable controls if speeds from VDM and ESP ECUs are too far apart. + float vdm_speed = ((GET_BYTE(to_push, 5) << 8) | GET_BYTE(to_push, 6)) * 0.01 / 3.6; + bool is_invalid_speed = ABS(vdm_speed - ((float)vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR)) > RIVIAN_MAX_SPEED_DELTA; + // TODO: this should generically cause rx valid to fall until re-enable + if (is_invalid_speed) { + controls_allowed = false; + } } // Driver torque @@ -20,11 +105,6 @@ static void rivian_rx_hook(const CANPacket_t *to_push) { update_sample(&torque_driver, torque_driver_new); } - // Gas pressed - if (addr == 0x150) { - gas_pressed = GET_BYTE(to_push, 3) | (GET_BYTE(to_push, 4) & 0xC0U); - } - // Brake pressed if (addr == 0x38f) { brake_pressed = GET_BIT(to_push, 23U); @@ -41,12 +121,17 @@ static void rivian_rx_hook(const CANPacket_t *to_push) { } static bool rivian_tx_hook(const CANPacket_t *to_send) { + // Rivian utilizes more torque at low speed to maintain the same lateral accel const TorqueSteeringLimits RIVIAN_STEERING_LIMITS = { - .max_steer = 250, + .max_torque = 350, + .dynamic_max_torque = true, + .max_torque_lookup = { + {9., 17., 17.}, + {350, 250, 250}, + }, .max_rate_up = 3, .max_rate_down = 5, .max_rt_delta = 125, - .max_rt_interval = 250000, .driver_torque_multiplier = 2, .driver_torque_allowance = 100, .type = TorqueDriverLimited, @@ -125,9 +210,9 @@ static safety_config rivian_init(uint16_t param) { static const CanMsg RIVIAN_LONG_TX_MSGS[] = {{0x120, 0, 8, true}, {0x321, 2, 7, false}, {0x160, 0, 5, true}}; static RxCheck rivian_rx_checks[] = { - {.msg = {{0x208, 0, 8, .frequency = 50U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // ESP_Status (speed) + {.msg = {{0x208, 0, 8, .frequency = 50U, .max_counter = 14U, .quality_flag = true}, { 0 }, { 0 }}}, // ESP_Status (speed) + {.msg = {{0x150, 0, 7, .frequency = 50U, .max_counter = 14U, .quality_flag = true}, { 0 }, { 0 }}}, // VDM_PropStatus (gas pedal & 2nd speed) {.msg = {{0x380, 0, 5, .frequency = 100U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // EPAS_SystemStatus (driver torque) - {.msg = {{0x150, 0, 7, .frequency = 50U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // VDM_PropStatus (gas pedal) {.msg = {{0x38f, 0, 6, .frequency = 50U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // iBESP2 (brakes) {.msg = {{0x100, 2, 8, .frequency = 100U, .ignore_checksum = true, .ignore_counter = true}, { 0 }, { 0 }}}, // ACM_Status (cruise state) }; @@ -150,4 +235,8 @@ const safety_hooks rivian_hooks = { .rx = rivian_rx_hook, .tx = rivian_tx_hook, .fwd = rivian_fwd_hook, + .get_counter = rivian_get_counter, + .get_checksum = rivian_get_checksum, + .compute_checksum = rivian_compute_checksum, + .get_quality_flag_valid = rivian_get_quality_flag_valid, }; diff --git a/opendbc_repo/opendbc/safety/safety/safety_subaru.h b/opendbc_repo/opendbc/safety/safety/safety_subaru.h index 8c597760..3b43d392 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_subaru.h +++ b/opendbc_repo/opendbc/safety/safety/safety_subaru.h @@ -4,9 +4,8 @@ #define SUBARU_STEERING_LIMITS_GENERATOR(steer_max, rate_up, rate_down) \ { \ - .max_steer = (steer_max), \ + .max_torque = (steer_max), \ .max_rt_delta = 940, \ - .max_rt_interval = 250000, \ .max_rate_up = (rate_up), \ .max_rate_down = (rate_down), \ .driver_torque_multiplier = 50, \ diff --git a/opendbc_repo/opendbc/safety/safety/safety_subaru_preglobal.h b/opendbc_repo/opendbc/safety/safety/safety_subaru_preglobal.h index 8c229a04..b985c7b7 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_subaru_preglobal.h +++ b/opendbc_repo/opendbc/safety/safety/safety_subaru_preglobal.h @@ -55,9 +55,8 @@ static void subaru_preglobal_rx_hook(const CANPacket_t *to_push) { static bool subaru_preglobal_tx_hook(const CANPacket_t *to_send) { const TorqueSteeringLimits SUBARU_PG_STEERING_LIMITS = { - .max_steer = 2047, + .max_torque = 2047, .max_rt_delta = 940, - .max_rt_interval = 250000, .max_rate_up = 50, .max_rate_down = 70, .driver_torque_multiplier = 10, diff --git a/opendbc_repo/opendbc/safety/safety/safety_toyota.h b/opendbc_repo/opendbc/safety/safety/safety_toyota.h index 7185001d..d9210ec8 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_toyota.h +++ b/opendbc_repo/opendbc/safety/safety/safety_toyota.h @@ -155,19 +155,18 @@ static void toyota_rx_hook(const CANPacket_t *to_push) { static bool toyota_tx_hook(const CANPacket_t *to_send) { const TorqueSteeringLimits TOYOTA_TORQUE_STEERING_LIMITS = { - .max_steer = 1500, + .max_torque = 1500, .max_rate_up = 15, // ramp up slow .max_rate_down = 25, // ramp down fast .max_torque_error = 350, // max torque cmd in excess of motor torque .max_rt_delta = 450, // the real time limit is 1800/sec, a 20% buffer - .max_rt_interval = 250000, .type = TorqueMotorLimited, // the EPS faults when the steering angle rate is above a certain threshold for too long. to prevent this, // we allow setting STEER_REQUEST bit to 0 while maintaining the requested torque value for a single frame .min_valid_request_frames = 18, .max_invalid_request_frames = 1, - .min_valid_request_rt_interval = 170000, // 170ms; a ~10% buffer on cutting every 19 frames + .min_valid_request_rt_interval = 171000, // 171ms; a ~10% buffer on cutting every 19 frames .has_steer_req_tolerance = true, }; diff --git a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_common.h b/opendbc_repo/opendbc/safety/safety/safety_volkswagen_common.h index 1285bb88..d75a5861 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_common.h +++ b/opendbc_repo/opendbc/safety/safety/safety_volkswagen_common.h @@ -17,13 +17,26 @@ bool volkswagen_resume_button_prev = false; #define MSG_LH_EPS_03 0x09F // RX from EPS, for driver steering torque #define MSG_ESP_19 0x0B2 // RX from ABS, for wheel speeds +#define MSG_ESC_51 0x0FC // RX, for wheel speeds +#define MSG_ESC_50 0x102 // RX, for yaw rate #define MSG_ESP_05 0x106 // RX from ABS, for brake switch state +#define MSG_Motor_51 0x10B // RX for TSK state #define MSG_TSK_06 0x120 // RX from ECU, for ACC status from drivetrain coordinator #define MSG_MOTOR_20 0x121 // RX from ECU, for driver throttle input #define MSG_ACC_06 0x122 // TX by OP, ACC control instructions to the drivetrain coordinator #define MSG_HCA_01 0x126 // TX by OP, Heading Control Assist steering torque #define MSG_GRA_ACC_01 0x12B // TX by OP, ACC control buttons for cancel/resume #define MSG_ACC_07 0x12E // TX by OP, ACC control instructions to the drivetrain coordinator +#define MSG_VMM_02 0x139 // RX, for ESP hold management +#define MSG_QFK_01 0x13D // RX, for steering angle +#define MSG_Motor_54 0x14C // RX, for accel pedal +#define MSG_ACC_18 0x14D // RX from ECU, for ACC status +#define MSG_EA_01 0x1A4 // TX, for EA mitigation +#define MSG_EA_02 0x1F0 // TX, for EA mitigation +#define MSG_EML_06 0x20A // RX, for yaw rate +#define MSG_TA_01 0x26B // TX for Travel Assist status +#define MSG_MEB_ACC_01 0x300 // RX from ECU, for ACC status +#define MSG_HCA_03 0x303 // TX by OP, Heading Control Assist steering torque #define MSG_ACC_02 0x30C // TX by OP, ACC HUD data to the instrument cluster #define MSG_LDW_02 0x397 // TX by OP, Lane line recognition and text alerts #define MSG_MOTOR_14 0x3BE // RX from ECU, for brake switch status @@ -56,12 +69,22 @@ static uint32_t volkswagen_mqb_meb_compute_crc(const CANPacket_t *to_push) { crc ^= (uint8_t[]){0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5}[counter]; } else if (addr == MSG_ESP_05) { crc ^= (uint8_t[]){0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07}[counter]; + } else if (addr == MSG_QFK_01) { + crc ^= (uint8_t[]){0x20,0xCA,0x68,0xD5,0x1B,0x31,0xE2,0xDA,0x08,0x0A,0xD4,0xDE,0x9C,0xE4,0x35,0x5B}[counter]; } else if (addr == MSG_TSK_06) { crc ^= (uint8_t[]){0xC4,0xE2,0x4F,0xE4,0xF8,0x2F,0x56,0x81,0x9F,0xE5,0x83,0x44,0x05,0x3F,0x97,0xDF}[counter]; } else if (addr == MSG_MOTOR_20) { crc ^= (uint8_t[]){0xE9,0x65,0xAE,0x6B,0x7B,0x35,0xE5,0x5F,0x4E,0xC7,0x86,0xA2,0xBB,0xDD,0xEB,0xB4}[counter]; } else if (addr == MSG_GRA_ACC_01) { crc ^= (uint8_t[]){0x6A,0x38,0xB4,0x27,0x22,0xEF,0xE1,0xBB,0xF8,0x80,0x84,0x49,0xC7,0x9E,0x1E,0x2B}[counter]; + } else if (addr == MSG_ESC_51) { + crc ^= (uint8_t[]){0x77,0x5C,0xA0,0x89,0x4B,0x7C,0xBB,0xD6,0x1F,0x6C,0x4F,0xF6,0x20,0x2B,0x43,0xDD}[counter]; + } else if (addr == MSG_Motor_54) { + crc ^= (uint8_t[]){0x16,0x35,0x59,0x15,0x9A,0x2A,0x97,0xB8,0x0E,0x4E,0x30,0xCC,0xB3,0x07,0x01,0xAD}[counter]; + } else if (addr == MSG_Motor_51) { + crc ^= (uint8_t[]){0x77,0x5C,0xA0,0x89,0x4B,0x7C,0xBB,0xD6,0x1F,0x6C,0x4F,0xF6,0x20,0x2B,0x43,0xDD}[counter]; + } else if (addr == MSG_MOTOR_14) { + crc ^= (uint8_t[]){0x1F,0x28,0xC6,0x85,0xE6,0xF8,0xB0,0x19,0x5B,0x64,0x35,0x21,0xE4,0xF7,0x9C,0x24}[counter]; } else { // Undefined CAN message, CRC check expected to fail } diff --git a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_mqb.h b/opendbc_repo/opendbc/safety/safety/safety_volkswagen_mqb.h index f3a18516..ebbff496 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_mqb.h +++ b/opendbc_repo/opendbc/safety/safety/safety_volkswagen_mqb.h @@ -127,9 +127,8 @@ static void volkswagen_mqb_rx_hook(const CANPacket_t *to_push) { static bool volkswagen_mqb_tx_hook(const CANPacket_t *to_send) { // lateral limits const TorqueSteeringLimits VOLKSWAGEN_MQB_STEERING_LIMITS = { - .max_steer = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated) + .max_torque = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated) .max_rt_delta = 75, // 4 max rate up * 50Hz send rate * 250000 RT interval / 1000000 = 50 ; 50 * 1.5 for safety pad = 75 - .max_rt_interval = 250000, // 250ms between real time checks .max_rate_up = 4, // 2.0 Nm/s RoC limit (EPS rack has own soft-limit of 5.0 Nm/s) .max_rate_down = 10, // 5.0 Nm/s RoC limit (EPS rack has own soft-limit of 5.0 Nm/s) .driver_torque_allowance = 80, diff --git a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_pq.h b/opendbc_repo/opendbc/safety/safety/safety_volkswagen_pq.h index 3f0d8269..c4cf014f 100644 --- a/opendbc_repo/opendbc/safety/safety/safety_volkswagen_pq.h +++ b/opendbc_repo/opendbc/safety/safety/safety_volkswagen_pq.h @@ -154,9 +154,8 @@ static void volkswagen_pq_rx_hook(const CANPacket_t *to_push) { static bool volkswagen_pq_tx_hook(const CANPacket_t *to_send) { // lateral limits const TorqueSteeringLimits VOLKSWAGEN_PQ_STEERING_LIMITS = { - .max_steer = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated) + .max_torque = 300, // 3.0 Nm (EPS side max of 3.0Nm with fault if violated) .max_rt_delta = 113, // 6 max rate up * 50Hz send rate * 250000 RT interval / 1000000 = 75 ; 125 * 1.5 for safety pad = 113 - .max_rt_interval = 250000, // 250ms between real time checks .max_rate_up = 6, // 3.0 Nm/s RoC limit (EPS rack has own soft-limit of 5.0 Nm/s) .max_rate_down = 10, // 5.0 Nm/s RoC limit (EPS rack has own soft-limit of 5.0 Nm/s) .driver_torque_multiplier = 3, diff --git a/opendbc_repo/opendbc/safety/safety_declarations.h b/opendbc_repo/opendbc/safety/safety_declarations.h index c9fe9131..85002f3f 100644 --- a/opendbc_repo/opendbc/safety/safety_declarations.h +++ b/opendbc_repo/opendbc/safety/safety_declarations.h @@ -33,6 +33,7 @@ extern const int MAX_WRONG_COUNTERS; #define MAX_SAMPLE_VALS 6 // used to represent floating point vehicle speed in a sample_t #define VEHICLE_SPEED_FACTOR 1000.0 +#define MAX_TORQUE_RT_INTERVAL 250000U // sample struct that keeps 6 samples in memory @@ -62,11 +63,13 @@ typedef enum { typedef struct { // torque cmd limits - const int max_steer; + const int max_torque; // this upper limit is always enforced + const bool dynamic_max_torque; // use max_torque_lookup to apply torque limit based on speed + const struct lookup_t max_torque_lookup; + const int max_rate_up; const int max_rate_down; - const int max_rt_delta; - const uint32_t max_rt_interval; + const int max_rt_delta; // max change in torque per 250ms interval (MAX_TORQUE_RT_INTERVAL) const SteeringControlType type; @@ -293,3 +296,4 @@ extern const safety_hooks toyota_hooks; extern const safety_hooks volkswagen_mqb_hooks; extern const safety_hooks volkswagen_pq_hooks; extern const safety_hooks rivian_hooks; +extern const safety_hooks volkswagen_meb_hooks; diff --git a/opendbc_repo/opendbc/safety/tests/common.py b/opendbc_repo/opendbc/safety/tests/common.py index f087fd52..6ca8d585 100644 --- a/opendbc_repo/opendbc/safety/tests/common.py +++ b/opendbc_repo/opendbc/safety/tests/common.py @@ -166,6 +166,7 @@ class LongitudinalGasBrakeSafetyTest(PandaSafetyTestBase, abc.ABC): MIN_GAS: int = 0 MAX_GAS: int | None = None INACTIVE_GAS = 0 + MIN_POSSIBLE_GAS: int = 0. MAX_POSSIBLE_GAS: int | None = None def test_gas_brake_limits_correct(self): @@ -187,16 +188,17 @@ class LongitudinalGasBrakeSafetyTest(PandaSafetyTestBase, abc.ABC): self._generic_limit_safety_check(self._send_brake_msg, self.MIN_BRAKE, self.MAX_BRAKE, 0, self.MAX_POSSIBLE_BRAKE, 1) def test_gas_safety_check(self): - self._generic_limit_safety_check(self._send_gas_msg, self.MIN_GAS, self.MAX_GAS, 0, self.MAX_POSSIBLE_GAS, 1, self.INACTIVE_GAS) + self._generic_limit_safety_check(self._send_gas_msg, self.MIN_GAS, self.MAX_GAS, self.MIN_POSSIBLE_GAS, self.MAX_POSSIBLE_GAS, 1, self.INACTIVE_GAS) class TorqueSteeringSafetyTestBase(PandaSafetyTestBase, abc.ABC): MAX_RATE_UP = 0 MAX_RATE_DOWN = 0 - MAX_TORQUE = 0 + MAX_TORQUE_LOOKUP: tuple[list[float], list[int]] = ([0], [0]) + DYNAMIC_MAX_TORQUE = False MAX_RT_DELTA = 0 - RT_INTERVAL = 0 + RT_INTERVAL = 250000 NO_STEER_REQ_BIT = False @@ -206,23 +208,53 @@ class TorqueSteeringSafetyTestBase(PandaSafetyTestBase, abc.ABC): cls.safety = None raise unittest.SkipTest + @property + def MAX_TORQUE(self): + return max(self.MAX_TORQUE_LOOKUP[1]) + + @property + def _torque_speed_range(self): + if not self.DYNAMIC_MAX_TORQUE: + return [0] + else: + # test with more precision inside breakpoint range + min_speed = min(self.MAX_TORQUE_LOOKUP[0]) + max_speed = max(self.MAX_TORQUE_LOOKUP[0]) + return np.concatenate([np.arange(0, min_speed, 5), np.arange(min_speed, max_speed, 0.5), np.arange(max_speed, 40, 5)]) + + def _get_max_torque(self, speed): + # matches safety fudge + torque = int(np.interp(speed - 1, self.MAX_TORQUE_LOOKUP[0], self.MAX_TORQUE_LOOKUP[1]) + 1) + return min(torque, self.MAX_TORQUE) + @abc.abstractmethod def _torque_cmd_msg(self, torque, steer_req=1): pass + @abc.abstractmethod + def _speed_msg(self, speed): + pass + + def _reset_speed_measurement(self, speed): + for _ in range(MAX_SAMPLE_VALS): + self._rx(self._speed_msg(speed)) + def _set_prev_torque(self, t): self.safety.set_desired_torque_last(t) self.safety.set_rt_torque_last(t) def test_steer_safety_check(self): - for enabled in [0, 1]: - for t in range(int(-self.MAX_TORQUE * 1.5), int(self.MAX_TORQUE * 1.5)): - self.safety.set_controls_allowed(enabled) - self._set_prev_torque(t) - if abs(t) > self.MAX_TORQUE or (not enabled and abs(t) > 0): - self.assertFalse(self._tx(self._torque_cmd_msg(t))) - else: - self.assertTrue(self._tx(self._torque_cmd_msg(t))) + for speed in self._torque_speed_range: + self._reset_speed_measurement(speed) + max_torque = self._get_max_torque(speed) + for enabled in [0, 1]: + for t in range(int(-max_torque * 1.5), int(max_torque * 1.5)): + self.safety.set_controls_allowed(enabled) + self._set_prev_torque(t) + if abs(t) > max_torque or (not enabled and abs(t) > 0): + self.assertFalse(self._tx(self._torque_cmd_msg(t))) + else: + self.assertTrue(self._tx(self._torque_cmd_msg(t))) def test_non_realtime_limit_up(self): self.safety.set_controls_allowed(True) @@ -266,7 +298,12 @@ class SteerRequestCutSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): # Safety around steering request bit mismatch tolerance MIN_VALID_STEERING_FRAMES: int MAX_INVALID_STEERING_FRAMES: int - MIN_VALID_STEERING_RT_INTERVAL: int + STEER_STEP: int = 1 + + @property + def MIN_VALID_STEERING_RT_INTERVAL(self): + # a ~10% buffer + return int((self.MIN_VALID_STEERING_FRAMES + 1) * self.STEER_STEP * 10000 * 0.9) def test_steer_req_bit_frames(self): """ @@ -389,40 +426,44 @@ class DriverTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): # Tests down limits and driver torque blending self.safety.set_controls_allowed(True) - # Cannot stay at MAX_TORQUE if above DRIVER_TORQUE_ALLOWANCE - for sign in [-1, 1]: - for driver_torque in np.arange(0, self.DRIVER_TORQUE_ALLOWANCE * 2, 1): - self._reset_torque_driver_measurement(-driver_torque * sign) - self._set_prev_torque(self.MAX_TORQUE * sign) - should_tx = abs(driver_torque) <= self.DRIVER_TORQUE_ALLOWANCE - self.assertEqual(should_tx, self._tx(self._torque_cmd_msg(self.MAX_TORQUE * sign))) + for speed in self._torque_speed_range: + self._reset_speed_measurement(speed) + max_torque = self._get_max_torque(speed) - # arbitrary high driver torque to ensure max steer torque is allowed - max_driver_torque = int(self.MAX_TORQUE / self.DRIVER_TORQUE_FACTOR + self.DRIVER_TORQUE_ALLOWANCE + 1) + # Cannot stay at MAX_TORQUE if above DRIVER_TORQUE_ALLOWANCE + for sign in [-1, 1]: + for driver_torque in np.arange(0, self.DRIVER_TORQUE_ALLOWANCE * 2, 1): + self._reset_torque_driver_measurement(-driver_torque * sign) + self._set_prev_torque(max_torque * sign) + should_tx = abs(driver_torque) <= self.DRIVER_TORQUE_ALLOWANCE + self.assertEqual(should_tx, self._tx(self._torque_cmd_msg(max_torque * sign))) - # spot check some individual cases - for sign in [-1, 1]: - # Ensure we wind down factor units for every unit above allowance - driver_torque = (self.DRIVER_TORQUE_ALLOWANCE + 10) * sign - torque_desired = (self.MAX_TORQUE - 10 * self.DRIVER_TORQUE_FACTOR) * sign - delta = 1 * sign - self._set_prev_torque(torque_desired) - self._reset_torque_driver_measurement(-driver_torque) - self.assertTrue(self._tx(self._torque_cmd_msg(torque_desired))) - self._set_prev_torque(torque_desired + delta) - self._reset_torque_driver_measurement(-driver_torque) - self.assertFalse(self._tx(self._torque_cmd_msg(torque_desired + delta))) + # arbitrary high driver torque to ensure max steer torque is allowed + max_driver_torque = int(max_torque / self.DRIVER_TORQUE_FACTOR + self.DRIVER_TORQUE_ALLOWANCE + 1) - # If we're well past the allowance, minimum wind down is MAX_RATE_DOWN - self._set_prev_torque(self.MAX_TORQUE * sign) - self._reset_torque_driver_measurement(-max_driver_torque * sign) - self.assertTrue(self._tx(self._torque_cmd_msg((self.MAX_TORQUE - self.MAX_RATE_DOWN) * sign))) - self._set_prev_torque(self.MAX_TORQUE * sign) - self._reset_torque_driver_measurement(-max_driver_torque * sign) - self.assertTrue(self._tx(self._torque_cmd_msg(0))) - self._set_prev_torque(self.MAX_TORQUE * sign) - self._reset_torque_driver_measurement(-max_driver_torque * sign) - self.assertFalse(self._tx(self._torque_cmd_msg((self.MAX_TORQUE - self.MAX_RATE_DOWN + 1) * sign))) + # spot check some individual cases + for sign in [-1, 1]: + # Ensure we wind down factor units for every unit above allowance + driver_torque = (self.DRIVER_TORQUE_ALLOWANCE + 10) * sign + torque_desired = (max_torque - 10 * self.DRIVER_TORQUE_FACTOR) * sign + delta = 1 * sign + self._set_prev_torque(torque_desired) + self._reset_torque_driver_measurement(-driver_torque) + self.assertTrue(self._tx(self._torque_cmd_msg(torque_desired))) + self._set_prev_torque(torque_desired + delta) + self._reset_torque_driver_measurement(-driver_torque) + self.assertFalse(self._tx(self._torque_cmd_msg(torque_desired + delta))) + + # If we're well past the allowance, minimum wind down is MAX_RATE_DOWN + self._set_prev_torque(max_torque * sign) + self._reset_torque_driver_measurement(-max_driver_torque * sign) + self.assertTrue(self._tx(self._torque_cmd_msg((max_torque - self.MAX_RATE_DOWN) * sign))) + self._set_prev_torque(max_torque * sign) + self._reset_torque_driver_measurement(-max_driver_torque * sign) + self.assertTrue(self._tx(self._torque_cmd_msg(0))) + self._set_prev_torque(max_torque * sign) + self._reset_torque_driver_measurement(-max_driver_torque * sign) + self.assertFalse(self._tx(self._torque_cmd_msg((max_torque - self.MAX_RATE_DOWN + 1) * sign))) def test_realtime_limits(self): self.safety.set_controls_allowed(True) @@ -479,34 +520,41 @@ class MotorTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): self.safety.set_torque_meas(t, t) def test_torque_absolute_limits(self): - for controls_allowed in [True, False]: - for torque in np.arange(-self.MAX_TORQUE - 1000, self.MAX_TORQUE + 1000, self.MAX_RATE_UP): - self.safety.set_controls_allowed(controls_allowed) - self.safety.set_rt_torque_last(torque) - self.safety.set_torque_meas(torque, torque) - self.safety.set_desired_torque_last(torque - self.MAX_RATE_UP) + for speed in self._torque_speed_range: + self._reset_speed_measurement(speed) + max_torque = self._get_max_torque(speed) + for controls_allowed in [True, False]: + for torque in np.arange(-max_torque - 1000, max_torque + 1000, self.MAX_RATE_UP): + self.safety.set_controls_allowed(controls_allowed) + self.safety.set_rt_torque_last(torque) + self.safety.set_torque_meas(torque, torque) + self.safety.set_desired_torque_last(torque - self.MAX_RATE_UP) - if controls_allowed: - send = (-self.MAX_TORQUE <= torque <= self.MAX_TORQUE) - else: - send = torque == 0 + if controls_allowed: + send = (-max_torque <= torque <= max_torque) + else: + send = torque == 0 - self.assertEqual(send, self._tx(self._torque_cmd_msg(torque))) + self.assertEqual(send, self._tx(self._torque_cmd_msg(torque))) def test_non_realtime_limit_down(self): self.safety.set_controls_allowed(True) - torque_meas = self.MAX_TORQUE - self.MAX_TORQUE_ERROR - 50 + for speed in self._torque_speed_range: + self._reset_speed_measurement(speed) + max_torque = self._get_max_torque(speed) - self.safety.set_rt_torque_last(self.MAX_TORQUE) - self.safety.set_torque_meas(torque_meas, torque_meas) - self.safety.set_desired_torque_last(self.MAX_TORQUE) - self.assertTrue(self._tx(self._torque_cmd_msg(self.MAX_TORQUE - self.MAX_RATE_DOWN))) + torque_meas = max_torque - self.MAX_TORQUE_ERROR - 50 - self.safety.set_rt_torque_last(self.MAX_TORQUE) - self.safety.set_torque_meas(torque_meas, torque_meas) - self.safety.set_desired_torque_last(self.MAX_TORQUE) - self.assertFalse(self._tx(self._torque_cmd_msg(self.MAX_TORQUE - self.MAX_RATE_DOWN + 1))) + self.safety.set_rt_torque_last(max_torque) + self.safety.set_torque_meas(torque_meas, torque_meas) + self.safety.set_desired_torque_last(max_torque) + self.assertTrue(self._tx(self._torque_cmd_msg(max_torque - self.MAX_RATE_DOWN))) + + self.safety.set_rt_torque_last(max_torque) + self.safety.set_torque_meas(torque_meas, torque_meas) + self.safety.set_desired_torque_last(max_torque) + self.assertFalse(self._tx(self._torque_cmd_msg(max_torque - self.MAX_RATE_DOWN + 1))) def test_exceed_torque_sensor(self): self.safety.set_controls_allowed(True) @@ -577,7 +625,23 @@ class MotorTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): self.assertEqual(self.safety.get_torque_meas_max(), 0) -class AngleSteeringSafetyTest(PandaSafetyTestBase): +class VehicleSpeedSafetyTest(PandaSafetyTestBase): + @classmethod + def setUpClass(cls): + if cls.__name__ == "VehicleSpeedSafetyTest": + cls.safety = None + raise unittest.SkipTest + + @abc.abstractmethod + def _speed_msg(self, speed): + pass + + def test_vehicle_speed_measurements(self): + # TODO: lower tolerance on these tests + self._common_measurement_test(self._speed_msg, 0, 80, 1, self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max) + + +class AngleSteeringSafetyTest(VehicleSpeedSafetyTest): STEER_ANGLE_MAX: float = 300 DEG_TO_CAN: float @@ -591,10 +655,6 @@ class AngleSteeringSafetyTest(PandaSafetyTestBase): cls.safety = None raise unittest.SkipTest - @abc.abstractmethod - def _speed_msg(self, speed): - pass - @abc.abstractmethod def _angle_cmd_msg(self, angle: float, enabled: bool): pass @@ -615,10 +675,6 @@ class AngleSteeringSafetyTest(PandaSafetyTestBase): for _ in range(MAX_SAMPLE_VALS): self._rx(self._speed_msg(speed)) - def test_vehicle_speed_measurements(self): - # TODO: lower tolerance on these tests - self._common_measurement_test(self._speed_msg, 0, 80, 1, self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max) - def test_steering_angle_measurements(self): self._common_measurement_test(self._angle_meas_msg, -self.STEER_ANGLE_MAX, self.STEER_ANGLE_MAX, self.DEG_TO_CAN, self.safety.get_angle_meas_min, self.safety.get_angle_meas_max) @@ -776,7 +832,8 @@ class PandaSafetyTest(PandaSafetyTestBase): continue if {attr, current_test}.issubset({'TestHyundaiLongitudinalSafety', 'TestHyundaiLongitudinalSafetyCameraSCC', 'TestHyundaiSafetyFCEVLong'}): continue - if {attr, current_test}.issubset({'TestVolkswagenMqbSafety', 'TestVolkswagenMqbStockSafety', 'TestVolkswagenMqbLongSafety'}): + if {attr, current_test}.issubset({'TestVolkswagenMqbSafety', 'TestVolkswagenMqbStockSafety', 'TestVolkswagenMqbLongSafety', + 'TestVolkswagenMebSafety', 'TestVolkswagenMebStockSafety',}): continue # overlapping TX addrs, but they're not actuating messages for either car diff --git a/opendbc_repo/opendbc/safety/tests/safety_replay/helpers.py b/opendbc_repo/opendbc/safety/tests/safety_replay/helpers.py index d4d31de9..f0e09fcf 100644 --- a/opendbc_repo/opendbc/safety/tests/safety_replay/helpers.py +++ b/opendbc_repo/opendbc/safety/tests/safety_replay/helpers.py @@ -18,7 +18,7 @@ def is_steering_msg(mode, param, addr): ret = addr == (0x191 if param & ToyotaSafetyFlags.LTA else 0x2E4) elif mode == CarParams.SafetyModel.gm: ret = addr == 384 - elif mode == CarParams.SafetyModel.hyundai: + elif mode in (CarParams.SafetyModel.hyundai, CarParams.SafetyModel.hyundaiLegacy): ret = addr == 832 elif mode == CarParams.SafetyModel.hyundaiCanfd: ret = addr == (0x110 if param & HyundaiSafetyFlags.CANFD_LKA_STEERING_ALT else @@ -52,7 +52,7 @@ def get_steer_value(mode, param, to_send): elif mode == CarParams.SafetyModel.gm: torque = ((to_send.data[0] & 0x7) << 8) | to_send.data[1] torque = to_signed(torque, 11) - elif mode == CarParams.SafetyModel.hyundai: + elif mode in (CarParams.SafetyModel.hyundai, CarParams.SafetyModel.hyundaiLegacy): torque = (((to_send.data[3] & 0x7) << 8) | to_send.data[2]) - 1024 elif mode == CarParams.SafetyModel.hyundaiCanfd: torque = ((to_send.data[5] >> 1) | (to_send.data[6] & 0xF) << 7) - 1024 diff --git a/opendbc_repo/opendbc/safety/tests/test_chrysler.py b/opendbc_repo/opendbc/safety/tests/test_chrysler.py index 854c2f5f..5510e137 100755 --- a/opendbc_repo/opendbc/safety/tests/test_chrysler.py +++ b/opendbc_repo/opendbc/safety/tests/test_chrysler.py @@ -15,9 +15,8 @@ class TestChryslerSafety(common.PandaCarSafetyTest, common.MotorTorqueSteeringSa MAX_RATE_UP = 3 MAX_RATE_DOWN = 3 - MAX_TORQUE = 261 + MAX_TORQUE_LOOKUP = [0], [261] MAX_RT_DELTA = 112 - RT_INTERVAL = 250000 MAX_TORQUE_ERROR = 80 LKAS_ACTIVE_VALUE = 1 @@ -80,7 +79,7 @@ class TestChryslerRamDTSafety(TestChryslerSafety): MAX_RATE_UP = 6 MAX_RATE_DOWN = 6 - MAX_TORQUE = 350 + MAX_TORQUE_LOOKUP = [0], [350] DAS_BUS = 2 @@ -101,7 +100,7 @@ class TestChryslerRamHDSafety(TestChryslerSafety): RELAY_MALFUNCTION_ADDRS = {0: (0x276,)} FWD_BLACKLISTED_ADDRS = {2: [0x275, 0x276]} - MAX_TORQUE = 361 + MAX_TORQUE_LOOKUP = [0], [361] MAX_RATE_UP = 14 MAX_RATE_DOWN = 14 MAX_RT_DELTA = 182 diff --git a/opendbc_repo/opendbc/safety/tests/test_gm.py b/opendbc_repo/opendbc/safety/tests/test_gm.py index ab652472..a92e1173 100755 --- a/opendbc_repo/opendbc/safety/tests/test_gm.py +++ b/opendbc_repo/opendbc/safety/tests/test_gm.py @@ -23,7 +23,8 @@ class GmLongitudinalBase(common.PandaCarSafetyTest, common.LongitudinalGasBrakeS MAX_POSSIBLE_BRAKE = 2 ** 12 MAX_BRAKE = 400 - MAX_POSSIBLE_GAS = 2 ** 12 + MAX_POSSIBLE_GAS = 4000 # reasonably excessive limits, not signal max + MIN_POSSIBLE_GAS = -4000 PCM_CRUISE = False # openpilot can control the PCM state if longitudinal @@ -80,9 +81,8 @@ class TestGmSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteeringSaf MAX_RATE_UP = 10 MAX_RATE_DOWN = 15 - MAX_TORQUE = 300 + MAX_TORQUE_LOOKUP = [0], [300] MAX_RT_DELTA = 128 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 65 DRIVER_TORQUE_FACTOR = 4 @@ -151,9 +151,9 @@ class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase): FWD_BUS_LOOKUP: dict[int, int] = {} BRAKE_BUS = 2 - MAX_GAS = 3072 - MIN_GAS = 1404 # maximum regen - INACTIVE_GAS = 1404 + MAX_GAS = 1018 + MIN_GAS = -650 # maximum regen + INACTIVE_GAS = -650 def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") @@ -211,9 +211,9 @@ class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase) FWD_BLACKLISTED_ADDRS = {2: [0x180, 0x2CB, 0x370, 0x315], 0: [0x184]} # block LKAS, ACC messages and PSCMStatus BUTTONS_BUS = 0 # rx only - MAX_GAS = 3400 - MIN_GAS = 1514 # maximum regen - INACTIVE_GAS = 1554 + MAX_GAS = 1346 + MIN_GAS = -540 # maximum regen + INACTIVE_GAS = -500 def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai.py b/opendbc_repo/opendbc/safety/tests/test_hyundai.py index fa95fdca..80ec273a 100755 --- a/opendbc_repo/opendbc/safety/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai.py @@ -53,16 +53,14 @@ class TestHyundaiSafety(HyundaiButtonBase, common.PandaCarSafetyTest, common.Dri MAX_RATE_UP = 3 MAX_RATE_DOWN = 7 - MAX_TORQUE = 384 + MAX_TORQUE_LOOKUP = [0], [384] MAX_RT_DELTA = 112 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 50 DRIVER_TORQUE_FACTOR = 2 # Safety around steering req bit MIN_VALID_STEERING_FRAMES = 89 MAX_INVALID_STEERING_FRAMES = 2 - MIN_VALID_STEERING_RT_INTERVAL = 810000 # a ~10% buffer, can send steer up to 110Hz cnt_gas = 0 cnt_speed = 0 @@ -117,7 +115,7 @@ class TestHyundaiSafety(HyundaiButtonBase, common.PandaCarSafetyTest, common.Dri class TestHyundaiSafetyAltLimits(TestHyundaiSafety): MAX_RATE_UP = 2 MAX_RATE_DOWN = 3 - MAX_TORQUE = 270 + MAX_TORQUE_LOOKUP = [0], [270] def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") @@ -129,7 +127,7 @@ class TestHyundaiSafetyAltLimits(TestHyundaiSafety): class TestHyundaiSafetyAltLimits2(TestHyundaiSafety): MAX_RATE_UP = 2 MAX_RATE_DOWN = 3 - MAX_TORQUE = 170 + MAX_TORQUE_LOOKUP = [0], [170] def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py index b31c8c3e..2fdea0d6 100755 --- a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py @@ -30,10 +30,9 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaCarSafetyTest, common. MAX_RATE_UP = 2 MAX_RATE_DOWN = 3 - MAX_TORQUE = 270 + MAX_TORQUE_LOOKUP = [0], [270] MAX_RT_DELTA = 112 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 250 DRIVER_TORQUE_FACTOR = 2 @@ -41,7 +40,6 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaCarSafetyTest, common. # Safety around steering req bit MIN_VALID_STEERING_FRAMES = 89 MAX_INVALID_STEERING_FRAMES = 2 - MIN_VALID_STEERING_RT_INTERVAL = 810000 # a ~10% buffer, can send steer up to 110Hz PT_BUS = 0 SCC_BUS = 2 diff --git a/opendbc_repo/opendbc/safety/tests/test_mazda.py b/opendbc_repo/opendbc/safety/tests/test_mazda.py index e60248d2..67a22d49 100755 --- a/opendbc_repo/opendbc/safety/tests/test_mazda.py +++ b/opendbc_repo/opendbc/safety/tests/test_mazda.py @@ -16,10 +16,9 @@ class TestMazdaSafety(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafe MAX_RATE_UP = 10 MAX_RATE_DOWN = 25 - MAX_TORQUE = 800 + MAX_TORQUE_LOOKUP = [0], [800] MAX_RT_DELTA = 300 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 15 DRIVER_TORQUE_FACTOR = 1 diff --git a/opendbc_repo/opendbc/safety/tests/test_rivian.py b/opendbc_repo/opendbc/safety/tests/test_rivian.py index a1b6b3e8..e3f88856 100755 --- a/opendbc_repo/opendbc/safety/tests/test_rivian.py +++ b/opendbc_repo/opendbc/safety/tests/test_rivian.py @@ -1,29 +1,51 @@ #!/usr/bin/env python3 import unittest +import numpy as np from opendbc.car.structs import CarParams from opendbc.safety.tests.libsafety import libsafety_py import opendbc.safety.tests.common as common from opendbc.safety.tests.common import CANPackerPanda from opendbc.car.rivian.values import RivianSafetyFlags +from opendbc.car.rivian.riviancan import checksum as _checksum -class TestRivianSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.LongitudinalAccelSafetyTest): +def checksum(msg): + addr, dat, bus = msg + ret = bytearray(dat) + + # ESP_Status + if addr == 0x208: + ret[0] = _checksum(ret[1:], 0x1D, 0xB1) + elif addr == 0x150: + ret[0] = _checksum(ret[1:], 0x1D, 0x9A) + + return addr, ret, bus + + +class TestRivianSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.LongitudinalAccelSafetyTest, + common.VehicleSpeedSafetyTest): TX_MSGS = [[0x120, 0], [0x321, 2], [0x162, 2]] RELAY_MALFUNCTION_ADDRS = {0: (0x120,)} FWD_BLACKLISTED_ADDRS = {0: [0x321, 0x162], 2: [0x120]} - MAX_TORQUE = 250 + MAX_TORQUE_LOOKUP = [9, 17], [350, 250] + DYNAMIC_MAX_TORQUE = True MAX_RATE_UP = 3 MAX_RATE_DOWN = 5 MAX_RT_DELTA = 125 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 100 DRIVER_TORQUE_FACTOR = 2 + # Max allowed delta between car speeds + MAX_SPEED_DELTA = 2.0 # m/s + + cnt_speed = 0 + cnt_speed_2 = 0 + def _torque_driver_msg(self, torque): values = {"EPAS_TorsionBarTorque": torque / 100.0} return self.packer.make_can_msg_panda("EPAS_SystemStatus", 0, values) @@ -32,26 +54,29 @@ class TestRivianSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteerin values = {"ACM_lkaStrToqReq": torque, "ACM_lkaActToi": steer_req} return self.packer.make_can_msg_panda("ACM_lkaHbaCmd", 0, values) - def _speed_msg(self, speed): - values = {"ESP_Status": speed * 3.6} - return self.packer.make_can_msg_panda("ESP_Vehicle_Speed", 0, values) + def _speed_msg(self, speed, quality_flag=True): + values = {"ESP_Vehicle_Speed": speed * 3.6, "ESP_Status_Counter": self.cnt_speed % 15, + "ESP_Vehicle_Speed_Q": 1 if quality_flag else 0} + self.__class__.cnt_speed += 1 + return self.packer.make_can_msg_panda("ESP_Status", 0, values, fix_checksum=checksum) + + def _speed_msg_2(self, speed, quality_flag=True): + return self._user_gas_msg(0, speed, quality_flag) def _user_brake_msg(self, brake): values = {"iBESP2_BrakePedalApplied": brake} return self.packer.make_can_msg_panda("iBESP2", 0, values) - def _user_gas_msg(self, gas): - values = {"VDM_AcceleratorPedalPosition": gas} - return self.packer.make_can_msg_panda("VDM_PropStatus", 0, values) + def _user_gas_msg(self, gas, speed=0, quality_flag=True): + values = {"VDM_AcceleratorPedalPosition": gas, "VDM_VehicleSpeed": speed * 3.6, + "VDM_PropStatus_Counter": self.cnt_speed_2 % 15, "VDM_VehicleSpeedQ": 1 if quality_flag else 0} + self.__class__.cnt_speed_2 += 1 + return self.packer.make_can_msg_panda("VDM_PropStatus", 0, values, fix_checksum=checksum) def _pcm_status_msg(self, enable): values = {"ACM_FeatureStatus": enable, "ACM_Unkown1": 1} return self.packer.make_can_msg_panda("ACM_Status", 2, values) - def _vehicle_moving_msg(self, speed: float): - values = {"ESP_Vehicle_Speed": speed} - return self.packer.make_can_msg_panda("ESP_Status", 0, values) - def _accel_msg(self, accel: float): values = {"ACM_AccelerationRequest": accel} return self.packer.make_can_msg_panda("ACM_longitudinalRequest", 0, values) @@ -67,6 +92,40 @@ class TestRivianSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueSteerin } self.assertTrue(self._tx(self.packer.make_can_msg_panda("SCCM_WheelTouch", 2, values))) + def test_rx_hook(self): + # checksum, counter, and quality flag checks + for quality_flag in (True, False): + for msg in ("speed", "speed_2"): + self.safety.set_controls_allowed(True) + # send multiple times to verify counter checks + for _ in range(10): + if msg == "speed": + to_push = self._speed_msg(0, quality_flag=quality_flag) + elif msg == "speed_2": + to_push = self._speed_msg_2(0, quality_flag=quality_flag) + + self.assertEqual(quality_flag, self._rx(to_push)) + self.assertEqual(quality_flag, self.safety.get_controls_allowed()) + + # Mess with checksum to make it fail + to_push[0].data[0] = 0xff + self.assertFalse(self._rx(to_push)) + self.assertFalse(self.safety.get_controls_allowed()) + + def test_rx_hook_speed_mismatch(self): + # TODO: this can be a common test w/ Ford + # Rivian has a dynamic max torque limit based on speed, so it checks two sources + for speed in np.arange(0, 40, 0.5): + for speed_delta in np.arange(-5, 5, 0.1): + speed_2 = round(max(speed + speed_delta, 0), 1) + # Set controls allowed in between rx since first message can reset it + self._rx(self._speed_msg(speed)) + self.safety.set_controls_allowed(True) + self._rx(self._speed_msg_2(speed_2)) + + within_delta = abs(speed - speed_2) <= self.MAX_SPEED_DELTA + self.assertEqual(self.safety.get_controls_allowed(), within_delta) + class TestRivianStockSafety(TestRivianSafetyBase): diff --git a/opendbc_repo/opendbc/safety/tests/test_subaru.py b/opendbc_repo/opendbc/safety/tests/test_subaru.py index 87277324..4936eae1 100755 --- a/opendbc_repo/opendbc/safety/tests/test_subaru.py +++ b/opendbc_repo/opendbc/safety/tests/test_subaru.py @@ -60,7 +60,6 @@ class TestSubaruSafetyBase(common.PandaCarSafetyTest): FWD_BLACKLISTED_ADDRS = fwd_blacklisted_addr() MAX_RT_DELTA = 940 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 60 DRIVER_TORQUE_FACTOR = 50 @@ -155,12 +154,12 @@ class TestSubaruLongitudinalSafetyBase(TestSubaruSafetyBase, common.Longitudinal class TestSubaruTorqueSafetyBase(TestSubaruSafetyBase, common.DriverTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest): MAX_RATE_UP = 50 MAX_RATE_DOWN = 70 - MAX_TORQUE = 2047 + MAX_TORQUE_LOOKUP = [0], [2047] # Safety around steering req bit MIN_VALID_STEERING_FRAMES = 7 MAX_INVALID_STEERING_FRAMES = 1 - MIN_VALID_STEERING_RT_INTERVAL = 144000 + STEER_STEP = 2 def _torque_cmd_msg(self, torque, steer_req=1): values = {"LKAS_Output": torque, "LKAS_Request": steer_req} @@ -178,7 +177,7 @@ class TestSubaruGen2TorqueSafetyBase(TestSubaruTorqueSafetyBase): MAX_RATE_UP = 40 MAX_RATE_DOWN = 40 - MAX_TORQUE = 1000 + MAX_TORQUE_LOOKUP = [0], [1000] class TestSubaruGen2TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruGen2TorqueSafetyBase): diff --git a/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py b/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py index b2f482bf..af2c6cec 100755 --- a/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py +++ b/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py @@ -17,10 +17,9 @@ class TestSubaruPreglobalSafety(common.PandaCarSafetyTest, common.DriverTorqueSt MAX_RATE_UP = 50 MAX_RATE_DOWN = 70 - MAX_TORQUE = 2047 + MAX_TORQUE_LOOKUP = [0], [2047] MAX_RT_DELTA = 940 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 75 DRIVER_TORQUE_FACTOR = 10 diff --git a/opendbc_repo/opendbc/safety/tests/test_toyota.py b/opendbc_repo/opendbc/safety/tests/test_toyota.py index dcca1eb8..a119615c 100755 --- a/opendbc_repo/opendbc/safety/tests/test_toyota.py +++ b/opendbc_repo/opendbc/safety/tests/test_toyota.py @@ -124,16 +124,14 @@ class TestToyotaSafetyTorque(TestToyotaSafetyBase, common.MotorTorqueSteeringSaf MAX_RATE_UP = 15 MAX_RATE_DOWN = 25 - MAX_TORQUE = 1500 + MAX_TORQUE_LOOKUP = [0], [1500] MAX_RT_DELTA = 450 - RT_INTERVAL = 250000 MAX_TORQUE_ERROR = 350 TORQUE_MEAS_TOLERANCE = 1 # toyota safety adds one to be conservative for rounding # Safety around steering req bit MIN_VALID_STEERING_FRAMES = 18 MAX_INVALID_STEERING_FRAMES = 1 - MIN_VALID_STEERING_RT_INTERVAL = 170000 # a ~10% buffer, can send steer up to 110Hz def setUp(self): self.packer = CANPackerPanda("toyota_nodsu_pt_generated") diff --git a/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py b/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py index de47b5ea..83e45f1f 100755 --- a/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py +++ b/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py @@ -28,9 +28,8 @@ class TestVolkswagenMqbSafetyBase(common.PandaCarSafetyTest, common.DriverTorque MAX_RATE_UP = 4 MAX_RATE_DOWN = 10 - MAX_TORQUE = 300 + MAX_TORQUE_LOOKUP = [0], [300] MAX_RT_DELTA = 75 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 80 DRIVER_TORQUE_FACTOR = 3 @@ -133,7 +132,7 @@ class TestVolkswagenMqbStockSafety(TestVolkswagenMqbSafetyBase): FWD_BLACKLISTED_ADDRS = {0: [MSG_LH_EPS_03], 2: [MSG_HCA_01, MSG_LDW_02]} def setUp(self): - self.packer = CANPackerPanda("vw_mqb_2010") + self.packer = CANPackerPanda("vw_mqb") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagen, 0) self.safety.init_tests() @@ -154,7 +153,7 @@ class TestVolkswagenMqbLongSafety(TestVolkswagenMqbSafetyBase): INACTIVE_ACCEL = 3.01 def setUp(self): - self.packer = CANPackerPanda("vw_mqb_2010") + self.packer = CANPackerPanda("vw_mqb") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagen, VolkswagenSafetyFlags.LONG_CONTROL) self.safety.init_tests() diff --git a/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py b/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py index cc9e87df..8f6de057 100755 --- a/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py +++ b/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py @@ -26,9 +26,8 @@ class TestVolkswagenPqSafetyBase(common.PandaCarSafetyTest, common.DriverTorqueS MAX_RATE_UP = 6 MAX_RATE_DOWN = 10 - MAX_TORQUE = 300 + MAX_TORQUE_LOOKUP = [0], [300] MAX_RT_DELTA = 113 - RT_INTERVAL = 250000 DRIVER_TORQUE_ALLOWANCE = 80 DRIVER_TORQUE_FACTOR = 3 @@ -116,7 +115,7 @@ class TestVolkswagenPqStockSafety(TestVolkswagenPqSafetyBase): FWD_BLACKLISTED_ADDRS = {2: [MSG_HCA_1, MSG_LDW_1]} def setUp(self): - self.packer = CANPackerPanda("vw_golf_mk4") + self.packer = CANPackerPanda("vw_pq") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenPq, 0) self.safety.init_tests() @@ -137,7 +136,7 @@ class TestVolkswagenPqLongSafety(TestVolkswagenPqSafetyBase, common.Longitudinal INACTIVE_ACCEL = 3.01 def setUp(self): - self.packer = CANPackerPanda("vw_golf_mk4") + self.packer = CANPackerPanda("vw_pq") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.volkswagenPq, VolkswagenSafetyFlags.LONG_CONTROL) self.safety.init_tests()