diff --git a/docs/CARS.md b/docs/CARS.md index aad2f2ceef5..819f2db3c54 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -350,6 +350,7 @@ |Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| +|Volkswagen|ID.4 2021-25|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Jetta 2015-18|Adaptive Cruise Control (ACC) & Lane Assist|[Dashcam mode](#dashcam)| |Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| |Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|[Upstream](#upstream)| diff --git a/opendbc/car/docs_definitions.py b/opendbc/car/docs_definitions.py index 04a7155822a..9f1ef11ec23 100644 --- a/opendbc/car/docs_definitions.py +++ b/opendbc/car/docs_definitions.py @@ -114,6 +114,7 @@ class CarHarness(EnumBase): fca = BaseCarHarness("FCA connector") ram = BaseCarHarness("Ram connector") vw_a = BaseCarHarness("VW A connector") + 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") diff --git a/opendbc/car/tests/routes.py b/opendbc/car/tests/routes.py index a8521838113..92e3eec6f3f 100644 --- a/opendbc/car/tests/routes.py +++ b/opendbc/car/tests/routes.py @@ -255,6 +255,7 @@ class CarTestRoute(NamedTuple): 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/car/torque_data/override.toml b/opendbc/car/torque_data/override.toml index 04b9f2a5f79..70943a3f98f 100644 --- a/opendbc/car/torque_data/override.toml +++ b/opendbc/car/torque_data/override.toml @@ -51,6 +51,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] diff --git a/opendbc/car/volkswagen/carcontroller.py b/opendbc/car/volkswagen/carcontroller.py index a79526dfa6e..b4990831f9e 100644 --- a/opendbc/car/volkswagen/carcontroller.py +++ b/opendbc/car/volkswagen/carcontroller.py @@ -3,7 +3,7 @@ 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 @@ -13,13 +13,22 @@ class CarController(CarControllerBase): def __init__(self, dbc_names, CP): super().__init__(dbc_names, CP) - self.CCP = CarControllerParams(CP) self.CAN = CanBus(CP) - self.CCS = pqcan if CP.flags & VolkswagenFlags.PQ else mqbcan + self.CCP = CarControllerParams(CP) self.packer_pt = CANPacker(dbc_names[Bus.pt]) + + self.CCS = mqbcan + if CP.flags & VolkswagenFlags.PQ: + self.CCS = pqcan + elif CP.flags & VolkswagenFlags.MEB: + self.CCS = mebcan + 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,37 +42,63 @@ def update(self, CC, CS, now_nanos): # **** 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 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 + 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) + + # 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, self.CAN.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 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 - if not hca_enabled: - self.hca_frame_timer_running = 0 + if not hca_enabled: + self.hca_frame_timer_running = 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, self.CAN.pt, apply_torque, hca_enabled)) + 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, self.CAN.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 @@ -74,9 +109,16 @@ def update(self, CC, CS, now_nanos): ea_simulated_torque = CS.out.steeringTorque can_sends.append(self.CCS.create_eps_update(self.packer_pt, self.CAN.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, self.CAN.pt)) + if self.frame % 50 == 0: + can_sends.append(mebcan.create_ea_hud(self.packer_pt, self.CAN.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 +142,7 @@ def update(self, CC, CS, now_nanos): can_sends.append(self.CCS.create_lka_hud_control(self.packer_pt, self.CAN.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 @@ -121,6 +163,7 @@ def update(self, CC, CS, now_nanos): 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/car/volkswagen/carstate.py b/opendbc/car/volkswagen/carstate.py index 86ac2bdaa68..d840982709e 100644 --- a/opendbc/car/volkswagen/carstate.py +++ b/opendbc/car/volkswagen/carstate.py @@ -18,6 +18,7 @@ def __init__(self, CP): 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: @@ -61,7 +62,54 @@ def update(self, can_parsers) -> structs.CarState: 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? + + self.parse_wheel_speeds(ret, + 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"], + ) + + hca_status = self.CCP.hca_status_values.get(pt_cp.vl["QFK_01"]["LatCon_HCA_Status"]) + + drive_mode = ret.gearShifter == GearShifter.drive + ret.gasPressed = pt_cp.vl["Motor_54"]["Accelerator_Pressure"] > 0 + 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 if self.CP.flags & VolkswagenFlags.KOMBI_PRESENT: self.upscale_lead_car_signal = bool(pt_cp.vl["Kombi_03"]["KBI_Variante"]) # Analog vs digital instrument cluster @@ -78,6 +126,8 @@ def update(self, can_parsers) -> structs.CarState: ret.carFaultedNonCritical = bool(cam_cp.vl["HCA_01"]["EA_Ruckfreigabe"]) or cam_cp.vl["HCA_01"]["EA_ACC_Sollstatus"] > 0 # EA drive_mode = True + ret.vEgoCluster = pt_cp.vl["Kombi_01"]["KBI_angez_Geschw"] * CV.KPH_TO_MS + ret.gasPressed = pt_cp.vl["Motor_20"]["MO_Fahrpedalrohwert_01"] > 0 ret.brake = pt_cp.vl["ESP_05"]["ESP_Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects brake_pedal_pressed = bool(pt_cp.vl["Motor_14"]["MO_Fahrer_bremst"]) brake_pressure_detected = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"]) @@ -112,7 +162,6 @@ def update(self, can_parsers) -> structs.CarState: ret.rightBlinker = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Right"]) # Shared logic - ret.vEgoCluster = pt_cp.vl["Kombi_01"]["KBI_angez_Geschw"] * CV.KPH_TO_MS ret.steeringAngleDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradwinkel"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradwinkel"])] ret.steeringRateDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradw_Geschw"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradw_Geschw"])] @@ -120,7 +169,6 @@ def update(self, can_parsers) -> structs.CarState: ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE ret.steerFaultTemporary, ret.steerFaultPermanent = self.update_hca_state(hca_status, drive_mode) - ret.gasPressed = pt_cp.vl["Motor_20"]["MO_Fahrpedalrohwert_01"] > 0 ret.espActive = bool(pt_cp.vl["ESP_21"]["ESP_Eingriff"]) ret.espDisabled = pt_cp.vl["ESP_21"]["ESP_Tastung_passiv"] != 0 ret.seatbeltUnlatched = pt_cp.vl["Airbag_02"]["AB_Gurtschloss_FA"] != 3 @@ -253,6 +301,8 @@ def update_hca_state(self, hca_status, drive_mode=True): 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) # another case of the 1-50Hz cam_messages = [] @@ -276,3 +326,12 @@ def get_can_parsers_pq(CP): Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).cam), } + @staticmethod + def get_can_parsers_meb(CP): + return { + Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [ + # the 50->1Hz is currently too much for the CANParser to figure out + ("Blinkmodi_02", 1), # From J519 BCM (sent at 1Hz when no lights active, 50Hz when active) + ], CanBus.pt), + Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus.cam), + } diff --git a/opendbc/car/volkswagen/fingerprints.py b/opendbc/car/volkswagen/fingerprints.py index 7949a11caa1..170844ac553 100644 --- a/opendbc/car/volkswagen/fingerprints.py +++ b/opendbc/car/volkswagen/fingerprints.py @@ -1015,6 +1015,14 @@ 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_FABIA_MK4: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705C906032L \xf1\x891701', diff --git a/opendbc/car/volkswagen/interface.py b/opendbc/car/volkswagen/interface.py index 4c40f8740b9..87c6ffa5047 100644 --- a/opendbc/car/volkswagen/interface.py +++ b/opendbc/car/volkswagen/interface.py @@ -37,6 +37,20 @@ def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alp # Panda ALLOW_DEBUG firmware required. ret.dashcamOnly = True + elif ret.flags & VolkswagenFlags.MEB: + # Set global MEB parameters + ret.dashcamOnly = True # In development, safety code not yet available + safety_configs = [get_safety_config(structs.CarParams.SafetyModel.noOutput)] + 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 safety_configs = [get_safety_config(structs.CarParams.SafetyModel.volkswagen)] @@ -65,6 +79,8 @@ def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alp 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.] diff --git a/opendbc/car/volkswagen/mebcan.py b/opendbc/car/volkswagen/mebcan.py new file mode 100644 index 00000000000..27ee03cc6c3 --- /dev/null +++ b/opendbc/car/volkswagen/mebcan.py @@ -0,0 +1,34 @@ +import opendbc.car.volkswagen.mqbcan as mqbcan + + +create_eps_update = mqbcan.create_eps_update +create_lka_hud_control = mqbcan.create_lka_hud_control +create_acc_buttons_control = mqbcan.create_acc_buttons_control + + +def create_steering_control(packer, bus, apply_curvature, lkas_enabled, power): + values = { + "Curvature": abs(apply_curvature), # in rad/m + "Curvature_VZ": 1 if apply_curvature > 0 and lkas_enabled else 0, + "Power": power if lkas_enabled else 0, + "RequestStatus": 4 if lkas_enabled else 2, + "HighSendRate": lkas_enabled, + } + return packer.make_can_msg("HCA_03", bus, values) + + +def create_ea_control(packer, bus): + values = { + "EA_Funktionsstatus": 1, # Configured but disabled + "EA_Sollbeschleunigung": 2046, # Inactive value + } + + return packer.make_can_msg("EA_01", bus, values) + + +def create_ea_hud(packer, bus): + values = { + "EA_Unknown": 1, # Undocumented, value when inactive + } + + return packer.make_can_msg("EA_02", bus, values) diff --git a/opendbc/car/volkswagen/values.py b/opendbc/car/volkswagen/values.py index 573596fb10e..6123d3792c4 100644 --- a/opendbc/car/volkswagen/values.py +++ b/opendbc/car/volkswagen/values.py @@ -102,6 +102,38 @@ def __init__(self, CP): "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 @@ -169,6 +201,7 @@ class VolkswagenFlags(IntFlag): # Static flags PQ = 2 + MEB = 8 @dataclass @@ -180,6 +213,18 @@ class VolkswagenMQBPlatformConfig(PlatformConfig): 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_pq'}) @@ -214,6 +259,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 platform is using CAN-FD, which is supported by comma 3x or red panda.", + Column.HARDWARE) @dataclass @@ -226,7 +274,9 @@ def init_make(self, CP: structs.CarParams): 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: @@ -238,7 +288,7 @@ def init_make(self, CP: structs.CarParams): # FW_VERSIONS for that existing CAR. class CAR(Platforms): - config: VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig + config: VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig | VolkswagenMEBPlatformConfig VOLKSWAGEN_ARTEON_MK1 = VolkswagenMQBPlatformConfig( [ @@ -300,6 +350,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),