From 473d671f2b64d41c0a82a299a1f45cc6951fca94 Mon Sep 17 00:00:00 2001 From: Rick Lan Date: Fri, 26 Dec 2025 11:04:35 +0800 Subject: [PATCH] feat: Squash all pre-brand features into pre --- common/params_keys.h | 6 + dragonpilot/settings.py | 36 ++++ opendbc_repo/opendbc/car/__init__.py | 1 + opendbc_repo/opendbc/car/honda/interface.py | 34 +++- opendbc_repo/opendbc/car/hyundai/interface.py | 7 + opendbc_repo/opendbc/car/structs.py | 6 + opendbc_repo/opendbc/car/subaru/interface.py | 8 +- opendbc_repo/opendbc/car/subaru/values.py | 13 +- opendbc_repo/opendbc/car/tests/routes.py | 1 + .../opendbc/car/toyota/carcontroller.py | 33 +++- opendbc_repo/opendbc/car/toyota/carstate.py | 38 +++- opendbc_repo/opendbc/car/toyota/interface.py | 61 +++++- opendbc_repo/opendbc/car/toyota/values.py | 8 + opendbc_repo/opendbc/car/toyota/zss.py | 81 ++++++++ .../opendbc/car/volkswagen/carcontroller.py | 32 +++- .../opendbc/car/volkswagen/interface.py | 11 +- opendbc_repo/opendbc/car/volkswagen/mlbcan.py | 2 +- opendbc_repo/opendbc/car/volkswagen/mqbcan.py | 2 +- opendbc_repo/opendbc/car/volkswagen/pqcan.py | 4 +- opendbc_repo/opendbc/car/volkswagen/values.py | 3 + opendbc_repo/opendbc/dbc/toyota_zss.dbc | 4 + opendbc_repo/opendbc/safety/modes/subaru.h | 14 +- opendbc_repo/opendbc/safety/modes/toyota.h | 25 ++- .../opendbc/safety/tests/test_tx_ext.py | 179 ++++++++++++++++++ selfdrive/car/card.py | 18 ++ 25 files changed, 592 insertions(+), 35 deletions(-) create mode 100644 opendbc_repo/opendbc/car/toyota/zss.py create mode 100644 opendbc_repo/opendbc/dbc/toyota_zss.dbc create mode 100644 opendbc_repo/opendbc/safety/tests/test_tx_ext.py diff --git a/common/params_keys.h b/common/params_keys.h index 0a8a5c178..9a6afda3d 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -155,4 +155,10 @@ inline static std::unordered_map keys = { {"dp_dev_disable_connect", {PERSISTENT, BOOL, "0"}}, {"dp_dev_tethering", {PERSISTENT, BOOL, "0"}}, {"dp_ui_mici", {PERSISTENT, BOOL, "0"}}, + {"dp_toyota_door_auto_lock_unlock", {PERSISTENT, BOOL, "0"}}, + {"dp_toyota_tss1_sng", {PERSISTENT, BOOL, "0"}}, + {"dp_toyota_stock_lon", {PERSISTENT, BOOL, "0"}}, + {"dp_vag_a0_sng", {PERSISTENT, BOOL, "0"}}, + {"dp_vag_pq_steering_patch", {PERSISTENT, BOOL, "0"}}, + {"dp_vag_avoid_eps_lockout", {PERSISTENT, BOOL, "0"}}, }; diff --git a/dragonpilot/settings.py b/dragonpilot/settings.py index 266926218..d26971cc1 100644 --- a/dragonpilot/settings.py +++ b/dragonpilot/settings.py @@ -5,6 +5,24 @@ SETTINGS = [ "title": "Toyota / Lexus", "condition": "brand == 'toyota'", "settings": [ + { + "key": "dp_toyota_door_auto_lock_unlock", + "type": "toggle_item", + "title": lambda: tr("Door Auto Lock/Unlock"), + "description": lambda: tr("Enable openpilot to auto-lock doors above 20 km/h and auto-unlock when shifting to Park."), + }, + { + "key": "dp_toyota_tss1_sng", + "type": "toggle_item", + "title": lambda: tr("Enable TSS1 SnG Mod"), + "description": "" + }, + { + "key": "dp_toyota_stock_lon", + "type": "toggle_item", + "title": lambda: tr("Use Stock Longitudinal Control"), + "description": "" + }, ], }, @@ -12,6 +30,24 @@ SETTINGS = [ "title": "VAG", "condition": "brand == 'volkswagen'", "settings": [ + { + "key": "dp_vag_a0_sng", + "type": "toggle_item", + "title": lambda: tr("MQB A0 SnG Mod"), + "description": "" + }, + { + "key": "dp_vag_pq_steering_patch", + "type": "toggle_item", + "title": lambda: tr("PQ Steering Patch"), + "description": "", + }, + { + "key": "dp_vag_avoid_eps_lockout", + "type": "toggle_item", + "title": lambda: tr("Avoid EPS Lockout"), + "description": "" + }, ], }, diff --git a/opendbc_repo/opendbc/car/__init__.py b/opendbc_repo/opendbc/car/__init__.py index 79fb45537..4213b39e1 100644 --- a/opendbc_repo/opendbc/car/__init__.py +++ b/opendbc_repo/opendbc/car/__init__.py @@ -91,6 +91,7 @@ class Bus(StrEnum): party = auto() ap_party = auto() + zss = auto() def rate_limit(new_value, last_value, dw_step, up_step): return float(np.clip(new_value, last_value + dw_step, last_value + up_step)) diff --git a/opendbc_repo/opendbc/car/honda/interface.py b/opendbc_repo/opendbc/car/honda/interface.py index a2eb7eaee..bd3c61430 100755 --- a/opendbc_repo/opendbc/car/honda/interface.py +++ b/opendbc_repo/opendbc/car/honda/interface.py @@ -91,13 +91,25 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kiV = [1.2, 0.8, 0.5] # Disable control if EPS mod detected + eps_modified = False for fw in car_fw: if fw.ecu == "eps" and b"," in fw.fwVersion: - ret.dashcamOnly = True + # ret.dashcamOnly = True + eps_modified = True if candidate == CAR.HONDA_CIVIC: - ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]] + if eps_modified: + # stock request input values: 0x0000, 0x00DE, 0x014D, 0x01EF, 0x0290, 0x0377, 0x0454, 0x0610, 0x06EE + # stock request output values: 0x0000, 0x0917, 0x0DC5, 0x1017, 0x119F, 0x140B, 0x1680, 0x1680, 0x1680 + # modified request output values: 0x0000, 0x0917, 0x0DC5, 0x1017, 0x119F, 0x140B, 0x1680, 0x2880, 0x3180 + # stock filter output values: 0x009F, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108 + # modified filter output values: 0x009F, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0400, 0x0480 + # note: max request allowed is 4096, but request is capped at 3840 in firmware, so modifications result in 2x max + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 8000], [0, 2560, 3840]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.1]] + else: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]] elif candidate in (CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CIVIC_BOSCH_DIESEL): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end @@ -112,7 +124,10 @@ class CarInterface(CarInterfaceBase): elif candidate == CAR.HONDA_ACCORD: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] + if eps_modified: + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.09]] + else: + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] elif candidate == CAR.ACURA_ILX: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] # TODO: determine if there is a dead zone at the top end @@ -124,8 +139,15 @@ class CarInterface(CarInterfaceBase): ret.wheelSpeedFactor = 1.025 elif candidate == CAR.HONDA_CRV_5G: - ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]] + if eps_modified: + # stock request input values: 0x0000, 0x00DB, 0x01BB, 0x0296, 0x0377, 0x0454, 0x0532, 0x0610, 0x067F + # stock request output values: 0x0000, 0x0500, 0x0A15, 0x0E6D, 0x1100, 0x1200, 0x129A, 0x134D, 0x1400 + # modified request output values: 0x0000, 0x0500, 0x0A15, 0x0E6D, 0x1100, 0x1200, 0x1ACD, 0x239A, 0x2800 + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 10000], [0, 2560, 3840]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.21], [0.07]] + else: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]] ret.wheelSpeedFactor = 1.025 elif candidate == CAR.HONDA_CRV_HYBRID: diff --git a/opendbc_repo/opendbc/car/hyundai/interface.py b/opendbc_repo/opendbc/car/hyundai/interface.py index 3744450ab..19c273ca0 100644 --- a/opendbc_repo/opendbc/car/hyundai/interface.py +++ b/opendbc_repo/opendbc/car/hyundai/interface.py @@ -152,6 +152,13 @@ class CarInterface(CarInterfaceBase): if candidate in (CAR.KIA_OPTIMA_H,): ret.dashcamOnly = True + # w/ SMDPS, allow steering to 0 + if 0x2AA in fingerprint[0]: + ret.minSteerSpeed = 0. + print("----------------------------------------------") + print("dragonpilot: SMDPS detected!") + print("----------------------------------------------") + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/structs.py b/opendbc_repo/opendbc/car/structs.py index 1f8d36083..71dbc5f4f 100644 --- a/opendbc_repo/opendbc/car/structs.py +++ b/opendbc_repo/opendbc/car/structs.py @@ -22,4 +22,10 @@ CarParamsT = capnp.lib.capnp._StructModule class DPFlags: LatALKA = 1 ExtRadar = 2 + ToyotaLockCtrl = 2 ** 2 + ToyotaTSS1SnG = 2 ** 3 + ToyotaStockLon = 2 ** 4 + VagA0SnG = 2 ** 5 + VAGPQSteeringPatch = 2 ** 6 + VagAvoidEPSLockout = 2 ** 7 pass diff --git a/opendbc_repo/opendbc/car/subaru/interface.py b/opendbc_repo/opendbc/car/subaru/interface.py index 09b9b3a91..1064324d9 100644 --- a/opendbc_repo/opendbc/car/subaru/interface.py +++ b/opendbc_repo/opendbc/car/subaru/interface.py @@ -33,6 +33,8 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.subaru)] if ret.flags & SubaruFlags.GLOBAL_GEN2: ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.GEN2.value + elif ret.flags & (SubaruFlags.IMPREZA_2018 | SubaruFlags.HYBRID) : + ret.safetyConfigs[0].safetyParam |= SubaruSafetyFlags.IMPREZA_2018.value ret.steerLimitTimer = 0.4 ret.steerActuatorDelay = 0.1 @@ -50,11 +52,11 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.0025, 0.1], [0.00025, 0.01]] elif candidate == CAR.SUBARU_IMPREZA: - ret.steerActuatorDelay = 0.4 # end-to-end angle controller + ret.steerActuatorDelay = 0.1 # end-to-end angle controller ret.lateralTuning.init('pid') - ret.lateralTuning.pid.kf = 0.00005 + ret.lateralTuning.pid.kf = 0.00003 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.12, 0.18], [0.012, 0.018]] elif candidate == CAR.SUBARU_IMPREZA_2020: ret.lateralTuning.init('pid') diff --git a/opendbc_repo/opendbc/car/subaru/values.py b/opendbc_repo/opendbc/car/subaru/values.py index a702d3943..ec5b095e0 100644 --- a/opendbc_repo/opendbc/car/subaru/values.py +++ b/opendbc_repo/opendbc/car/subaru/values.py @@ -26,6 +26,12 @@ class CarControllerParams: elif CP.carFingerprint == CAR.SUBARU_IMPREZA_2020: self.STEER_DELTA_UP = 35 self.STEER_MAX = 1439 + self.STEER_DELTA_UP = 35 + self.STEER_DELTA_DOWN = 70 + elif CP.carFingerprint == CAR.SUBARU_IMPREZA: + self.STEER_MAX = 3071 + self.STEER_DELTA_UP = 60 + self.STEER_DELTA_DOWN = 60 else: self.STEER_MAX = 2047 @@ -57,7 +63,7 @@ class SubaruSafetyFlags(IntFlag): GEN2 = 1 LONG = 2 PREGLOBAL_REVERSED_DRIVER_TORQUE = 4 - + IMPREZA_2018 = 8 class SubaruFlags(IntFlag): # Detected flags @@ -74,6 +80,8 @@ class SubaruFlags(IntFlag): HYBRID = 32 LKAS_ANGLE = 64 + # rick + IMPREZA_2018 = 2 ** 10 GLOBAL_ES_ADDR = 0x787 GEN2_ES_BUTTONS_DID = b'\x11\x30' @@ -143,7 +151,8 @@ class CAR(Platforms): SubaruCarDocs("Subaru Crosstrek 2018-19", video="https://youtu.be/Agww7oE1k-s?t=26"), SubaruCarDocs("Subaru XV 2018-19", video="https://youtu.be/Agww7oE1k-s?t=26"), ], - CarSpecs(mass=1568, wheelbase=2.67, steerRatio=15), + CarSpecs(mass=1568, wheelbase=2.67, steerRatio=13.5), + flags=SubaruFlags.IMPREZA_2018, ) SUBARU_IMPREZA_2020 = SubaruPlatformConfig( [ diff --git a/opendbc_repo/opendbc/car/tests/routes.py b/opendbc_repo/opendbc/car/tests/routes.py index 01bcd5b30..4470a06ea 100644 --- a/opendbc_repo/opendbc/car/tests/routes.py +++ b/opendbc_repo/opendbc/car/tests/routes.py @@ -268,6 +268,7 @@ routes = [ CarTestRoute("6719965b0e1d1737/2023-02-09--22-44-05", TOYOTA.TOYOTA_CHR_TSS2), # hybrid CarTestRoute("6719965b0e1d1737/2023-08-29--06-40-05", TOYOTA.TOYOTA_CHR_TSS2), # hybrid, openpilot longitudinal, radar disabled CarTestRoute("14623aae37e549f3/2021-10-24--01-20-49", TOYOTA.TOYOTA_PRIUS_V), + CarTestRoute("ea8fbe72b96a185c|2023-02-22--09-20-34", TOYOTA.TOYOTA_CHR_TSS2), # openpilot longitudinal, with Radar Filter CarTestRoute("202c40641158a6e5/2021-09-21--09-43-24", VOLKSWAGEN.VOLKSWAGEN_ARTEON_MK1), CarTestRoute("2c68dda277d887ac/2021-05-11--15-22-20", VOLKSWAGEN.VOLKSWAGEN_ATLAS_MK1), diff --git a/opendbc_repo/opendbc/car/toyota/carcontroller.py b/opendbc_repo/opendbc/car/toyota/carcontroller.py index fb23c7e1a..ec26c24f9 100644 --- a/opendbc_repo/opendbc/car/toyota/carcontroller.py +++ b/opendbc_repo/opendbc/car/toyota/carcontroller.py @@ -2,6 +2,7 @@ import math import numpy as np from opendbc.car import Bus, make_tester_present_msg, rate_limit, structs, ACCELERATION_DUE_TO_GRAVITY, DT_CTRL from opendbc.car.lateral import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance +from opendbc.car.can_definitions import CanData from opendbc.car.carlog import carlog from opendbc.car.common.filter_simple import FirstOrderFilter, HighPassFilter from opendbc.car.common.pid import PIDController @@ -34,6 +35,17 @@ MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut # EPS allows user torque above threshold for 50 frames before permanently faulting MAX_USER_TORQUE = 500 +# Lock / unlock door commands - Credit goes to AlexandreSato! +from opendbc.car.common.conversions import Conversions as CV +LOCK_SPEED = 20 * CV.KPH_TO_MS + +LOCK_UNLOCK_CAN_ID = 0x750 +UNLOCK_CMD = b'\x40\x05\x30\x11\x00\x40\x00\x00' +LOCK_CMD = b'\x40\x05\x30\x11\x00\x80\x00\x00' + +from cereal import car +PARK = car.CarState.GearShifter.park +DRIVE = car.CarState.GearShifter.drive def get_long_tune(CP, params): if CP.carFingerprint in TSS2_CAR: @@ -78,6 +90,8 @@ class CarController(CarControllerBase): self.secoc_acc_message_counter = 0 self.secoc_prev_reset_counter = 0 + self.doors_locked = False + def update(self, CC, CS, now_nanos): actuators = CC.actuators stopping = actuators.longControlState == LongCtrlState.stopping @@ -190,6 +204,9 @@ class CarController(CarControllerBase): if not should_resume and CS.out.cruiseState.standstill: self.standstill_req = True + if (self.CP.flags & ToyotaFlags.TSS1_SNG.value) and CS.out.standstill and not self.last_standstill: + self.standstill_req = False + self.last_standstill = CS.out.standstill # handle UI messages @@ -261,6 +278,10 @@ class CarController(CarControllerBase): elif net_acceleration_request_min > 0.3: self.permit_braking = False + # rick - do not do delay compensation for non-TSS2 vehicles (e.g. car with sDSU?), assign the value back to actuators.accel + # from Jason, see https://github.com/sunnypilot/opendbc/compare/dd2016f77f8467ca2f7934db1b8c6d73164b3df7...f90b75b1531d0ef949c1c7fb8c175059448a2a97#diff-dc03b1fc7156134429efc0cdced75bc227d0ceb8bbd0c55763022fb9db6794d9 + if not self.CP.carFingerprint in TSS2_CAR: + pcm_accel_cmd = actuators.accel pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX)) main_accel_cmd = 0. if self.CP.flags & ToyotaFlags.SECOC.value else pcm_accel_cmd @@ -306,11 +327,11 @@ class CarController(CarControllerBase): hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart, lat_active, CS.lkas_hud)) - if (self.frame % 100 == 0 or send_ui) and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: + if (self.frame % 100 == 0 or send_ui) and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value and not self.CP.flags & ToyotaFlags.RADAR_FILTER.value: can_sends.append(toyotacan.create_fcw_command(self.packer, fcw_alert)) # keep radar disabled - if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: + if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value and not self.CP.flags & ToyotaFlags.RADAR_FILTER.value: can_sends.append(make_tester_present_msg(0x750, 0, 0xF)) new_actuators = actuators.as_builder() @@ -319,5 +340,13 @@ class CarController(CarControllerBase): new_actuators.steeringAngleDeg = self.last_angle new_actuators.accel = self.accel + if self.CP.flags & ToyotaFlags.LOCK_CTRL.value: + if not self.doors_locked and CS.out.gearShifter == DRIVE and CS.out.vEgo >= LOCK_SPEED: + can_sends.append(CanData(LOCK_UNLOCK_CAN_ID, LOCK_CMD, 0)) + self.doors_locked = True + elif self.doors_locked and CS.out.gearShifter == PARK: + can_sends.append(CanData(LOCK_UNLOCK_CAN_ID, UNLOCK_CMD, 0)) + self.doors_locked = False + self.frame += 1 return new_actuators, can_sends diff --git a/opendbc_repo/opendbc/car/toyota/carstate.py b/opendbc_repo/opendbc/car/toyota/carstate.py index 0522e3a82..a1d26d08c 100644 --- a/opendbc_repo/opendbc/car/toyota/carstate.py +++ b/opendbc_repo/opendbc/car/toyota/carstate.py @@ -53,12 +53,29 @@ class CarState(CarStateBase): self.gvc = 0.0 self.secoc_synchronization = None + # radar filter (mainly for CHR/Camry) + # the idea is to place a Panda in between Radar and camera/body (engine room) to block 0x343 (longitudinal) + # depends on the firmware, we should be able to read most CAN directly from cp (not cp_cam, its empty) + self.dp_radar_filter = bool(self.CP.flags & ToyotaFlags.RADAR_FILTER.value) + + # rick - dsu_bypass from cydia2020: https://github.com/cydia2020/toyota-dsu-reroute-harness/ + # the idea is to "re-route" the DSU to Panda CAN2 (Which connects to ADAS Camera) + # * when comma device is not available, the DSU message can still communicate with ADAS Camera, and over to car. + # * when comma device is active, CAN message of DSU and ADAS camera will the be blocked by Panda, only forward some CAN messages over to car (from CAN0). + self.dp_dsu_bypass = self.CP.flags & ToyotaFlags.DSU_BYPASS.value + + from opendbc.car.toyota.zss import ZSS + self.zss = ZSS(CP.flags) + def update(self, can_parsers) -> structs.CarState: cp = can_parsers[Bus.pt] cp_cam = can_parsers[Bus.cam] ret = structs.CarState() - cp_acc = cp_cam if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) else cp + if self.dp_dsu_bypass: + cp_acc = cp_cam + else: + cp_acc = cp_cam if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) else cp if not self.CP.flags & ToyotaFlags.SECOC.value: self.gvc = cp.vl["VSC1S07"]["GVC"] @@ -78,7 +95,7 @@ class CarState(CarStateBase): else: ret.gasPressed = cp.vl["PCM_CRUISE"]["GAS_RELEASED"] == 0 # TODO: these also have GAS_PEDAL, come back and unify can_gear = int(cp.vl["GEAR_PACKET"]["GEAR"]) - if not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: + if not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value or self.dp_radar_filter: ret.stockAeb = bool(cp_acc.vl["PRE_COLLISION"]["PRECOLLISION_ACTIVE"] and cp_acc.vl["PRE_COLLISION"]["FORCE"] < -1e-5) self.parse_wheel_speeds(ret, @@ -117,6 +134,10 @@ class CarState(CarStateBase): # we could use the override bit from dbc, but it's triggered at too high torque values ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD + if self.zss.enabled: + self.zss.set_values(can_parsers[Bus.zss]) + ret.steeringAngleDeg = self.zss.get_steering_angle_deg(cp.vl["PCM_CRUISE"]["CRUISE_ACTIVE"], ret.steeringAngleDeg) + # Check EPS LKA/LTA fault status ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in TEMP_STEER_FAULTS ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] in PERM_STEER_FAULTS @@ -147,8 +168,10 @@ class CarState(CarStateBase): conversion_factor = CV.KPH_TO_MS if is_metric else CV.MPH_TO_MS ret.cruiseState.speedCluster = cluster_set_speed * conversion_factor - if self.CP.carFingerprint in TSS2_CAR and not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value: - self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"] + if (self.CP.carFingerprint in TSS2_CAR and not self.CP.flags & ToyotaFlags.DISABLE_RADAR.value) or \ + self.dp_dsu_bypass: + if not (self.CP.flags & ToyotaFlags.SDSU.value): + self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"] ret.stockFcw = bool(cp_acc.vl["PCS_HUD"]["FCW"]) # some TSS2 cars have low speed lockout permanently set, so ignore on those cars @@ -211,7 +234,12 @@ class CarState(CarStateBase): ("BLINKERS_STATE", float('nan')), ] - return { + parsers = { Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, 0), Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], 2), } + + if CP.flags & ToyotaFlags.ZSS: + parsers[Bus.zss] = CANParser("toyota_zss", [("SECONDARY_STEER_ANGLE", float('nan'))], 0) + + return parsers diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py index e4ae9f8fe..ebac5161a 100644 --- a/opendbc_repo/opendbc/car/toyota/interface.py +++ b/opendbc_repo/opendbc/car/toyota/interface.py @@ -59,13 +59,32 @@ class CarInterface(CarInterfaceBase): if Ecu.hybrid in found_ecus: ret.flags |= ToyotaFlags.HYBRID.value + # 0x343 should not be present on bus 2 on cars other than TSS2_CAR unless we are re-routing DSU + dsu_bypass = False + if (0x343 in fingerprint[2] or 0x4CB in fingerprint[2]) and candidate not in TSS2_CAR: + print("----------------------------------------------") + print("dragonpilot: DSU_BYPASS detected!") + print("----------------------------------------------") + # rick - disable for now, breaks TOYOTA_AVALON_2019 model tests. + # dsu_bypass = True + # ret.flags |= ToyotaFlags.DSU_BYPASS.value + + if 0x23 in fingerprint[0]: + print("----------------------------------------------") + print("dragonpilot: ZSS detected!") + print("----------------------------------------------") + ret.flags |= ToyotaFlags.ZSS.value + if candidate == CAR.TOYOTA_PRIUS: stop_and_go = True # Only give steer angle deadzone to for bad angle sensor prius for fw in car_fw: if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': - ret.steerActuatorDelay = 0.25 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg=0.2) + if ret.flags & ToyotaFlags.ZSS.value: + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + else: + ret.steerActuatorDelay = 0.25 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg=0.2) elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RX_TSS2): stop_and_go = True @@ -115,6 +134,27 @@ class CarInterface(CarInterfaceBase): if alpha_long and candidate in RADAR_ACC_CAR: ret.flags |= ToyotaFlags.DISABLE_RADAR.value + # RADAR_ACC_CAR = CHR TSS2 / RAV4 TSS2 + # NO_DSU_CAR = CAMRY / CHR + if 0x2FF in fingerprint[0] or 0x2AA in fingerprint[0]: + print("----------------------------------------------") + print("dragonpilot: RADAR_FILTER detected!") + print("----------------------------------------------") + ret.alphaLongitudinalAvailable = False + ret.flags |= ToyotaFlags.RADAR_FILTER.value | ToyotaFlags.DISABLE_RADAR.value + + sdsu_active = False + if not (candidate in (RADAR_ACC_CAR | NO_DSU_CAR)) and 0x2FF in fingerprint[0]: + print("----------------------------------------------") + print("dragonpilot: SDSU detected!") + print("----------------------------------------------") + + sdsu_active = True + stop_and_go = True + + ret.flags |= ToyotaFlags.SDSU.value + ret.alphaLongitudinalAvailable = False + # openpilot longitudinal enabled by default: # - cars w/ DSU disconnected # - TSS2 cars with camera sending ACC_CONTROL where we can block it @@ -122,7 +162,13 @@ class CarInterface(CarInterfaceBase): # - TSS2 radar ACC cars (disables radar) ret.openpilotLongitudinalControl = (candidate in (TSS2_CAR - RADAR_ACC_CAR) or - bool(ret.flags & ToyotaFlags.DISABLE_RADAR.value)) + bool(ret.flags & ToyotaFlags.DISABLE_RADAR.value) or \ + dsu_bypass) or \ + sdsu_active + + if dp_params & structs.DPFlags.ToyotaStockLon: + ret.openpilotLongitudinalControl = False + ret.alphaLongitudinalAvailable = False ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR @@ -144,12 +190,19 @@ class CarInterface(CarInterfaceBase): if ret.flags & ToyotaFlags.HYBRID.value: ret.longitudinalActuatorDelay = 0.05 + if dp_params & structs.DPFlags.ToyotaLockCtrl: + ret.flags |= ToyotaFlags.LOCK_CTRL.value + ret.safetyConfigs[0].safetyParam |= ToyotaSafetyFlags.LOCK_CTRL.value + + if dp_params & structs.DPFlags.ToyotaTSS1SnG: + ret.flags |= ToyotaFlags.TSS1_SNG.value + return ret @staticmethod def init(CP, can_recv, can_send, communication_control=None): # disable radar if alpha longitudinal toggled on radar-ACC car - if CP.flags & ToyotaFlags.DISABLE_RADAR.value: + if not CP.flags & ToyotaFlags.RADAR_FILTER.value and CP.flags & ToyotaFlags.DISABLE_RADAR.value: if communication_control is None: communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL]) disable_ecu(can_recv, can_send, bus=0, addr=0x750, sub_addr=0xf, com_cont_req=communication_control) diff --git a/opendbc_repo/opendbc/car/toyota/values.py b/opendbc_repo/opendbc/car/toyota/values.py index b27cb64a1..6ab108c48 100644 --- a/opendbc_repo/opendbc/car/toyota/values.py +++ b/opendbc_repo/opendbc/car/toyota/values.py @@ -57,11 +57,14 @@ class ToyotaSafetyFlags(IntFlag): LTA = (4 << 8) SECOC = (8 << 8) UNSUPPORTED_DSU = (16 << 8) # dp - use DSU_CRUISE (0x365) for ACC main instead of PCM_CRUISE_2 (0x1D3) + LOCK_CTRL = (32 << 8) class ToyotaFlags(IntFlag): # Detected flags HYBRID = 1 + # use legacy id + SDSU = 2 DISABLE_RADAR = 4 # Static flags @@ -81,6 +84,11 @@ class ToyotaFlags(IntFlag): SNG_WITHOUT_DSU_DEPRECATED = 512 ALKA = 2 ** 12 + LOCK_CTRL = 2 ** 13 + TSS1_SNG = 2 ** 14 + RADAR_FILTER = 2 ** 15 + DSU_BYPASS = 2 ** 16 + ZSS = 2 ** 17 def dbc_dict(pt, radar): return {Bus.pt: pt, Bus.radar: radar} diff --git a/opendbc_repo/opendbc/car/toyota/zss.py b/opendbc_repo/opendbc/car/toyota/zss.py new file mode 100644 index 000000000..62f09233a --- /dev/null +++ b/opendbc_repo/opendbc/car/toyota/zss.py @@ -0,0 +1,81 @@ +from opendbc.car.toyota.values import ToyotaFlags +from opendbc.can.parser import CANParser + +ANGLE_DIFF_THRESHOLD = 4.0 +THRESHOLD_COUNT = 10 + +class ZSS: + + def __init__(self, flags: int): + self.enabled = flags & ToyotaFlags.ZSS.value + # self._alka_enabled = flags & ToyotaFlags.ALKA.value + + self._offset_compute_once = True + # self._alka_active_prev = False + self._cruise_active_prev = False + self._offset = 0. + self._threshold_count = 0 + self._zss_value = 0. + self._print_allow = True + + def set_values(self, cp: CANParser): + self._zss_value = cp.vl["SECONDARY_STEER_ANGLE"]["ZORRO_STEER"] + + def get_enabled(self): + return self.enabled + + def get_steering_angle_deg(self, cruise_active, stock_steering_angle_deg): + # off, fall back to stock + if not self.enabled: + return stock_steering_angle_deg + + # when lka control is off, use stock + # alka_active = self._is_alka_active(main_on) + if not cruise_active: # and not alka_active: + return stock_steering_angle_deg + + # lka just activated + # if not self._offset_compute_once and ((alka_active and not self._alka_active_prev) or (cruise_active and not self._cruise_active_prev)): + if not self._offset_compute_once and (cruise_active and not self._cruise_active_prev): + self._threshold_count = 0 + self._offset_compute_once = True + self._print_allow = True + + # self._alka_active_prev = alka_active + self._cruise_active_prev = cruise_active + + # compute offset when required + if self._offset_compute_once: + self._offset_compute_once = not self._compute_offset(stock_steering_angle_deg) + + # error checking + if self._threshold_count >= THRESHOLD_COUNT: + if self._print_allow: + print("ZSS: Too many large diff, fallback to stock.") + self._print_allow = False + return stock_steering_angle_deg + + if self._offset_compute_once: + print("ZSS: Compute offset required, fallback to stock.") + return stock_steering_angle_deg + + zss_steering_angle_deg = self._zss_value - self._offset + angle_diff = abs(stock_steering_angle_deg - zss_steering_angle_deg) + if angle_diff > ANGLE_DIFF_THRESHOLD: + print(f"ZSS: Diff too large ({angle_diff}), fallback to stock. ") + # if self._is_alka_active(main_on) or cruise_active: + if cruise_active: + self._threshold_count += 1 + return stock_steering_angle_deg + + return zss_steering_angle_deg + + # def _is_alka_active(self, main_on): + # return self._alka_enabled and main_on != 0 + + def _compute_offset(self, steering_angle_deg): + if abs(steering_angle_deg) > 1e-3 and abs(self._zss_value) > 1e-3: + self._offset = self._zss_value - steering_angle_deg + print(f"ZSS: offset computed: {self._offset}") + return True + return False diff --git a/opendbc_repo/opendbc/car/volkswagen/carcontroller.py b/opendbc_repo/opendbc/car/volkswagen/carcontroller.py index 387a10fa3..e1b5c4fa6 100644 --- a/opendbc_repo/opendbc/car/volkswagen/carcontroller.py +++ b/opendbc_repo/opendbc/car/volkswagen/carcontroller.py @@ -31,6 +31,9 @@ class CarController(CarControllerBase): self.eps_timer_soft_disable_alert = False self.hca_frame_timer_running = 0 self.hca_frame_same_torque = 0 + self._dp_vag_a0_sng = bool(self.CP.flags & VolkswagenFlags.A0SnG) + self.dp_vag_pq_steering_patch = 7 if CP.flags & VolkswagenFlags.PQSteeringPatch else 5 + self.dp_avoid_eps_lockout = CP.flags & VolkswagenFlags.AVOID_EPS_LOCKOUT def update(self, CC, CS, now_nanos): actuators = CC.actuators @@ -50,7 +53,13 @@ class CarController(CarControllerBase): # 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)) + if self.dp_avoid_eps_lockout: + #根據速度縮放new_torque扭力上限 + torque_scale = np.interp(CS.out.vEgo, [0.4, 3.5, 4.0], [0.8, 0.95, 1.0]) + scaled_steer_max = self.CCP.STEER_MAX * torque_scale + new_torque = int(round(actuators.torque * scaled_steer_max)) + else: + 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: @@ -70,7 +79,7 @@ class CarController(CarControllerBase): 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)) + can_sends.append(self.CCS.create_steering_control(self.packer_pt, self.CAN.pt, apply_torque, hca_enabled, self.dp_vag_pq_steering_patch)) if self.CP.flags & VolkswagenFlags.STOCK_HCA_PRESENT: # Pacify VW Emergency Assist driver inactivity detection by changing its view of driver steering input torque @@ -119,11 +128,22 @@ class CarController(CarControllerBase): lead_distance, hud_control.leadDistanceBars)) # **** Stock ACC Button Controls **************************************** # + if self._dp_vag_a0_sng: + if self.CP.pcmCruise and CS.gra_stock_values["COUNTER"] != self.gra_acc_counter_last: + standing_resume_spam = CS.out.standstill + spam_window = self.frame % 50 < 15 - gra_send_ready = self.CP.pcmCruise and CS.gra_stock_values["COUNTER"] != self.gra_acc_counter_last - if gra_send_ready and (CC.cruiseControl.cancel or CC.cruiseControl.resume): - can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, self.CAN.ext, CS.gra_stock_values, - cancel=CC.cruiseControl.cancel, resume=CC.cruiseControl.resume)) + send_cancel = CC.cruiseControl.cancel + send_resume = CC.cruiseControl.resume or (standing_resume_spam and spam_window) + + if send_cancel or send_resume: + can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, self.CAN.ext, CS.gra_stock_values, + cancel=send_cancel, resume=send_resume)) + else: + gra_send_ready = self.CP.pcmCruise and CS.gra_stock_values["COUNTER"] != self.gra_acc_counter_last + if gra_send_ready and (CC.cruiseControl.cancel or CC.cruiseControl.resume): + can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, self.CAN.ext, CS.gra_stock_values, + cancel=CC.cruiseControl.cancel, resume=CC.cruiseControl.resume)) new_actuators = actuators.as_builder() new_actuators.torque = self.apply_torque_last / self.CCP.STEER_MAX diff --git a/opendbc_repo/opendbc/car/volkswagen/interface.py b/opendbc_repo/opendbc/car/volkswagen/interface.py index b20c9b42d..9ba474b45 100644 --- a/opendbc_repo/opendbc/car/volkswagen/interface.py +++ b/opendbc_repo/opendbc/car/volkswagen/interface.py @@ -35,7 +35,7 @@ class CarInterface(CarInterfaceBase): # It is documented in a four-part blog series: # https://blog.willemmelching.nl/carhacking/2022/01/02/vw-part1/ # Panda ALLOW_DEBUG firmware required. - ret.dashcamOnly = True + # ret.dashcamOnly = True elif ret.flags & VolkswagenFlags.MLB: # Set global MLB parameters @@ -106,4 +106,13 @@ class CarInterface(CarInterfaceBase): safety_configs.insert(0, get_safety_config(structs.CarParams.SafetyModel.noOutput)) ret.safetyConfigs = safety_configs + if dp_params & structs.DPFlags.VagA0SnG: + ret.flags |= VolkswagenFlags.A0SnG.value + + if ret.flags & VolkswagenFlags.PQ and dp_params & structs.DPFlags.VAGPQSteeringPatch: + ret.flags |= VolkswagenFlags.PQSteeringPatch.value + + if dp_params & structs.DPFlags.VagAvoidEPSLockout: + ret.flags |= VolkswagenFlags.AVOID_EPS_LOCKOUT.value + return ret diff --git a/opendbc_repo/opendbc/car/volkswagen/mlbcan.py b/opendbc_repo/opendbc/car/volkswagen/mlbcan.py index dc3b717e9..b781ed55f 100644 --- a/opendbc_repo/opendbc/car/volkswagen/mlbcan.py +++ b/opendbc_repo/opendbc/car/volkswagen/mlbcan.py @@ -2,7 +2,7 @@ from opendbc.car.volkswagen.mqbcan import (volkswagen_mqb_meb_checksum, xor_chec create_lka_hud_control as mqb_create_lka_hud_control) # TODO: Parameterize the hca control type (5 vs 7) and consolidate with MQB (and PQ?) -def create_steering_control(packer, bus, apply_steer, lkas_enabled): +def create_steering_control(packer, bus, apply_steer, lkas_enabled, dp_vag_pq_steering_patch): values = { "HCA_01_Status_HCA": 7 if lkas_enabled else 3, "HCA_01_LM_Offset": abs(apply_steer), diff --git a/opendbc_repo/opendbc/car/volkswagen/mqbcan.py b/opendbc_repo/opendbc/car/volkswagen/mqbcan.py index 78a2eeff6..a2980f271 100644 --- a/opendbc_repo/opendbc/car/volkswagen/mqbcan.py +++ b/opendbc_repo/opendbc/car/volkswagen/mqbcan.py @@ -1,7 +1,7 @@ from opendbc.car.crc import CRC8H2F -def create_steering_control(packer, bus, apply_torque, lkas_enabled): +def create_steering_control(packer, bus, apply_torque, lkas_enabled, dp_vag_pq_steering_patch): values = { "HCA_01_Status_HCA": 5 if lkas_enabled else 3, "HCA_01_LM_Offset": abs(apply_torque), diff --git a/opendbc_repo/opendbc/car/volkswagen/pqcan.py b/opendbc_repo/opendbc/car/volkswagen/pqcan.py index 138e4ec5a..950cecdf8 100644 --- a/opendbc_repo/opendbc/car/volkswagen/pqcan.py +++ b/opendbc_repo/opendbc/car/volkswagen/pqcan.py @@ -1,8 +1,8 @@ -def create_steering_control(packer, bus, apply_torque, lkas_enabled): +def create_steering_control(packer, bus, apply_torque, lkas_enabled, dp_vag_pq_steering_patch = 5): values = { "LM_Offset": abs(apply_torque), "LM_OffSign": 1 if apply_torque < 0 else 0, - "HCA_Status": 5 if (lkas_enabled and apply_torque != 0) else 3, + "HCA_Status": dp_vag_pq_steering_patch if (lkas_enabled and apply_torque != 0) else 3, "Vib_Freq": 16, } diff --git a/opendbc_repo/opendbc/car/volkswagen/values.py b/opendbc_repo/opendbc/car/volkswagen/values.py index 513affdcb..781ab9ad2 100644 --- a/opendbc_repo/opendbc/car/volkswagen/values.py +++ b/opendbc_repo/opendbc/car/volkswagen/values.py @@ -190,6 +190,9 @@ class VolkswagenFlags(IntFlag): PQ = 2 MLB = 8 + A0SnG = 2 ** 10 + PQSteeringPatch = 2 ** 11 + AVOID_EPS_LOCKOUT = 2 ** 12 @dataclass class VolkswagenMLBPlatformConfig(PlatformConfig): diff --git a/opendbc_repo/opendbc/dbc/toyota_zss.dbc b/opendbc_repo/opendbc/dbc/toyota_zss.dbc new file mode 100644 index 000000000..551b83ab2 --- /dev/null +++ b/opendbc_repo/opendbc/dbc/toyota_zss.dbc @@ -0,0 +1,4 @@ +BO_ 35 SECONDARY_STEER_ANGLE: 8 XXX + SG_ ZORRO_STEER : 7|24@0- (0.004901594652,0) [-500|500] "" XXX + +CM_ "BO_ SECONDARY_STEER_ANGLE: ZSS is a high-precision steering angle sensor that can replace the lower resolution sensor in most TSS1 Toyotas. Learn more: https://github.com/commaai/openpilot/wiki/Toyota-Lexus#zorro-steering-sensor-zss"; diff --git a/opendbc_repo/opendbc/safety/modes/subaru.h b/opendbc_repo/opendbc/safety/modes/subaru.h index aab96090a..cfe4ed169 100644 --- a/opendbc_repo/opendbc/safety/modes/subaru.h +++ b/opendbc_repo/opendbc/safety/modes/subaru.h @@ -72,6 +72,8 @@ static bool subaru_gen2 = false; static bool subaru_longitudinal = false; +// rick +static bool subaru_impreza_2018 = false; static uint32_t subaru_get_checksum(const CANPacket_t *msg) { return (uint8_t)msg->data[0]; @@ -140,6 +142,8 @@ static void subaru_rx_hook(const CANPacket_t *msg) { static bool subaru_tx_hook(const CANPacket_t *msg) { const TorqueSteeringLimits SUBARU_STEERING_LIMITS = SUBARU_STEERING_LIMITS_GENERATOR(2047, 50, 70); const TorqueSteeringLimits SUBARU_GEN2_STEERING_LIMITS = SUBARU_STEERING_LIMITS_GENERATOR(1000, 40, 40); + // rick + const TorqueSteeringLimits SUBARU_IMPREZA_2018_STEERING_LIMITS = SUBARU_STEERING_LIMITS_GENERATOR(3071, 60, 60); const LongitudinalLimits SUBARU_LONG_LIMITS = { .min_gas = 808, // appears to be engine braking @@ -161,7 +165,11 @@ static bool subaru_tx_hook(const CANPacket_t *msg) { bool steer_req = (msg->data[3] >> 5) & 1U; - const TorqueSteeringLimits limits = subaru_gen2 ? SUBARU_GEN2_STEERING_LIMITS : SUBARU_STEERING_LIMITS; + // const TorqueSteeringLimits limits = subaru_gen2 ? SUBARU_GEN2_STEERING_LIMITS : SUBARU_STEERING_LIMITS; + // rick + const TorqueSteeringLimits limits = subaru_gen2 ? SUBARU_GEN2_STEERING_LIMITS : + (subaru_impreza_2018 ? SUBARU_IMPREZA_2018_STEERING_LIMITS: SUBARU_STEERING_LIMITS); + violation |= steer_torque_cmd_checks(desired_torque, steer_req, limits); } @@ -244,6 +252,10 @@ static safety_config subaru_init(uint16_t param) { subaru_gen2 = GET_FLAG(param, SUBARU_PARAM_GEN2); + // rick + const uint16_t SUBARU_PARAM_IMPREZA_2018 = 8; + subaru_impreza_2018 = GET_FLAG(param, SUBARU_PARAM_IMPREZA_2018); + #ifdef ALLOW_DEBUG const uint16_t SUBARU_PARAM_LONGITUDINAL = 2; subaru_longitudinal = GET_FLAG(param, SUBARU_PARAM_LONGITUDINAL); diff --git a/opendbc_repo/opendbc/safety/modes/toyota.h b/opendbc_repo/opendbc/safety/modes/toyota.h index 11db60939..1793cda6a 100644 --- a/opendbc_repo/opendbc/safety/modes/toyota.h +++ b/opendbc_repo/opendbc/safety/modes/toyota.h @@ -65,6 +65,9 @@ static int toyota_dbc_eps_torque_factor = 100; // conversion factor for STEER_ // dp - use DSU_CRUISE (0x365) for ACC main instead of PCM_CRUISE_2 (0x1D3) static bool toyota_unsupported_dsu = false; +// lock ctrl +static bool toyota_lock_ctrl = false; + static uint32_t toyota_compute_checksum(const CANPacket_t *msg) { int len = GET_LEN(msg); uint8_t checksum = (uint8_t)(msg->addr) + (uint8_t)((unsigned int)(msg->addr) >> 8U) + (uint8_t)(len); @@ -335,7 +338,8 @@ static bool toyota_tx_hook(const CANPacket_t *msg) { } // UDS: Only tester present ("\x0F\x02\x3E\x00\x00\x00\x00\x00") allowed on diagnostics address - if (msg->addr == 0x750U) { + // dp - lock_ctrl bypasses this restriction to allow any UDS message + if ((msg->addr == 0x750U) && !toyota_lock_ctrl) { // this address is sub-addressed. only allow tester present to radar (0xF) bool invalid_uds_msg = (GET_BYTES(msg, 0, 4) != 0x003E020FU) || (GET_BYTES(msg, 4, 4) != 0x0U); if (invalid_uds_msg) { @@ -382,11 +386,16 @@ static safety_config toyota_init(uint16_t param) { const uint32_t TOYOTA_PARAM_UNSUPPORTED_DSU = 16UL << TOYOTA_PARAM_OFFSET; toyota_unsupported_dsu = GET_FLAG(param, TOYOTA_PARAM_UNSUPPORTED_DSU); + // lock ctrl + const uint32_t TOYOTA_PARAM_LOCK_CTRL = 32UL << TOYOTA_PARAM_OFFSET; + toyota_lock_ctrl = GET_FLAG(param, TOYOTA_PARAM_LOCK_CTRL); + toyota_alt_brake = GET_FLAG(param, TOYOTA_PARAM_ALT_BRAKE); toyota_stock_longitudinal = GET_FLAG(param, TOYOTA_PARAM_STOCK_LONGITUDINAL); toyota_lta = GET_FLAG(param, TOYOTA_PARAM_LTA); toyota_dbc_eps_torque_factor = param & TOYOTA_EPS_FACTOR; + // upstream TX selection + dp features via tx_ext (lock_ctrl, long_filter) safety_config ret; if (toyota_secoc) { if (toyota_stock_longitudinal) { @@ -433,6 +442,19 @@ static safety_config toyota_init(uint16_t param) { return ret; } +// dp - tx_ext hook for dragonpilot-specific TX messages (lock_ctrl) +static TxExtResult toyota_tx_ext_hook(const CANPacket_t *msg) { + TxExtResult result = {.allowed = false, .check_relay = false}; + int len = GET_LEN(msg); + + // dp - lock_ctrl: allow any UDS message on 0x750 (stock long already has tester present only) + if ((msg->addr == 0x750U) && toyota_lock_ctrl && (msg->bus == 0U) && (len == 8)) { + result.allowed = true; + } + + return result; +} + // dp - rx_ext hook for optional messages (placeholder) static void toyota_rx_ext_hook(const CANPacket_t *msg) { if (alka_allowed && ((alternative_experience & ALT_EXP_ALKA) != 0)) { @@ -457,6 +479,7 @@ const safety_hooks toyota_hooks = { .rx = toyota_rx_hook, .rx_ext = toyota_rx_ext_hook, .tx = toyota_tx_hook, + .tx_ext = toyota_tx_ext_hook, .get_checksum = toyota_get_checksum, .compute_checksum = toyota_compute_checksum, .get_quality_flag_valid = toyota_get_quality_flag_valid, diff --git a/opendbc_repo/opendbc/safety/tests/test_tx_ext.py b/opendbc_repo/opendbc/safety/tests/test_tx_ext.py new file mode 100644 index 000000000..8b7b5ba5e --- /dev/null +++ b/opendbc_repo/opendbc/safety/tests/test_tx_ext.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +""" +Tests for tx_ext hook functionality. + +tx_ext allows additional TX messages beyond the base whitelist, with per-message +check_relay control. This is used for dragonpilot-specific features: +- lock_ctrl: allows any UDS message on 0x750 (not just tester present) +""" +import unittest + +from opendbc.car.toyota.values import ToyotaSafetyFlags +from opendbc.car.structs import CarParams +from opendbc.safety.tests.libsafety import libsafety_py +from opendbc.safety.tests.common import CANPackerSafety + + +class TestTxExtBase(unittest.TestCase): + """Base test class for tx_ext functionality.""" + + TX_MSGS = [] # Required by test_tx_hook_on_wrong_safety_mode + + safety: libsafety_py.LibSafety + packer: CANPackerSafety + + @classmethod + def setUpClass(cls): + if cls.__name__ == "TestTxExtBase": + raise unittest.SkipTest("Base class") + + def _tx(self, msg): + return self.safety.safety_tx_hook(msg) + + def _rx(self, msg): + return self.safety.safety_rx_hook(msg) + + +class TestTxExtToyotaLockCtrl(TestTxExtBase): + """Test lock_ctrl feature for Toyota. + + lock_ctrl allows any UDS message on 0x750, not just tester present. + This is used for door lock control via diagnostic messages. + """ + + EPS_SCALE = 73 + + def setUp(self): + self.packer = CANPackerSafety("toyota_nodsu_pt_generated") + self.safety = libsafety_py.libsafety + + def _uds_msg(self, data: bytes): + """Create a UDS message on 0x750.""" + return libsafety_py.make_CANPacket(0x750, 0, data) + + def _tester_present_msg(self): + """Valid tester present message (always allowed without lock_ctrl).""" + return self._uds_msg(b"\x0F\x02\x3E\x00\x00\x00\x00\x00") + + def _lock_ctrl_msg(self): + """Example lock control message (only allowed with lock_ctrl).""" + return self._uds_msg(b"\x0F\x03\x30\x01\x00\x00\x00\x00") + + def _random_uds_msg(self): + """Random UDS message (only allowed with lock_ctrl).""" + return self._uds_msg(b"\x0F\x05\xAA\xBB\xCC\xDD\x00\x00") + + def test_lock_ctrl_disabled_only_tester_present(self): + """Without lock_ctrl, only tester present is allowed on 0x750.""" + # OP long mode (0x750 in whitelist), no lock_ctrl + self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, self.EPS_SCALE) + self.safety.init_tests() + + # Tester present should be allowed + self.assertTrue(self._tx(self._tester_present_msg())) + + # Other UDS messages should be blocked + self.assertFalse(self._tx(self._lock_ctrl_msg())) + self.assertFalse(self._tx(self._random_uds_msg())) + + def test_lock_ctrl_enabled_allows_any_uds(self): + """With lock_ctrl, any UDS message is allowed on 0x750.""" + # OP long mode with lock_ctrl + param = self.EPS_SCALE | ToyotaSafetyFlags.LOCK_CTRL + self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, param) + self.safety.init_tests() + + # All UDS messages should be allowed + self.assertTrue(self._tx(self._tester_present_msg())) + self.assertTrue(self._tx(self._lock_ctrl_msg())) + self.assertTrue(self._tx(self._random_uds_msg())) + + def test_lock_ctrl_stock_long_allows_uds(self): + """With lock_ctrl + stock_long, 0x750 is allowed via tx_ext.""" + # Stock long mode (0x750 NOT in base whitelist) with lock_ctrl + param = self.EPS_SCALE | ToyotaSafetyFlags.STOCK_LONGITUDINAL | ToyotaSafetyFlags.LOCK_CTRL + self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, param) + self.safety.init_tests() + + # All UDS messages should be allowed via tx_ext + self.assertTrue(self._tx(self._tester_present_msg())) + self.assertTrue(self._tx(self._lock_ctrl_msg())) + self.assertTrue(self._tx(self._random_uds_msg())) + + def test_lock_ctrl_stock_long_without_flag_blocks_uds(self): + """Without lock_ctrl, stock_long blocks all 0x750 messages.""" + # Stock long mode without lock_ctrl + param = self.EPS_SCALE | ToyotaSafetyFlags.STOCK_LONGITUDINAL + self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, param) + self.safety.init_tests() + + # 0x750 is not in stock long whitelist, should be blocked + self.assertFalse(self._tx(self._tester_present_msg())) + self.assertFalse(self._tx(self._lock_ctrl_msg())) + + +class TestTxExtRelayCheck(TestTxExtBase): + """Test check_relay behavior for tx_ext messages. + + Messages allowed via tx_ext can have check_relay=true or false. + - lock_ctrl 0x750: check_relay=false (no relay check) + """ + + EPS_SCALE = 73 + + def setUp(self): + self.packer = CANPackerSafety("toyota_nodsu_pt_generated") + self.safety = libsafety_py.libsafety + + def _uds_msg_raw(self): + """Create raw UDS message for relay testing.""" + return libsafety_py.make_CANPacket(0x750, 0, b"\x00" * 8) + + def _wait_for_relay_timeout(self): + """Wait for relay transition timeout (1 second = 100 ticks at 100Hz).""" + for _ in range(200): + self.safety.safety_tick_current_safety_config() + + def test_lock_ctrl_no_relay_malfunction(self): + """lock_ctrl 0x750 should not trigger relay malfunction when received.""" + param = self.EPS_SCALE | ToyotaSafetyFlags.STOCK_LONGITUDINAL | ToyotaSafetyFlags.LOCK_CTRL + self.safety.set_safety_hooks(CarParams.SafetyModel.toyota, param) + self.safety.init_tests() + + self._wait_for_relay_timeout() + + # Receiving 0x750 should NOT trigger relay malfunction + # (check_relay=false for lock_ctrl) + self.assertFalse(self.safety.get_relay_malfunction()) + self._rx(self._uds_msg_raw()) + self.assertFalse(self.safety.get_relay_malfunction()) + + +class TestTxExtDisabledBrands(unittest.TestCase): + """Test that tx_ext returns not allowed for brands without tx_ext hook.""" + + TX_MSGS = [] # Required by test_tx_hook_on_wrong_safety_mode + + def test_hyundai_no_tx_ext(self): + """Hyundai should not have any tx_ext allowances.""" + safety = libsafety_py.libsafety + safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0) + safety.init_tests() + + # Random message should not be allowed via tx_ext + msg = libsafety_py.make_CANPacket(0x750, 0, b"\x00" * 8) + self.assertFalse(safety.safety_tx_hook(msg)) + + def test_honda_no_tx_ext(self): + """Honda should not have any tx_ext allowances.""" + safety = libsafety_py.libsafety + safety.set_safety_hooks(CarParams.SafetyModel.hondaNidec, 0) + safety.init_tests() + + # Random message should not be allowed via tx_ext + msg = libsafety_py.make_CANPacket(0x750, 0, b"\x00" * 8) + self.assertFalse(safety.safety_tx_hook(msg)) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index f4c7010f0..4f4db4fc9 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -105,6 +105,24 @@ class Car: if self.params.get_bool("dp_lat_alka"): dp_params |= structs.DPFlags.LatALKA + if self.params.get_bool("dp_toyota_door_auto_lock_unlock"): + dp_params |= structs.DPFlags.ToyotaLockCtrl + + if self.params.get_bool("dp_toyota_tss1_sng"): + dp_params |= structs.DPFlags.ToyotaTSS1SnG + + if self.params.get_bool("dp_toyota_stock_lon"): + dp_params |= structs.DPFlags.ToyotaStockLon + + if self.params.get_bool("dp_vag_a0_sng"): + dp_params |= structs.DPFlags.VagA0SnG + + if self.params.get_bool("dp_vag_pq_steering_patch"): + dp_params |= structs.DPFlags.VAGPQSteeringPatch + + if self.params.get_bool("dp_vag_avoid_eps_lockout"): + dp_params |= structs.DPFlags.VagAvoidEPSLockout + dp_fingerprint = str(self.params.get("dp_dev_model_selected") or "") self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, dp_params, cached_params, dp_fingerprint=dp_fingerprint) self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP)