from collections import defaultdict, namedtuple from dataclasses import dataclass, field from enum import Enum, IntFlag, StrEnum from iqdbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, CanBusBase, CarSpecs, DbcDict, DT_CTRL, PlatformConfig, Platforms, structs, uds from iqdbc.car.lateral import CurvatureSteeringLimits from iqdbc.can import CANDefine from iqdbc.car.common.conversions import Conversions as CV from iqdbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column from iqdbc.car.lateral import AngleSteeringLimits, ISO_LATERAL_ACCEL, ISO_LATERAL_JERK from iqdbc.car.fw_query_definitions import EcuAddrSubAddr, FwQueryConfig, Request, p16 from iqdbc.car.vin import Vin Ecu = structs.CarParams.Ecu NetworkLocation = structs.CarParams.NetworkLocation TransmissionType = structs.CarParams.TransmissionType DashcamOnlyReason = structs.CarParams.DashcamOnlyReason GearShifter = structs.CarState.GearShifter Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) class CanBus(CanBusBase): def __init__(self, CP=None, fingerprint=None) -> None: super().__init__(CP, fingerprint) self.offset = 0 @property def pt(self) -> int: # ADAS / Extended CAN, gateway side of the relay return 0 @property def aux(self) -> int: # NetworkLocation.fwdCamera: radar-camera object fusion CAN # NetworkLocation.gateway: powertrain CAN return 1 @property def powertrain(self) -> int: return 1 @property def main(self) -> int: return 1 @property def cam(self) -> int: # ADAS / Extended CAN, camera side of the relay return 2 @property def ext(self) -> int: # ADAS / Extended CAN, side of the relay with the ACC radar return 2 # Extra Tolerances For Road Variance AVERAGE_ROAD_ROLL = 0.06 PQ_STOPPING_SPEED = 1.5 * CV.KPH_TO_MS PASSAT_B7_STOPPING_SPEED = 0.55 * CV.KPH_TO_MS PASSAT_B7_STOP_ACCEL = -0.55 class CarControllerParams: ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits( # Max Steering Angle Allowed 500, # deg # Volkswagen uses a vehicle model ([], []), ([], []), # Vehicle Model Angle Limits # Add extra tolerance for average banked road since safety doesn't have the roll calculation MAX_LATERAL_ACCEL=ISO_LATERAL_ACCEL + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL), # ~3.6 m/s^2 MAX_LATERAL_JERK=ISO_LATERAL_JERK + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL), # ~3.6 m/s^3 # Limit Angle Rate to both prevent a openpilot fault and for low speed comfort (~12 mph rate down to 0 mph) MAX_ANGLE_RATE=5, # deg/20ms frame ) STEER_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz AEB_CONTROL_STEP = 2 # ACC_10 frequency 50Hz AEB_HUD_STEP = 20 # ACC_15 frequency 5Hz VW_LOW_SPEED_STATE_SPEED = 0.5 # m/s; below this, always force starting or stopping in MQB legacy long SNG_HANDOFF_SPEED = 5.0 * CV.KPH_TO_MS SNG_HOLD_DECEL_MAX = -0.1 # Documented lateral limits: 3.00 Nm max, rate of change 5.00 Nm/sec. # MQB vs PQ maximums are shared, but rate-of-change limited differently # based on safety requirements driven by lateral accel testing. STEER_MAX = 300 # Max heading control assist torque 3.00 Nm STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily STEER_DRIVER_FACTOR = 1 # from dbc STEER_TIME_MAX = 360 # Max time that EPS allows uninterrupted HCA steering control STEER_TIME_BM = STEER_TIME_MAX - 120 # Attempts to mitigate the EPS max steer timer begin STEER_TIME_ALERT = STEER_TIME_MAX - 10 # If mitigation fails, time to soft disengage before EPS timer expires STEER_LOW_TORQUE = int(STEER_MAX * 0.20) # Steer timer mitigation performed when torque output under 20% STEER_TIME_LOW_TORQUE = 0.5 # Wait for this duration of STEER_LOW_TORQUE to begin mitigation STEER_TIME_STUCK_TORQUE = 1.9 # EPS limits same torque to 6 seconds, reset timer 3x within that period STEER_TIME_RESET = 1.1 # Duration of HCA disable needed for effective EPS timer reset' IQ_PQ_UNAVAILABLE_HUD_FRAMES = 25 GRA_CANCEL_TAP_ON = 10 # Stock GRA frames to hold Abbrechen, roughly a human button tap GRA_CANCEL_TAP_OFF = 20 # Stock GRA frames to release between taps GRA_CANCEL_MAX_TAPS = 3 # PQ engine faults its GRA input if Abbrechen is held indefinitely DEFAULT_MIN_STEER_SPEED = 0.4 # m/s, newer EPS racks fault below this speed, don't show a low speed alert ACCEL_MAX = 2.0 # 2.0 m/s max acceleration ACCEL_MIN = -3.5 # 3.5 m/s max deceleration VW_LOW_SPEED_STATE_SPEED = 0.5 # Always send either starting or stopping below this speed def __init__(self, CP): can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt]) self.STEER_TIME_RESET = self.__class__.STEER_TIME_RESET if CP.flags & VolkswagenFlags.PQ: self.LDW_STEP = 5 # LDW_1 message frequency 20Hz self.ACC_HUD_STEP = 4 # ACC_GRA_Anzeige frequency 25Hz self.STEER_DRIVER_ALLOWANCE = 80 self.STEER_DELTA_UP = 6 self.STEER_DELTA_DOWN = 10 if CP.transmissionType == TransmissionType.automatic: self.shifter_values = can_define.dv["Getriebe_1"]["GE1_Wahl_Pos"] self.hca_status_values = can_define.dv["Lenkhilfe_2"]["LH2_Sta_HCA"] self.BUTTONS = [ Button(structs.CarState.ButtonEvent.Type.setCruise, "GRA_Neu", "GRA_Neu_Setzen", [1]), Button(structs.CarState.ButtonEvent.Type.resumeCruise, "GRA_Neu", "GRA_Recall", [1]), Button(structs.CarState.ButtonEvent.Type.accelCruise, "GRA_Neu", "GRA_Up_kurz", [1]), Button(structs.CarState.ButtonEvent.Type.decelCruise, "GRA_Neu", "GRA_Down_kurz", [1]), Button(structs.CarState.ButtonEvent.Type.cancel, "GRA_Neu", "GRA_Abbrechen", [1]), Button(structs.CarState.ButtonEvent.Type.gapAdjustCruise, "GRA_Neu", "GRA_Zeitluecke", [1, 2, 3]), ] self.LDW_MESSAGES = { "none": 0, # Nothing to display "laneAssistUnavail": 1, # "Lane Assist currently not available." "laneAssistUnavailSysError": 2, # "Lane Assist system error" "laneAssistUnavailNoSensorView": 3, # "Lane Assist not available. No sensor view." "laneAssistTakeOver": 4, # "Lane Assist: Please Take Over Steering" "laneAssistDeactivTrailer": 5, # "Lane Assist: no function with trailer" } elif CP.flags & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO): self.LDW_STEP = 10 self.ACC_HUD_STEP = 6 self.STEER_DRIVER_ALLOWANCE = 80 self.STEER_DRIVER_MAX = 300 self.STEERING_POWER_MAX = 90 self.STEERING_POWER_MIN = 4 self.STEERING_POWER_STEP = 2 self.CURVATURE_HANDOFF_RATE = 0.20 self.CURVATURE_PID: structs.CarParams.LateralPIDTuning = structs.CarParams.LateralPIDTuning( kpBP=[10., 40.], kiBP=[10., 40.], kf=1., kpV=[0., 1.45], kiV=[0., 0.12], ) self.CURVATURE_LIMITS: CurvatureSteeringLimits = CurvatureSteeringLimits( 0.195, ) if CP.flags & VolkswagenFlags.ALT_GEAR: self.shifter_values = can_define.dv["Gateway_73"]["GE_Fahrstufe"] else: self.shifter_values = can_define.dv["Getriebe_11"]["GE_Fahrstufe"] self.hca_status_values = can_define.dv["QFK_01"]["LatCon_HCA_Status"] BASE_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.gapAdjustCruise, "GRA_ACC_01", "GRA_Verstellung_Zeitluecke", [1, 2, 3]), ] self.BUTTONS = BASE_BUTTONS + [ Button(structs.CarState.ButtonEvent.Type.cancel, "GRA_ACC_01", "GRA_Hauptschalter", [1]), ] self.BUTTONS_ALT = BASE_BUTTONS + [ Button(structs.CarState.ButtonEvent.Type.cancel, "GRA_ACC_01", "GRA_Abbrechen", [1]), ] self.LDW_MESSAGES = { "none": 0, "laneAssistTakeOverUrgent": 4, "laneAssistTakeOver": 8, } self.LDW_SOUNDS = { "None": 0, "Chime": 1, "Beep": 2, } else: self.LDW_STEP = 10 # LDW_02 message frequency 10Hz self.ACC_HUD_STEP = 6 # ACC_02 message frequency 16Hz self.hca_status_values = can_define.dv["LH_EPS_03"]["EPS_HCA_Status"] if CP.flags & VolkswagenFlags.MLB: self.STEER_DRIVER_ALLOWANCE = 80 # Driver intervention threshold 0.8 Nm self.STEER_DELTA_UP = 9 # Max HCA reached in 0.66s (STEER_MAX / (50Hz * 0.66)) self.STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60)) self.ACC_HUD_TEXT_STEP = int(2.0 / DT_CTRL) # ACC_02 primary display text dwell time if CP.carFingerprint == CAR.PORSCHE_MACAN_MK1: self.shifter_values = can_define.dv["Getriebe_03"]["GE_Waehlhebel"] else: self.shifter_values = None self.BUTTONS = [ Button(structs.CarState.ButtonEvent.Type.setCruise, "LS_01", "LS_Tip_Setzen", [1]), Button(structs.CarState.ButtonEvent.Type.resumeCruise, "LS_01", "LS_Tip_Wiederaufnahme", [1]), Button(structs.CarState.ButtonEvent.Type.accelCruise, "LS_01", "LS_Tip_Hoch", [1]), Button(structs.CarState.ButtonEvent.Type.decelCruise, "LS_01", "LS_Tip_Runter", [1]), Button(structs.CarState.ButtonEvent.Type.cancel, "LS_01", "LS_Abbrechen", [1]), Button(structs.CarState.ButtonEvent.Type.gapAdjustCruise, "LS_01", "LS_Verstellung_Zeitluecke", [1, 2, 3]), ] # ACC_02.ACC_Texte_Primaeranz, primary ACC display text at the bottom of the cluster self.ACC_HUD_TEXTS = { "none": 0, "setSpeed": 21, } self.ACC_HUD_TEXT_DISTANCE = {1: 2, 2: 3, 3: 4, 4: 5} # follow distance bars to display text else: self.STEER_DRIVER_ALLOWANCE = 80 self.STEER_DELTA_UP = 4 # Max HCA reached in 1.50s (STEER_MAX / (50Hz * 1.50)) self.STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60)) if CP.transmissionType == TransmissionType.automatic: self.shifter_values = can_define.dv["Gateway_73"]["GE_Fahrstufe"] elif CP.transmissionType == TransmissionType.direct: self.shifter_values = can_define.dv["Motor_EV_01"]["MO_Waehlpos"] 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_Abbrechen", [1]), Button(structs.CarState.ButtonEvent.Type.gapAdjustCruise, "GRA_ACC_01", "GRA_Verstellung_Zeitluecke", [1, 2, 3]), ] self.LDW_MESSAGES = { "none": 0, # Nothing to display "laneAssistUnavailChime": 1, # "Lane Assist currently not available." with chime "laneAssistUnavailNoSensorChime": 3, # "Lane Assist not available. No sensor view." with chime "laneAssistTakeOverUrgent": 4, # "Lane Assist: Please Take Over Steering" with urgent beep "emergencyAssistUrgent": 6, # "Emergency Assist: Please Take Over Steering" with urgent beep "laneAssistTakeOverChime": 7, # "Lane Assist: Please Take Over Steering" with chime "laneAssistTakeOver": 8, # "Lane Assist: Please Take Over Steering" silent "emergencyAssistChangingLanes": 9, # "Emergency Assist: Changing lanes..." with urgent beep "laneAssistDeactivated": 10, # "Lane Assist deactivated." silent with persistent icon afterward } HOLD_MAX_FRAMES = 60 HOLD_TORQUE_DEADBAND_NM = 20 HOLD_ACCEL_KI = 0.00002 class WMI(StrEnum): VOLKSWAGEN_USA_SUV = "1V2" VOLKSWAGEN_USA_CAR = "1VW" VOLKSWAGEN_MEXICO_SUV = "3VV" VOLKSWAGEN_MEXICO_CAR = "3VW" VOLKSWAGEN_ARGENTINA = "8AW" VOLKSWAGEN_BRASIL = "9BW" SAIC_VOLKSWAGEN = "LSV" SKODA = "TMB" SEAT = "VSS" AUDI_EUROPE_MPV = "WA1" AUDI_GERMANY_CAR = "WAU" MAN = "WMA" PORSCHE_SUV = "WP1" AUDI_SPORT = "WUA" VOLKSWAGEN_COMMERCIAL = "WV1" VOLKSWAGEN_COMMERCIAL_BUS_VAN = "WV2" VOLKSWAGEN_EUROPE_SUV = "WVG" VOLKSWAGEN_EUROPE_CAR = "WVW" VOLKSWAGEN_GROUP_RUS = "XW8" class VolkswagenSafetyFlags(IntFlag): LONG_CONTROL = 1 ALT_CRC_VARIANT_1 = 2 NO_GAS_OFFSET = 4 ALLOW_LONG_ACCEL_WITH_GAS_PRESSED = 8 DISABLE_RADAR = 16 PQ_ALC_MODULE = 32 PQ_LOWLINE = 64 PQ_NO_CAM_BUS = 128 PQ_ACC_FTS_EPB = 256 PQ_SNG_ECD = 512 MLB_NO_ECAN = 1024 class VolkswagenFlags(IntFlag): # Detected flags STOCK_HCA_PRESENT = 1 KOMBI_PRESENT = 4 # Static flags PQ = 2 MLB = 8 MEB = 256 MEB_GEN2 = 512 MQB_EVO = 1024 # Detected flags (MEB/MQBevo) STOCK_KLR_PRESENT = 2048 STOCK_PSD_PRESENT = 4096 STOCK_PSD_06_PRESENT = 8192 STOCK_DIAGNOSE_01_PRESENT = 16384 ALT_GEAR = 32768 DISABLE_RADAR = 65536 class VolkswagenFlagsIQ(IntFlag): IQ_CC_ONLY = 1 << 5 # CC only mode with radar (has AEB) IQ_CC_ONLY_NO_RADAR = 1 << 6 # CC only mode without radar IQ_LVBS_ALC_MODULE = 1 << 7 # IQ.Lvbs VW ALC hardware module present / intended active path IQ_PQ_LOWLINE = 1 << 17 # Non-ECAN lateral-only PQ: bus 0 dead, TX on bus 1 (ptCAN) IQ_PQ_ACC_FTS_EPB = 1 << 18 # B7 TRW450: ACC FtS + EPB hold, Motor_1 resume spoof on bus 1 IQ_PQ_SNG_ECD = 1 << 19 IQ_PQ_TIMEBOMB = 1 << 20 IQ_MLB_NO_ECAN = 1 << 21 IQ_MLB_NO_HCA_EPS = 1 << 22 RADAR_DISABLE_STATE = {"error": False} MLB_MSG_LH_EPS_03 = 0x9F MLB_MSG_GETRIEBE_01 = 0x82 MLB_MSG_GETRIEBE_02 = 0x83 MLB_MSG_GETRIEBE_03 = 0x102 MLB_MSG_GETRIEBE_04 = 0x441 MLB_MSG_ACC_01 = 0x109 MLB_MSG_ACC_05 = 0x10D MLB_MSG_ACC_02 = 0x30C MLB_MSG_ACC_10 = 0x117 MLB_MSG_GATEWAY_05 = 0x39C MLB_ACC_COORDINATOR_MSGS = (MLB_MSG_ACC_01, MLB_MSG_ACC_05, MLB_MSG_ACC_02) MLB_GEARBOX_MSGS = (MLB_MSG_GETRIEBE_01, MLB_MSG_GETRIEBE_02, MLB_MSG_GETRIEBE_03, MLB_MSG_GETRIEBE_04) @dataclass class VolkswagenMLBPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_mlb'}) chassis_codes: set[str] = field(default_factory=set) wmis: set[WMI] = field(default_factory=set) def init(self): self.flags |= VolkswagenFlags.MLB @dataclass class VolkswagenMQBPlatformConfig(PlatformConfig): 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) model_years: set[str] = field(default_factory=set) @dataclass class VolkswagenPQPlatformConfig(VolkswagenMQBPlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_pq'}) def init(self): self.flags |= VolkswagenFlags.PQ @dataclass class VolkswagenMEBPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_meb', Bus.radar: 'vw_meb'}) chassis_codes: set[str] = field(default_factory=set) wmis: set[WMI] = field(default_factory=set) model_years: set[str] = field(default_factory=set) def init(self): self.flags |= VolkswagenFlags.MEB if self.flags & VolkswagenFlags.MEB_GEN2: self.dbc_dict = {Bus.pt: 'vw_meb_2024', Bus.radar: 'vw_meb_2024'} @dataclass class VolkswagenMQBevoPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: {Bus.pt: 'vw_mqbevo', Bus.radar: 'vw_mqbevo'}) chassis_codes: set[str] = field(default_factory=set) wmis: set[WMI] = field(default_factory=set) def init(self): self.flags |= VolkswagenFlags.MQB_EVO @dataclass(frozen=True, kw_only=True) class VolkswagenCarSpecs(CarSpecs): centerToFrontRatio: float = 0.45 steerRatio: float = 18.4 minSteerSpeed: float = CarControllerParams.DEFAULT_MIN_STEER_SPEED # FIXME: nuke this class Footnote(Enum): KAMIQ = CarFootnote( "Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.", Column.MODEL) PASSAT = CarFootnote( "Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.", Column.MODEL) SKODA_HEATED_WINDSHIELD = CarFootnote( "Some Škoda vehicles are equipped with heated windshields, which are known " + "to block GPS signal needed for some comma four functionality.", Column.MODEL) VW_EXP_LONG = CarFootnote( "Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness " + "are limited to using stock ACC.", Column.LONGITUDINAL, docs_only=True) VW_MQB_A0 = CarFootnote( "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) @dataclass class VWCarDocs(CarDocs): package: str = "Adaptive Cruise Control (ACC) & Lane Assist" car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.vw_j533])) def init_make(self, CP: structs.CarParams): self.footnotes.append(Footnote.VW_EXP_LONG) if "SKODA" in CP.carFingerprint: self.footnotes.append(Footnote.SKODA_HEATED_WINDSHIELD) if abs(CP.minSteerSpeed - CarControllerParams.DEFAULT_MIN_STEER_SPEED) < 1e-3: self.min_steer_speed = 0 # Check the 7th and 8th characters of the VIN before adding a new CAR. If the # chassis code is already listed below, don't add a new CAR, just add to the # FW_VERSIONS for that existing CAR. class CAR(Platforms): config: VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig | VolkswagenMLBPlatformConfig | VolkswagenMEBPlatformConfig | VolkswagenMQBevoPlatformConfig VOLKSWAGEN_ARTEON_MK1 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Arteon 2018-23", video="https://youtu.be/FAomFKPFlDA"), VWCarDocs("Volkswagen Arteon R 2020-23", video="https://youtu.be/FAomFKPFlDA"), VWCarDocs("Volkswagen Arteon eHybrid 2020-23", video="https://youtu.be/FAomFKPFlDA"), VWCarDocs("Volkswagen Arteon Shooting Brake 2020-23", video="https://youtu.be/FAomFKPFlDA"), VWCarDocs("Volkswagen CC 2018-22", video="https://youtu.be/FAomFKPFlDA"), ], VolkswagenCarSpecs(mass=1733, wheelbase=2.84), chassis_codes={"AN", "3H"}, wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) VOLKSWAGEN_ATLAS_MK1 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Atlas 2018-23"), VWCarDocs("Volkswagen Atlas Cross Sport 2020-22"), VWCarDocs("Volkswagen Teramont 2018-22"), VWCarDocs("Volkswagen Teramont Cross Sport 2021-22"), VWCarDocs("Volkswagen Teramont X 2021-22"), ], VolkswagenCarSpecs(mass=2011, wheelbase=2.98), chassis_codes={"CA"}, wmis={WMI.VOLKSWAGEN_USA_SUV, WMI.VOLKSWAGEN_EUROPE_SUV}, ) VOLKSWAGEN_CADDY_MK3 = VolkswagenPQPlatformConfig( [ VWCarDocs("Volkswagen Caddy 2019"), VWCarDocs("Volkswagen Caddy Maxi 2019"), ], VolkswagenCarSpecs(mass=1613, wheelbase=2.6), chassis_codes={"2K"}, wmis={WMI.VOLKSWAGEN_COMMERCIAL_BUS_VAN}, ) VOLKSWAGEN_CRAFTER_MK2 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Crafter 2017-24", video="https://youtu.be/4100gLeabmo"), VWCarDocs("Volkswagen e-Crafter 2018-24", video="https://youtu.be/4100gLeabmo"), VWCarDocs("Volkswagen Grand California 2019-24", video="https://youtu.be/4100gLeabmo"), VWCarDocs("MAN TGE 2017-24", video="https://youtu.be/4100gLeabmo"), VWCarDocs("MAN eTGE 2020-24", video="https://youtu.be/4100gLeabmo"), ], VolkswagenCarSpecs(mass=2100, wheelbase=3.64, minSteerSpeed=50 * CV.KPH_TO_MS), chassis_codes={"SY", "SZ", "UY", "UZ"}, wmis={WMI.VOLKSWAGEN_COMMERCIAL, WMI.MAN}, ) VOLKSWAGEN_GOLF_MK7 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen e-Golf 2014-20"), VWCarDocs("Volkswagen Golf (Mk7) 2015-20", auto_resume=False), VWCarDocs("Volkswagen Golf Alltrack 2015-19", auto_resume=False), VWCarDocs("Volkswagen Golf GTD 2015-20"), VWCarDocs("Volkswagen Golf GTE 2015-20"), VWCarDocs("Volkswagen Golf GTI 2015-21", auto_resume=False), VWCarDocs("Volkswagen Golf R 2015-19"), VWCarDocs("Volkswagen Golf SportsVan 2015-20"), ], VolkswagenCarSpecs(mass=1397, wheelbase=2.62, steerRatio=17.0), chassis_codes={"5G", "AU", "BA", "BE"}, wmis={WMI.VOLKSWAGEN_MEXICO_CAR, WMI.VOLKSWAGEN_EUROPE_CAR}, ) VOLKSWAGEN_GOLF_MK8 = VolkswagenMQBevoPlatformConfig( [VWCarDocs("Volkswagen Golf (Mk8) 2020-25")], VolkswagenCarSpecs(mass=1397, wheelbase=2.62), chassis_codes={"CD"}, wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) VOLKSWAGEN_ID3_MK1 = VolkswagenMEBPlatformConfig( [ VWCarDocs("Volkswagen ID.3 2020-23"), VWCarDocs("Volkswagen MEB Gen 1 2020-23"), ], VolkswagenCarSpecs(mass=1935, wheelbase=2.77), chassis_codes={"E1"}, wmis={WMI.VOLKSWAGEN_USA_SUV, WMI.VOLKSWAGEN_EUROPE_CAR}, model_years={"L", "M", "N", "P"}, ) VOLKSWAGEN_ID3_MK2 = VolkswagenMEBPlatformConfig( [ VWCarDocs("Volkswagen ID.3 2024-25"), VWCarDocs("Volkswagen MEB Gen 2 2024-25"), ], VolkswagenCarSpecs(mass=1935, wheelbase=2.77), chassis_codes={"E1"}, wmis={WMI.VOLKSWAGEN_USA_SUV, WMI.VOLKSWAGEN_EUROPE_CAR}, model_years={"R", "S"}, flags=VolkswagenFlags.MEB_GEN2, ) VOLKSWAGEN_ID4_MK1 = VolkswagenMEBPlatformConfig( [ VWCarDocs("Volkswagen ID.4 2021-23"), VWCarDocs("Volkswagen ID.5 2022-23"), ], VolkswagenCarSpecs(mass=2224, wheelbase=2.77), chassis_codes={"E2"}, wmis={WMI.VOLKSWAGEN_USA_SUV, WMI.VOLKSWAGEN_EUROPE_CAR, WMI.VOLKSWAGEN_EUROPE_SUV}, ) VOLKSWAGEN_ID4_MK2 = VolkswagenMEBPlatformConfig( [VWCarDocs("Volkswagen ID.4 2024-25")], VolkswagenCarSpecs(mass=2224, wheelbase=2.77), chassis_codes={"E8"}, wmis={WMI.VOLKSWAGEN_USA_SUV, WMI.VOLKSWAGEN_EUROPE_CAR, WMI.VOLKSWAGEN_EUROPE_SUV}, flags=VolkswagenFlags.MEB_GEN2, ) VOLKSWAGEN_JETTA_MK6 = VolkswagenPQPlatformConfig( [VWCarDocs("Volkswagen Jetta 2015-18")], VolkswagenCarSpecs(mass=1518, wheelbase=2.65), chassis_codes={"5K", "AJ"}, wmis={WMI.VOLKSWAGEN_MEXICO_CAR}, ) VOLKSWAGEN_JETTA_MK7 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Jetta 2019-23"), VWCarDocs("Volkswagen Jetta GLI 2021-23"), ], VolkswagenCarSpecs(mass=1328, wheelbase=2.71), chassis_codes={"BU"}, wmis={WMI.VOLKSWAGEN_MEXICO_CAR, WMI.VOLKSWAGEN_EUROPE_CAR}, ) VOLKSWAGEN_PASSAT_MK8 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]), VWCarDocs("Volkswagen Passat Alltrack 2015-22"), VWCarDocs("Volkswagen Passat GTE 2015-22"), ], VolkswagenCarSpecs(mass=1551, wheelbase=2.79), chassis_codes={"3C", "3G"}, wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, model_years={"F", "G", "H", "J", "K", "L", "M", "N"}, # 2015-2022 ) VOLKSWAGEN_PASSAT_MK7 = VolkswagenPQPlatformConfig( [VWCarDocs("Volkswagen Passat 2.0 TDI 2014")], VolkswagenCarSpecs(mass=1836, wheelbase=2.70, steerRatio=13.0, minSteerSpeed=31 * CV.KPH_TO_MS), chassis_codes={"3C"}, wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, model_years={"E"}, # 2014 ) VOLKSWAGEN_PASSAT_NMS = VolkswagenPQPlatformConfig( [VWCarDocs("Volkswagen Passat NMS 2015-17")], VolkswagenCarSpecs(mass=1503, wheelbase=2.80), chassis_codes={"A3"}, wmis={WMI.VOLKSWAGEN_USA_CAR}, # NMS and NMS+ share chassis code A3; disambiguate by model year model_years={"F", "G", "H"}, # 2015-2017 ) VOLKSWAGEN_PASSAT_NMS_PLUS = VolkswagenPQPlatformConfig( [VWCarDocs("Volkswagen Passat NMS 2018-22")], VolkswagenCarSpecs(mass=1503, wheelbase=2.80, minEnableSpeed=20 * CV.KPH_TO_MS), chassis_codes={"A3"}, wmis={WMI.VOLKSWAGEN_USA_CAR}, model_years={"J", "K", "L", "M", "N"}, # 2018-2022 ) VOLKSWAGEN_PASSAT_B7 = VolkswagenPQPlatformConfig( [VWCarDocs("Volkswagen Passat B7 2011-15")], VolkswagenCarSpecs(mass=1503, wheelbase=2.712, steerRatio=16.4, minSteerSpeed=0), chassis_codes={"A3"}, wmis={WMI.VOLKSWAGEN_USA_CAR}, ) VOLKSWAGEN_POLO_MK6 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Polo 2018-23", footnotes=[Footnote.VW_MQB_A0]), VWCarDocs("Volkswagen Polo GTI 2018-23", footnotes=[Footnote.VW_MQB_A0]), ], VolkswagenCarSpecs(mass=1230, wheelbase=2.55), chassis_codes={"AW"}, wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) VOLKSWAGEN_SHARAN_MK2 = VolkswagenPQPlatformConfig( [ VWCarDocs("Volkswagen Sharan 2018-22"), ], VolkswagenCarSpecs(mass=1639, wheelbase=2.92), chassis_codes={"7N"}, wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) SEAT_ALHAMBRA_MK1 = VolkswagenPQPlatformConfig( [ VWCarDocs("SEAT Alhambra 2018-20"), ], VolkswagenCarSpecs(mass=1639, wheelbase=2.92, minSteerSpeed=50 * CV.KPH_TO_MS), chassis_codes={"7N"}, wmis={WMI.SEAT}, ) VOLKSWAGEN_TAOS_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Volkswagen Taos 2022-24")], VolkswagenCarSpecs(mass=1498, wheelbase=2.69), chassis_codes={"B2"}, wmis={WMI.VOLKSWAGEN_MEXICO_SUV, WMI.VOLKSWAGEN_ARGENTINA}, ) VOLKSWAGEN_TCROSS_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0])], VolkswagenCarSpecs(mass=1150, wheelbase=2.60), chassis_codes={"C1"}, wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, ) VOLKSWAGEN_TIGUAN_MK2 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Tiguan 2018-24"), VWCarDocs("Volkswagen Tiguan eHybrid 2021-23"), ], VolkswagenCarSpecs(mass=1715, wheelbase=2.74), chassis_codes={"5N", "AD", "AX", "BW"}, wmis={WMI.VOLKSWAGEN_EUROPE_SUV, WMI.VOLKSWAGEN_MEXICO_SUV}, ) VOLKSWAGEN_TOURAN_MK2 = VolkswagenMQBPlatformConfig( [VWCarDocs("Volkswagen Touran 2016-23")], VolkswagenCarSpecs(mass=1516, wheelbase=2.79), chassis_codes={"1T"}, wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, ) VOLKSWAGEN_TRANSPORTER_T61 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Caravelle 2020"), VWCarDocs("Volkswagen California 2021-23"), ], VolkswagenCarSpecs(mass=1926, wheelbase=3.00, minSteerSpeed=14.0), chassis_codes={"7H", "7L"}, wmis={WMI.VOLKSWAGEN_COMMERCIAL_BUS_VAN}, ) VOLKSWAGEN_TROC_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Volkswagen T-Roc 2018-23")], VolkswagenCarSpecs(mass=1413, wheelbase=2.63), chassis_codes={"A1"}, wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, ) AUDI_A3_MK3 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Audi A3 2014-19"), VWCarDocs("Audi A3 Sportback e-tron 2017-18"), VWCarDocs("Audi RS3 2018"), VWCarDocs("Audi S3 2015-17"), ], VolkswagenCarSpecs(mass=1335, wheelbase=2.61), chassis_codes={"8V", "FF"}, wmis={WMI.AUDI_GERMANY_CAR, WMI.AUDI_SPORT}, ) AUDI_A4_MK4 = VolkswagenMLBPlatformConfig( [VWCarDocs("Audi A4 2013-16", package="Cruise Control")], VolkswagenCarSpecs(mass=1610, wheelbase=2.81, steerRatio=15.9), chassis_codes={"8K", "FL"}, wmis={WMI.AUDI_GERMANY_CAR}, ) AUDI_Q2_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Audi Q2 2018")], VolkswagenCarSpecs(mass=1205, wheelbase=2.61), chassis_codes={"GA"}, wmis={WMI.AUDI_GERMANY_CAR}, ) AUDI_Q3_MK2 = VolkswagenMQBPlatformConfig( [VWCarDocs("Audi Q3 2019-24")], VolkswagenCarSpecs(mass=1623, wheelbase=2.68), chassis_codes={"8U", "F3", "FS"}, wmis={WMI.AUDI_EUROPE_MPV, WMI.AUDI_GERMANY_CAR}, ) AUDI_Q4_MK1 = VolkswagenMEBPlatformConfig( [VWCarDocs("Audi Q4 2021-23")], VolkswagenCarSpecs(mass=1965, wheelbase=2.764), chassis_codes={"FZ"}, wmis={WMI.AUDI_EUROPE_MPV}, model_years={"M", "N", "P"}, ) AUDI_Q4_MK2 = VolkswagenMEBPlatformConfig( [VWCarDocs("Audi Q4 2024-25")], VolkswagenCarSpecs(mass=1965, wheelbase=2.764), chassis_codes={"FZ"}, wmis={WMI.AUDI_EUROPE_MPV}, model_years={"R", "S"}, flags=VolkswagenFlags.MEB_GEN2, ) AUDI_Q5_MK1 = VolkswagenMLBPlatformConfig( [VWCarDocs("Audi Q5 2013-17")], VolkswagenCarSpecs(mass=1895, wheelbase=2.81), chassis_codes={"8R"}, wmis={WMI.AUDI_EUROPE_MPV, WMI.AUDI_GERMANY_CAR}, ) PORSCHE_MACAN_MK1 = VolkswagenMLBPlatformConfig( [VWCarDocs("Porsche Macan 2017-24")], VolkswagenCarSpecs(mass=1895, wheelbase=2.81, steerRatio=16.2), chassis_codes={"95", "A5"}, wmis={WMI.PORSCHE_SUV}, ) SEAT_ATECA_MK1 = VolkswagenMQBPlatformConfig( [ VWCarDocs("CUPRA Ateca 2018-23"), VWCarDocs("SEAT Ateca 2016-23"), VWCarDocs("SEAT Leon (Mk3) 2014-20"), ], VolkswagenCarSpecs(mass=1300, wheelbase=2.64), chassis_codes={"5F"}, wmis={WMI.SEAT}, ) SEAT_LEON_MK4 = VolkswagenMQBevoPlatformConfig( [VWCarDocs("SEAT Leon (Mk4) 2020-25")], VolkswagenCarSpecs(mass=1300, wheelbase=2.685), chassis_codes={"KL"}, wmis={WMI.SEAT}, ) CUPRA_BORN_MK1 = VolkswagenMEBPlatformConfig( [VWCarDocs("CUPRA Born 2022-23")], VolkswagenCarSpecs(mass=1950, wheelbase=2.766, steerRatio=15.9), chassis_codes={"K1"}, model_years={"N", "P"}, wmis={WMI.SEAT}, ) SKODA_ENYAQ_MK1 = VolkswagenMEBPlatformConfig( [VWCarDocs("Škoda Enyaq 2021-23")], VolkswagenCarSpecs(mass=1965, wheelbase=2.77), chassis_codes={"NY"}, model_years={"M", "N", "P"}, wmis={WMI.SKODA}, ) SKODA_ENYAQ_MK2 = VolkswagenMEBPlatformConfig( [VWCarDocs("Škoda Enyaq 2024-25")], VolkswagenCarSpecs(mass=1965, wheelbase=2.77), chassis_codes={"NY"}, model_years={"R", "S"}, wmis={WMI.SKODA}, flags=VolkswagenFlags.MEB_GEN2, ) SKODA_FABIA_MK4 = VolkswagenMQBPlatformConfig( [VWCarDocs("Škoda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0])], VolkswagenCarSpecs(mass=1266, wheelbase=2.56), chassis_codes={"PJ"}, wmis={WMI.SKODA}, ) SKODA_KAMIQ_MK1 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Škoda Kamiq 2021-23", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), VWCarDocs("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), ], VolkswagenCarSpecs(mass=1230, wheelbase=2.66), chassis_codes={"NW"}, wmis={WMI.SKODA}, ) SKODA_KAROQ_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Škoda Karoq 2019-23")], VolkswagenCarSpecs(mass=1278, wheelbase=2.66), chassis_codes={"NU"}, wmis={WMI.SKODA}, ) SKODA_KODIAQ_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Škoda Kodiaq 2017-23")], VolkswagenCarSpecs(mass=1569, wheelbase=2.79), chassis_codes={"NS"}, wmis={WMI.SKODA, WMI.VOLKSWAGEN_GROUP_RUS}, ) SKODA_OCTAVIA_MK3 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Škoda Octavia 2015-19"), VWCarDocs("Škoda Octavia RS 2016"), VWCarDocs("Škoda Octavia Scout 2017-19"), ], VolkswagenCarSpecs(mass=1388, wheelbase=2.68), chassis_codes={"NE"}, wmis={WMI.SKODA}, ) SKODA_SUPERB_MK3 = VolkswagenMQBPlatformConfig( [VWCarDocs("Škoda Superb 2015-22")], VolkswagenCarSpecs(mass=1505, wheelbase=2.84), chassis_codes={"3V", "NP"}, wmis={WMI.SKODA}, ) def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str]: candidates = set() # Compile all FW versions for each ECU all_ecu_versions: dict[EcuAddrSubAddr, set[str]] = defaultdict(set) for ecus in offline_fw_versions.values(): for ecu, versions in ecus.items(): all_ecu_versions[ecu] |= set(versions) # Check the WMI and chassis code to determine the platform # https://www.clubvw.org.au/vwreference/vwvin vin_obj = Vin(vin) vin_wmi = vin_obj.wmi if len(vin_obj.wmi) == 3 else None chassis_code = vin_obj.vds[3:5] if len(vin_obj.vds) >= 5 else None model_year_code = vin_obj.vis[0] if len(vin_obj.vis) > 0 else None vin_available = vin_wmi is not None and chassis_code is not None fallback_ecus = CHECK_FUZZY_ECUS | {Ecu.fwdCamera, Ecu.adas, Ecu.cornerRadar, Ecu.parkingAdas} fallback_optional_ecus = fallback_ecus - CHECK_FUZZY_ECUS for platform in CAR: if not vin_available: matched_ecus = set() optional_ecu_seen = False for ecu, versions in offline_fw_versions.get(platform, {}).items(): if ecu[0] not in fallback_ecus: continue found_versions = live_fw_versions.get(ecu[1:], []) if len(found_versions) == 0: continue if ecu[0] in fallback_optional_ecus: optional_ecu_seen = True if any(found_version in versions for found_version in found_versions): matched_ecus.add(ecu[0]) else: matched_ecus = set() break if Ecu.fwdRadar in matched_ecus and (len(matched_ecus) >= 2 or not optional_ecu_seen): candidates.add(platform) continue valid_ecus = set() for ecu in offline_fw_versions.get(platform, {}): addr = ecu[1:] if ecu[0] not in CHECK_FUZZY_ECUS: continue # Sanity check that live FW is in the superset of all FW, Volkswagen ECU part numbers are commonly shared found_versions = live_fw_versions.get(addr, []) expected_versions = all_ecu_versions[ecu] if not any(found_version in expected_versions for found_version in found_versions): break valid_ecus.add(ecu[0]) if valid_ecus != CHECK_FUZZY_ECUS: continue model_years = getattr(platform.config, "model_years", set()) if vin_wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes: if len(model_years) > 0 and model_year_code is not None and model_year_code not in model_years: continue candidates.add(platform) return {str(c) for c in candidates} def refine_fw_matches(matches, vin) -> set[str]: vin_obj = Vin(vin) if len(vin_obj.wmi) != 3 or len(vin_obj.vds) < 5: return matches chassis_code = vin_obj.vds[3:5] model_year_code = vin_obj.vis[0] if len(vin_obj.vis) > 0 else None candidates = set() for platform in CAR: if platform not in matches: continue model_years = getattr(platform.config, "model_years", set()) if vin_obj.wmi not in platform.config.wmis or chassis_code not in platform.config.chassis_codes: continue if model_years and model_year_code not in model_years: continue candidates.add(platform) return {str(candidate) for candidate in candidates} or matches # These ECUs are required to match to gain a VIN match CHECK_FUZZY_ECUS = {Ecu.fwdRadar} # All supported cars should return FW from the engine, srs, eps, and fwdRadar. Cars # with a manual trans won't return transmission firmware, but all other cars will. # # The 0xF187 SW part number query should return in the form of N[NX][NX] NNN NNN [X[X]], # where N=number, X=letter, and the trailing two letters are optional. Performance # tuners sometimes tamper with that field (e.g. 8V0 9C0 BB0 1 from COBB/EQT). Tampered # ECU SW part numbers are invalid for vehicle ID and compatibility checks. Try to have # them repaired by the tuner before including them in openpilot. VOLKSWAGEN_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) VOLKSWAGEN_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) VOLKSWAGEN_RX_OFFSET = 0x6a VOLKSWAGEN_RX_OFFSET_CANFD = 0x20000 FW_QUERY_CONFIG = FwQueryConfig( requests=[request for bus, obd_multiplexing in [(1, True), (1, False), (0, False)] for request in [ Request( [VOLKSWAGEN_VERSION_REQUEST_MULTI], [VOLKSWAGEN_VERSION_RESPONSE], whitelist_ecus=[Ecu.srs, Ecu.eps, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.parkingAdas, Ecu.cornerRadar, Ecu.adas], rx_offset=VOLKSWAGEN_RX_OFFSET, bus=bus, obd_multiplexing=obd_multiplexing, ), Request( [VOLKSWAGEN_VERSION_REQUEST_MULTI], [VOLKSWAGEN_VERSION_RESPONSE], whitelist_ecus=[Ecu.engine, Ecu.transmission], bus=bus, obd_multiplexing=obd_multiplexing, ), Request( [VOLKSWAGEN_VERSION_REQUEST_MULTI], [VOLKSWAGEN_VERSION_RESPONSE], whitelist_ecus=[Ecu.engine, Ecu.inverter], rx_offset=VOLKSWAGEN_RX_OFFSET_CANFD, bus=bus, obd_multiplexing=obd_multiplexing, ), ]], non_essential_ecus={Ecu.eps: list(CAR)}, match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, refine_fw_matches=refine_fw_matches, ) MQB_A0_CARS = { CAR.VOLKSWAGEN_POLO_MK6, CAR.VOLKSWAGEN_TCROSS_MK1, CAR.SKODA_FABIA_MK4, CAR.SKODA_KAMIQ_MK1, } def get_longitudinal_stopping_speed_override(candidate: CAR, flags: int) -> float: if candidate == CAR.VOLKSWAGEN_PASSAT_B7: return PASSAT_B7_STOPPING_SPEED if flags & VolkswagenFlags.PQ: return PQ_STOPPING_SPEED return 0.0 def apply_pq_stopping_accel(candidate: CAR, accel: float, stopping: bool) -> float: return PASSAT_B7_STOP_ACCEL if candidate == CAR.VOLKSWAGEN_PASSAT_B7 and stopping else accel DBC = CAR.create_dbc_map()