diff --git a/common/libcommon.a b/common/libcommon.a index aca92d2f6..a9b709e90 100644 Binary files a/common/libcommon.a and b/common/libcommon.a differ diff --git a/common/params.py b/common/params.py index f688bfa8b..4b740354f 100644 --- a/common/params.py +++ b/common/params.py @@ -1,73 +1,255 @@ -from openpilot.common.params_pyx import Params as _Params, ParamKeyFlag, ParamKeyType, UnknownKeyName -assert _Params -assert ParamKeyFlag -assert ParamKeyType -assert UnknownKeyName +from __future__ import annotations +from enum import IntEnum, IntFlag +from pathlib import Path +import tempfile -class Params(_Params): - def get(self, key, block=False, return_default=False, encoding=None, default=None): - try: - value = super().get(key, block=block, return_default=return_default) - except UnknownKeyName: - return default - if value is None: - return default - if encoding is not None and isinstance(value, bytes): +try: + from openpilot.common.params_pyx import Params as _Params, ParamKeyFlag, ParamKeyType, UnknownKeyName +except Exception: + class UnknownKeyName(KeyError): + pass + + class ParamKeyFlag(IntFlag): + PERSISTENT = 0x02 + CLEAR_ON_MANAGER_START = 0x04 + CLEAR_ON_ONROAD_TRANSITION = 0x08 + CLEAR_ON_OFFROAD_TRANSITION = 0x10 + DONT_LOG = 0x20 + DEVELOPMENT_ONLY = 0x40 + CLEAR_ON_IGNITION_ON = 0x80 + ALL = 0xFFFFFFFF + + class ParamKeyType(IntEnum): + STRING = 0 + BOOL = 1 + INT = 2 + FLOAT = 3 + TIME = 4 + JSON = 5 + BYTES = 6 + + def _load_key_types() -> dict[str, ParamKeyType]: + key_types: dict[str, ParamKeyType] = {} + params_keys = Path(__file__).with_name("params_keys.h") + if not params_keys.exists(): + return key_types + + for line in params_keys.read_text(encoding="utf-8", errors="ignore").splitlines(): + if not line.lstrip().startswith('{"'): + continue + + parts = line.split('"') + if len(parts) < 2: + continue + + key = parts[1] + for type_name in ParamKeyType.__members__: + if f", {type_name}" in line: + key_types[key] = ParamKeyType[type_name] + break + else: + key_types[key] = ParamKeyType.STRING + + return key_types + + _KEY_TYPES = _load_key_types() + _PERSISTENT_STORE: dict[str, object] = {} + _MEMORY_STORE: dict[str, object] = {} + + class Params: + def __init__(self, d: str | None = None, memory: bool = False, return_defaults: bool = False): + self.d = d if d is not None else "" + self.m = memory + self.return_defaults = return_defaults + self._store = _MEMORY_STORE if memory else _PERSISTENT_STORE + + def __reduce__(self): + return type(self), (self.d, self.m, self.return_defaults) + + def clear_all(self, tx_flag=ParamKeyFlag.ALL): + self._store.clear() + + def check_key(self, key): + if isinstance(key, bytes): + key = key.decode("utf-8") + return str(key) + + def _coerce_bool(self, value) -> bool: + if isinstance(value, bool): + return value + if isinstance(value, (int, float)): + return bool(value) + if isinstance(value, bytes): + return value == b"1" + if isinstance(value, str): + return value.strip().lower() not in ("", "0", "false", "none", "null") + return bool(value) + + def _coerce_value(self, key: str, value): + value_type = self.get_type(key) + if value_type == ParamKeyType.BOOL: + return self._coerce_bool(value) + if value_type == ParamKeyType.INT: + return int(float(value)) + if value_type == ParamKeyType.FLOAT: + return float(value) + if value_type == ParamKeyType.BYTES and isinstance(value, str): + return value.encode("utf-8") + return value + + def get(self, key, block: bool = False, return_default: bool = False, encoding=None, default=None): + key = self.check_key(key) + value = self._store.get(key, default) + if value is None: + return default + if encoding is not None and isinstance(value, bytes): + try: + return value.decode(encoding) + except Exception: + return value.decode("utf-8", errors="replace") + return value + + def get_bool(self, key, block: bool = False, default: bool = False): + value = self.get(key, block=block, return_default=True, default=default) + return self._coerce_bool(value) + + def get_int(self, key, block: bool = False, return_default: bool = False, default: int = 0): + value = self.get(key, block=block, return_default=return_default, encoding="utf-8", default=default) + if value is None or value == "": + return default try: - return value.decode(encoding) - except Exception: - return value.decode("utf-8", errors="replace") - return value + return int(float(value)) + except (TypeError, ValueError): + return default - def get_bool(self, key, block=False, default=False): - try: - result = super().get(key, block=block, return_default=True) - if result is None: + def get_float(self, key, block: bool = False, return_default: bool = False, default: float = 0.0): + value = self.get(key, block=block, return_default=return_default, encoding="utf-8", default=default) + if value is None or value == "": + return default + try: + return float(value) + except (TypeError, ValueError): + return default + + def put(self, key, dat): + key = self.check_key(key) + self._store[key] = self._coerce_value(key, dat) + + def put_bool(self, key, val: bool): + self.put(key, bool(val)) + + def put_nonblocking(self, key, dat): + self.put(key, dat) + + def put_bool_nonblocking(self, key, val: bool): + self.put_bool(key, val) + + def put_int(self, key, val): + self.put(key, int(val)) + + def put_float(self, key, val): + self.put(key, float(val)) + + def remove(self, key): + self._store.pop(self.check_key(key), None) + + def get_param_path(self, key: str = ""): + base = Path(tempfile.gettempdir()) / ("params_memory" if self.m else "params") + base.mkdir(parents=True, exist_ok=True) + return str(base / key) if key else str(base) + + def get_type(self, key): + return _KEY_TYPES.get(self.check_key(key), ParamKeyType.STRING) + + def all_keys(self): + return list(_KEY_TYPES) + + def get_default_value(self, key): + return None + + def cpp2python(self, key, value): + return self._coerce_value(self.check_key(key), value) + + def get_key_flag(self, key): + return ParamKeyFlag.PERSISTENT + + def get_stock_value(self, key): + return None + + def get_tuning_level(self, key): + return 0 + +else: + assert _Params + assert ParamKeyFlag + assert ParamKeyType + assert UnknownKeyName + + class Params(_Params): + def get(self, key, block=False, return_default=False, encoding=None, default=None): + try: + value = super().get(key, block=block, return_default=return_default) + except UnknownKeyName: + return default + if value is None: + return default + if encoding is not None and isinstance(value, bytes): + try: + return value.decode(encoding) + except Exception: + return value.decode("utf-8", errors="replace") + return value + + def get_bool(self, key, block=False, default=False): + try: + result = super().get(key, block=block, return_default=True) + if result is None: + return bool(default) + return bool(result) + except UnknownKeyName: return bool(default) - return bool(result) - except UnknownKeyName: - return bool(default) - def get_int(self, key, block=False, return_default=False, default=0): - val = self.get(key, block=block, return_default=return_default, encoding="utf-8") - if val is None or val == "": - return default - try: - return int(float(val)) - except ValueError: - return default + def get_int(self, key, block=False, return_default=False, default=0): + val = self.get(key, block=block, return_default=return_default, encoding="utf-8") + if val is None or val == "": + return default + try: + return int(float(val)) + except ValueError: + return default - def get_float(self, key, block=False, return_default=False, default=0.0): - val = self.get(key, block=block, return_default=return_default, encoding="utf-8") - if val is None or val == "": - return default - try: - return float(val) - except ValueError: - return default + def get_float(self, key, block=False, return_default=False, default=0.0): + val = self.get(key, block=block, return_default=return_default, encoding="utf-8") + if val is None or val == "": + return default + try: + return float(val) + except ValueError: + return default - def put_int(self, key, val): - t = self.get_type(key) - if t == ParamKeyType.FLOAT: - self.put(key, float(val)) - elif t == ParamKeyType.INT: - self.put(key, int(val)) - elif t == ParamKeyType.BOOL: - self.put(key, bool(val)) - else: - self.put(key, str(int(val))) + def put_int(self, key, val): + t = self.get_type(key) + if t == ParamKeyType.FLOAT: + self.put(key, float(val)) + elif t == ParamKeyType.INT: + self.put(key, int(val)) + elif t == ParamKeyType.BOOL: + self.put(key, bool(val)) + else: + self.put(key, str(int(val))) + + def put_float(self, key, val): + t = self.get_type(key) + if t == ParamKeyType.FLOAT: + self.put(key, float(val)) + elif t == ParamKeyType.INT: + self.put(key, int(val)) + elif t == ParamKeyType.BOOL: + self.put(key, bool(val)) + else: + self.put(key, str(float(val))) - def put_float(self, key, val): - t = self.get_type(key) - if t == ParamKeyType.FLOAT: - self.put(key, float(val)) - elif t == ParamKeyType.INT: - self.put(key, int(val)) - elif t == ParamKeyType.BOOL: - self.put(key, bool(val)) - else: - self.put(key, str(float(val))) if __name__ == "__main__": import sys diff --git a/common/params_keys.h b/common/params_keys.h index c9b2d6ee8..d15d396ac 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -59,6 +59,8 @@ inline static std::unordered_map keys = { {"GsmRoaming", {PERSISTENT, BOOL}}, {"HardwareSerial", {PERSISTENT, STRING}}, {"HasAcceptedTerms", {PERSISTENT, STRING, "0"}}, + {"HondaGasFactorParams", {PERSISTENT, FLOAT}}, + {"HondaWindFactorParams", {PERSISTENT, FLOAT}}, {"InstallDate", {PERSISTENT, TIME}}, {"IsDriverViewEnabled", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsEngaged", {PERSISTENT, BOOL}}, diff --git a/common/params_pyx.so b/common/params_pyx.so index f277b9b7c..a01e645f9 100755 Binary files a/common/params_pyx.so and b/common/params_pyx.so differ diff --git a/opendbc_repo/opendbc/car/honda/carcontroller.py b/opendbc_repo/opendbc/car/honda/carcontroller.py index 68ba5d32e..597ea458a 100644 --- a/opendbc_repo/opendbc/car/honda/carcontroller.py +++ b/opendbc_repo/opendbc/car/honda/carcontroller.py @@ -2,7 +2,8 @@ import math import numpy as np from opendbc.can import CANPacker -from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, rate_limit, make_tester_present_msg, structs +from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, create_gas_interceptor_command, rate_limit, make_tester_present_msg, structs +from opendbc.car.common.pid import PIDController from opendbc.car.honda import hondacan from opendbc.car.honda.values import ( CAR, @@ -16,6 +17,7 @@ from opendbc.car.honda.values import ( HondaFlags, ) from opendbc.car.interfaces import CarControllerBase +from openpilot.common.params import Params VisualAlert = structs.CarControl.HUDControl.VisualAlert LongCtrlState = structs.CarControl.Actuators.LongControlState @@ -206,6 +208,7 @@ class CarController(CarControllerBase): super().__init__(dbc_names, CP) self.packer = CANPacker(dbc_names[Bus.pt]) self.params = CarControllerParams(CP) + self.param_store = Params() self.CAN = hondacan.CanBus(CP) self.tja_control = CP.carFingerprint in HONDA_BOSCH_TJA_CONTROL @@ -225,10 +228,15 @@ class CarController(CarControllerBase): self.prev_torque_cmd = 0.0 self.steering_pressed_filter_s = 0.0 self.steering_pressed_robust_prev = False - self.bosch_gas_factor = 1.0 - self.bosch_wind_factor = 1.0 - self.bosch_wind_factor_before_brake = 0.0 + self.bosch_last_gas = 0.0 + self.bosch_gas_factor = self.param_store.get_float("HondaGasFactorParams", default=1.0) + self.bosch_wind_factor = self.param_store.get_float("HondaWindFactorParams", default=1.0) + self.bosch_wind_factor_before_brake = self.bosch_wind_factor + self.bosch_gas_factor_before_gasmax = self.bosch_gas_factor + self.bosch_wind_factor_before_gasmax = self.bosch_wind_factor self.pitch = 0.0 + self.brake_pid = PIDController(k_p=([0.0], [0.0]), k_i=([0.0], [0.5]), pos_limit=0.0, neg_limit=-2.0, rate=50) + self.brake_pid.reset() def _modified_civic_standard_active(self) -> bool: return self.CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH and bool(self.CP.flags & HondaFlags.EPS_MODIFIED) @@ -249,6 +257,7 @@ class CarController(CarControllerBase): hud_control = CC.hudControl hud_v_cruise = hud_control.setSpeed / CS.v_cruise_factor if hud_control.speedVisible else 255 pcm_cancel_cmd = CC.cruiseControl.cancel + gas_interceptor_command = 0.0 if len(CC.orientationNED) == 3: self.pitch = CC.orientationNED[1] hill_brake = math.sin(self.pitch) * ACCELERATION_DUE_TO_GRAVITY @@ -305,20 +314,21 @@ class CarController(CarControllerBase): # tester present - w/ no response (keeps radar disabled) if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and self.CP.openpilotLongitudinalControl: if self.frame % 10 == 0: - can_sends.append(make_tester_present_msg(0x18DAB0F1, 1, suppress_response=True)) + can_sends.append(make_tester_present_msg(0x18DAB0F1, self.CAN.pt, suppress_response=True)) # Send steering command. can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_torque, CC.latActive, self.tja_control)) # wind brake from air resistance decel at high speed - wind_brake = np.interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15]) + wind_brake = float(np.interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15])) + wind_brake_mps2 = get_honda_bosch_wind_brake_mps2(CS.out.vEgo) # all of this is only relevant for HONDA NIDEC max_accel = np.interp(CS.out.vEgo, self.params.NIDEC_MAX_ACCEL_BP, self.params.NIDEC_MAX_ACCEL_V) # TODO this 1.44 is just to maintain previous behavior pcm_speed_BP = [-wind_brake, -wind_brake * (3 / 4), 0.0, 0.5] # The Honda ODYSSEY seems to have different PCM_ACCEL # msgs, is it other cars too? - if not CC.longActive: + if self.CP.enableGasInterceptorDEPRECATED or not CC.longActive: pcm_speed = 0.0 pcm_accel = int(0.0) elif self.CP.carFingerprint in HONDA_NIDEC_ALT_PCM_ACCEL: @@ -345,27 +355,54 @@ class CarController(CarControllerBase): ts = self.frame * DT_CTRL if self.CP.carFingerprint in HONDA_BOSCH: - self.accel = float(np.clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX)) - gas_pedal_force = self.accel + if accel < 0.0 and CS.out.vEgo > 1e-3: + brake_addon = self.brake_pid.update(error=accel - CS.out.aEgo, speed=CS.out.vEgo) + target_accel = min(accel, accel + brake_addon) + else: + self.brake_pid.reset() + target_accel = accel + + self.accel = float(np.clip(target_accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX)) + gas_pedal_force = self.accel + hill_brake + if self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: - wind_brake_mps2 = get_honda_bosch_wind_brake_mps2(CS.out.vEgo) - if (actuators.longControlState == LongCtrlState.pid) and (not CS.out.gasPressed): - gas_pedal_force += wind_brake_mps2 * self.bosch_wind_factor + hill_brake - self.bosch_gas_factor, self.bosch_wind_factor, self.bosch_wind_factor_before_brake = update_honda_bosch_live_learning( - self.bosch_gas_factor, - self.bosch_wind_factor, - self.bosch_wind_factor_before_brake, - self.accel, - CS.out.aEgo, - gas_pedal_force, - wind_brake_mps2, - bool(CS.out.brakePressed), - CS.out.vEgo, - ) - else: - gas_pedal_force += wind_brake_mps2 + hill_brake + gas_pedal_force += wind_brake_mps2 * self.bosch_wind_factor + + if actuators.longControlState == LongCtrlState.pid and not CS.out.gasPressed: + gas_error = self.accel - CS.out.aEgo + + if gas_error != 0.0 and gas_pedal_force > 0.0: + if self.CP.carFingerprint == CAR.HONDA_INSIGHT: + gas_learn_speed = 150.0 + elif self.CP.carFingerprint in (CAR.ACURA_RDX_3G, CAR.ACURA_RDX_3G_MMR): + gas_learn_speed = 300.0 + else: + gas_learn_speed = 50.0 + self.bosch_gas_factor = float(np.clip(self.bosch_gas_factor + gas_error / gas_learn_speed * gas_pedal_force, 0.1, 3.0)) + + if gas_error != 0.0 and not CS.out.brakePressed and CS.out.vEgo > 0.0: + wind_learn_speed = 100.0 if self.CP.carFingerprint in (CAR.ACURA_RDX_3G, CAR.ACURA_RDX_3G_MMR) else 1000.0 + wind_adjust = 1.0 + wind_brake_mps2 / wind_learn_speed + if gas_error > 0.0: + self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor * wind_adjust, 0.1, 3.0)) + else: + self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor / wind_adjust, 0.1, 3.0)) + + if gas_pedal_force <= 0.0: + self.bosch_wind_factor = max(self.bosch_wind_factor, self.bosch_wind_factor_before_brake) + else: + self.bosch_wind_factor_before_brake = self.bosch_wind_factor + + if gas_pedal_force >= self.params.BOSCH_ACCEL_MAX: + self.bosch_gas_factor = min(self.bosch_gas_factor, self.bosch_gas_factor_before_gasmax) + self.bosch_wind_factor = min(self.bosch_wind_factor, self.bosch_wind_factor_before_gasmax) + else: + self.bosch_gas_factor_before_gasmax = self.bosch_gas_factor + self.bosch_wind_factor_before_gasmax = self.bosch_wind_factor self.gas = float(np.interp(gas_pedal_force * self.bosch_gas_factor, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V)) + self.gas = min(self.gas, max(60.0, self.bosch_last_gas + 60.0)) + self.bosch_last_gas = self.gas stopping = actuators.longControlState == LongCtrlState.stopping self.stopping_counter = self.stopping_counter + 1 if stopping else 0 @@ -386,6 +423,33 @@ class CarController(CarControllerBase): self.apply_brake_last = apply_brake self.brake = apply_brake / self.params.NIDEC_BRAKE_MAX + if self.CP.enableGasInterceptorDEPRECATED: + gas_error = actuators.accel - CS.out.aEgo + + if not CS.out.gasPressed and actuators.longControlState == LongCtrlState.pid: + if gas_error != 0.0 and gas > 0.0: + self.bosch_gas_factor = float(np.clip(self.bosch_gas_factor + gas_error / 150.0 * (gas * 4.8), 0.1, 3.0)) + if gas_error != 0.0 and not CS.out.brakePressed and CS.out.vEgo > 0.0: + wind_adjust = 1.0 + (wind_brake * 4.8) / 1000.0 + if gas_error > 0.0: + self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor * wind_adjust, 0.1, 5.0)) + else: + self.bosch_wind_factor = float(np.clip(self.bosch_wind_factor / wind_adjust, 0.1, 5.0)) + if gas <= 0.0: + self.bosch_wind_factor = max(self.bosch_wind_factor, self.bosch_wind_factor_before_brake) + else: + self.bosch_wind_factor_before_brake = self.bosch_wind_factor + + gas_mult = float(np.interp(CS.out.vEgo, [0.0, 10.0], [0.4, 1.0])) + if CC.longActive: + gas_interceptor_command = float(np.clip( + gas_mult * ((gas * self.bosch_gas_factor) - brake + (wind_brake * self.bosch_wind_factor * 3.0 / 4.0)), + 0.0, + 1.0, + )) + idx = (self.frame // 2) % 0x10 + can_sends.append(create_gas_interceptor_command(self.packer, gas_interceptor_command, idx)) + # Send dashboard UI commands. if self.frame % 10 == 0: if self.CP.openpilotLongitudinalControl: @@ -410,7 +474,14 @@ class CarController(CarControllerBase): can_sends.append(hondacan.create_legacy_brake_command(self.packer, self.CAN.pt)) if self.CP.carFingerprint not in HONDA_BOSCH: self.speed = pcm_speed - self.gas = pcm_accel / self.params.NIDEC_GAS_MAX + if self.CP.enableGasInterceptorDEPRECATED: + self.gas = gas_interceptor_command + else: + self.gas = pcm_accel / self.params.NIDEC_GAS_MAX + + if self.frame > 0 and self.frame % 6000 == 0: + self.param_store.put_float("HondaGasFactorParams", self.bosch_gas_factor) + self.param_store.put_float("HondaWindFactorParams", self.bosch_wind_factor) new_actuators = actuators.as_builder() new_actuators.speed = self.speed diff --git a/opendbc_repo/opendbc/car/honda/carstate.py b/opendbc_repo/opendbc/car/honda/carstate.py index 36e141403..d597cc78e 100644 --- a/opendbc_repo/opendbc/car/honda/carstate.py +++ b/opendbc_repo/opendbc/car/honda/carstate.py @@ -3,11 +3,11 @@ from collections import defaultdict from cereal import custom from opendbc.can import CANDefine, CANParser -from opendbc.car import Bus, create_button_events, structs +from opendbc.car import Bus, DT_CTRL, create_button_events, structs from opendbc.car.common.conversions import Conversions as CV from opendbc.car.honda.hondacan import CanBus from opendbc.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_BOSCH_ALT_RADAR, HONDA_BOSCH_CANFD, \ - HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, \ + HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, HONDA_BOSCH_TJA_CONTROL, \ HondaFlags, CruiseButtons, CruiseSettings, GearShifter, CarControllerParams, HondaStarPilotFlags from opendbc.car.interfaces import CarStateBase @@ -54,13 +54,19 @@ class CarState(CarStateBase): self.brake_switch_active = False self.low_speed_alert = False - self.dynamic_v_cruise_units = self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_ALT_RADAR | HONDA_BOSCH_CANFD) + self.dynamic_v_cruise_units = self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_ALT_RADAR | + HONDA_BOSCH_TJA_CONTROL | HONDA_BOSCH_CANFD) self.cruise_setting = 0 self.v_cruise_pcm_prev = 0 # When available we use cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] to populate vEgoCluster # However, on cars without a digital speedometer this is not always present (HRV, FIT, CRV 2016, ILX and RDX) self.dash_speed_seen = False + self.is_metric = False + self.v_cruise_factor = 1. + + self.initial_accFault_cleared = False + self.initial_accFault_cleared_timer = int(10 / DT_CTRL) def update(self, can_parsers, starpilot_toggles) -> structs.CarState: cp = can_parsers[Bus.pt] @@ -81,18 +87,19 @@ class CarState(CarStateBase): self.cruise_buttons = cp.vl["SCM_BUTTONS"]["CRUISE_BUTTONS"] # used for car hud message - self.is_metric = not cp.vl["CAR_SPEED"]["IMPERIAL_UNIT"] + self.is_metric = self.CP.carFingerprint in (CAR.HONDA_ODYSSEY_TWN,) or not cp.vl["CAR_SPEED"]["IMPERIAL_UNIT"] self.v_cruise_factor = CV.MPH_TO_MS if self.dynamic_v_cruise_units and not self.is_metric else CV.KPH_TO_MS # ******************* parse out can ******************* # blend in transmission speed at low speed, since it has more low speed accuracy # STANDSTILL->WHEELS_MOVING bit can be noisy around zero, so use XMISSION_SPEED + lowspeed_source = cp.vl["CAR_SPEED"]["CAR_SPEED"] if self.CP.carFingerprint == CAR.ACURA_INTEGRA else cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] v_wheel = sum([cp.vl["WHEEL_SPEEDS"][f"WHEEL_SPEED_{s}"] for s in ("FL", "FR", "RL", "RR")]) / 4.0 * CV.KPH_TO_MS v_weight = float(np.interp(v_wheel, v_weight_bp, v_weight_v)) - ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel + ret.vEgoRaw = (1. - v_weight) * lowspeed_source * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) - ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 1e-5 + ret.standstill = lowspeed_source < 1e-5 # doorOpen is true if we can find any door open, but signal locations vary, and we may only see the driver's door # TODO: Test the eight Nidec cars without SCM signals for driver's door state, may be able to consolidate further @@ -107,7 +114,8 @@ class CarState(CarStateBase): ret.seatbeltUnlatched = bool(cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_LAMP"] or not cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_LATCHED"]) steer_status = self.steer_status_values[cp.vl["STEER_STATUS"]["STEER_STATUS"]] - ret.steerFaultPermanent = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TMP_FAULT") + ret.steerFaultPermanent = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TJA_LOW_SPEED_LOCKOUT", + "TMP_FAULT") if self.CP.carFingerprint in HONDA_BOSCH_ALT_RADAR: # TODO: See if this logic works for all other Honda min_steer_speed = max(CarControllerParams.STEER_GLOBAL_MIN_SPEED, self.CP.minSteerSpeed) @@ -117,7 +125,11 @@ class CarState(CarStateBase): # LOW_SPEED_LOCKOUT is not worth a warning # NO_TORQUE_ALERT_2 can be caused by bump or steering nudge from driver # FIXME: the stock camera stops steering on NO_TORQUE_ALERT_1 - ret.steerFaultTemporary = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2") + ret.steerFaultTemporary = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "TJA_LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2") + + if self.CP.carFingerprint == CAR.ACURA_MDX_4G and steer_status == "TJA_LOW_SPEED_LOCKOUT": + ret.steerFaultPermanent = False + ret.steerFaultTemporary = False # All Honda EPS cut off slightly above standstill, some much higher # Don't alert in the near-standstill range, but alert for per-vehicle configured minimums above that @@ -128,22 +140,23 @@ class CarState(CarStateBase): self.low_speed_alert = False ret.lowSpeedAlert = self.low_speed_alert - if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS: + if self.CP.carFingerprint not in HONDA_BOSCH: + ret.carFaultedNonCritical = bool(cp_cam.vl["ACC_HUD"]["ACC_PROBLEM"] or cp_cam.vl["LKAS_HUD"]["LKAS_PROBLEM"]) + elif self.CP.carFingerprint in HONDA_BOSCH_RADARLESS: ret.accFaulted = bool(cp.vl["CRUISE_FAULT_STATUS"]["CRUISE_FAULT"]) - else: - if self.CP.openpilotLongitudinalControl: + elif self.CP.openpilotLongitudinalControl: + if self.CP.carFingerprint in (HONDA_BOSCH_CANFD | HONDA_BOSCH_TJA_CONTROL) and (self.CP.flags & HondaFlags.BOSCH_ALT_BRAKE): + ret.accFaulted = bool(cp.vl["BRAKE_MODULE"]["CRUISE_FAULT"]) + else: ret.accFaulted = bool(cp.vl[self.brake_error_msg]["BRAKE_ERROR_1"] or cp.vl[self.brake_error_msg]["BRAKE_ERROR_2"]) - # Log non-critical stock ACC/LKAS faults if Nidec (camera) - if self.CP.carFingerprint not in HONDA_BOSCH: - ret.carFaultedNonCritical = bool(cp_cam.vl["ACC_HUD"]["ACC_PROBLEM"] or cp_cam.vl["LKAS_HUD"]["LKAS_PROBLEM"]) - ret.espDisabled = cp.vl["VSA_STATUS"]["ESP_DISABLED"] != 0 - self.dash_speed_seen = self.dash_speed_seen or cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] > 1e-3 - if self.dash_speed_seen: - conversion = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS - ret.vEgoCluster = cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] * conversion + if self.CP.carFingerprint not in (CAR.HONDA_ODYSSEY_TWN,): + self.dash_speed_seen = self.dash_speed_seen or cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] > 1e-3 + if self.dash_speed_seen: + conversion = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS + ret.vEgoCluster = cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] * conversion ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE"] ret.steeringRateDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE_RATE"] @@ -154,12 +167,16 @@ class CarState(CarStateBase): ret.parkingBrake = bool(cp.vl[self.car_state_scm_msg]["PARKING_BRAKE_ON"]) if self.CP.transmissionType == TransmissionType.manual: - ret.gearShifter = GearShifter.reverse if bool(cp.vl["SCM_FEEDBACK"]["REVERSE_LIGHT"]) else GearShifter.drive + ret.gearShifter = GearShifter.reverse if bool(cp.vl[self.car_state_scm_msg]["REVERSE_LIGHT"]) else GearShifter.drive else: gear_position = self.shifter_values.get(cp.vl[self.gearbox_msg]["GEAR_SHIFTER"], None) ret.gearShifter = self.parse_gear_shifter(gear_position) - ret.gasPressed = cp.vl["POWERTRAIN_DATA"]["PEDAL_GAS"] > 1e-5 + if self.CP.enableGasInterceptorDEPRECATED: + gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2. + ret.gasPressed = gas > 492 + else: + ret.gasPressed = cp.vl["POWERTRAIN_DATA"]["PEDAL_GAS"] > 1e-5 ret.steeringTorque = cp.vl["STEER_STATUS"]["STEER_TORQUE_SENSOR"] ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD.get(self.CP.carFingerprint, 1200) @@ -200,6 +217,16 @@ class CarState(CarStateBase): ret.cruiseState.enabled = cp.vl["POWERTRAIN_DATA"]["ACC_STATUS"] != 0 ret.cruiseState.available = bool(cp.vl[self.car_state_scm_msg]["MAIN_ON"]) + if ret.accFaulted: + if self.CP.carFingerprint in HONDA_BOSCH and not self.initial_accFault_cleared: + ret.accFaulted = False + ret.cruiseState.available = False + elif self.initial_accFault_cleared_timer == 0: + self.initial_accFault_cleared = True + + if self.initial_accFault_cleared_timer > 0: + self.initial_accFault_cleared_timer -= 1 + # Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models if self.CP.carFingerprint in (CAR.HONDA_PILOT, CAR.HONDA_RIDGELINE): if ret.brake > 0.1: @@ -218,7 +245,7 @@ class CarState(CarStateBase): ret.stockFcw = cp_cam.vl["BRAKE_COMMAND"]["FCW"] != 0 self.acc_hud = cp_cam.vl["ACC_HUD"] self.stock_brake = cp_cam.vl["BRAKE_COMMAND"] - if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS: + if self.CP.carFingerprint in (HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD): self.lkas_hud = cp_cam.vl["LKAS_HUD"] if self.CP.enableBsm: diff --git a/opendbc_repo/opendbc/car/honda/interface.py b/opendbc_repo/opendbc/car/honda/interface.py index dbf83ac81..53a3e41f1 100644 --- a/opendbc_repo/opendbc/car/honda/interface.py +++ b/opendbc_repo/opendbc/car/honda/interface.py @@ -23,6 +23,8 @@ class CarInterface(CarInterfaceBase): def get_pid_accel_limits(CP, current_speed, cruise_speed): if CP.carFingerprint in HONDA_BOSCH: return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX + elif CP.enableGasInterceptorDEPRECATED: + return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX else: # NIDECs don't allow acceleration near cruise_speed, # so limit limits of pid to prevent windup @@ -46,15 +48,17 @@ class CarInterface(CarInterfaceBase): # Disable the radar and let openpilot control longitudinal # WARNING: THIS DISABLES AEB! # If Bosch radarless, this blocks ACC messages from the camera - # TODO: get radar disable working on Bosch CANFD - ret.alphaLongitudinalAvailable = candidate not in HONDA_BOSCH_CANFD - ret.openpilotLongitudinalControl = alpha_long and (candidate not in HONDA_BOSCH_CANFD) + ret.alphaLongitudinalAvailable = True + ret.openpilotLongitudinalControl = alpha_long ret.pcmCruise = not ret.openpilotLongitudinalControl else: ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.hondaNidec)] ret.openpilotLongitudinalControl = True ret.pcmCruise = True + ret.enableGasInterceptorDEPRECATED = 0x201 in fingerprint[CAN.pt] + if ret.enableGasInterceptorDEPRECATED: + ret.pcmCruise = False if candidate == CAR.HONDA_CRV_5G: ret.enableBsm = 0x12f8bfa7 in fingerprint[CAN.radar] @@ -80,6 +84,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kf = 0.00006 # conservative feed-forward ret.steerActuatorDelay = 0.1 + ret.stoppingDecelRate = 0.3 if candidate in HONDA_BOSCH: ret.longitudinalActuatorDelay = 0.5 # s @@ -98,6 +103,11 @@ class CarInterface(CarInterfaceBase): if eps_modified: ret.flags |= HondaFlags.EPS_MODIFIED.value + if candidate == CAR.HONDA_CITY_7G: + ret.vEgoStopping = 2.0 + ret.vEgoStarting = ret.vEgoStopping + ret.stoppingDecelRate = 0.3 + if candidate == CAR.HONDA_CIVIC: if eps_modified: # stock request input values: 0x0000, 0x00DE, 0x014D, 0x01EF, 0x0290, 0x0377, 0x0454, 0x0610, 0x06EE @@ -129,6 +139,13 @@ class CarInterface(CarInterfaceBase): 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]] + if ret.transmissionType == TransmissionType.manual: + CarControllerParams.BOSCH_GAS_LOOKUP_BP = [-0.2, 2.0] + + elif candidate == CAR.HONDA_ACCORD_11G: + ret.steerActuatorDelay = 0.22 + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 5200], [0, 2560, 12747]] + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) 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 @@ -156,6 +173,13 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] ret.wheelSpeedFactor = 1.025 + elif candidate == CAR.HONDA_CRV_6G: + ret.steerActuatorDelay = 0.15 + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 5100], [0, 5100]] + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + if ret.flags & HondaFlags.HYBRID: + CarControllerParams.BOSCH_GAS_LOOKUP_BP = [-0.3, 2.0] + elif candidate == CAR.HONDA_FIT: 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.2], [0.05]] @@ -182,16 +206,38 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] elif candidate == CAR.ACURA_RDX_3G: - ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4095], [0, 4095]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] + CarControllerParams.BOSCH_GAS_LOOKUP_V = [0, 2200] + + elif candidate == CAR.ACURA_RDX_3G_MMR: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + CarControllerParams.BOSCH_GAS_LOOKUP_V = [0, 2000] + if not ret.openpilotLongitudinalControl: + ret.minSteerSpeed = 70. * CV.KPH_TO_MS elif candidate == CAR.HONDA_ODYSSEY: ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - elif candidate == CAR.HONDA_PILOT: + elif candidate == CAR.HONDA_ODYSSEY_TWN: + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] + + elif candidate in (CAR.HONDA_PILOT, CAR.HONDA_PILOT_4G): 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.38], [0.11]] + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kpV = [[0, 10], [0.05, 0.5]] + ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kiV = [[0, 10], [0.0125, 0.125]] + + elif candidate == CAR.ACURA_MDX_4G: + ret.steerActuatorDelay = 0.15 + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 4209], [0, 2560, 9150]] + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + + elif candidate == CAR.ACURA_MDX_4G_MMR: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 4920], [0, 2560, 12000]] + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.HONDA_RIDGELINE: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end @@ -201,7 +247,7 @@ class CarInterface(CarInterfaceBase): 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]] - elif candidate == CAR.HONDA_E: + elif candidate in (CAR.HONDA_E, CAR.HONDA_E_ADVANCE): 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]] # TODO: can probably use some tuning @@ -217,19 +263,48 @@ class CarInterface(CarInterfaceBase): # When using stock ACC, the radar intercepts and filters steering commands the EPS would otherwise accept ret.minSteerSpeed = 70. * CV.KPH_TO_MS + elif candidate == CAR.ACURA_TLX_2G_MMR: + ret.steerActuatorDelay = 0.15 + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kpV = [[0, 10], [0.05, 0.5]] + ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kiV = [[0, 10], [0.0125, 0.125]] + + elif candidate in (CAR.HONDA_FIT_4G,): + ret.steerActuatorDelay = 0.15 + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + + elif candidate == CAR.ACURA_INTEGRA: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] + + elif candidate == CAR.ACURA_ADX: + ret.steerActuatorDelay = 0.15 + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 5000], [0, 5000]] + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kpV = [[0, 10], [0.05, 0.5]] + ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kiV = [[0, 10], [0.0125, 0.125]] + + elif candidate == CAR.HONDA_PASSPORT_4G: + ret.steerActuatorDelay = 0.15 + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 5120], [0, 2560, 12789]] + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + else: ret.steerActuatorDelay = 0.15 ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]] CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) # These cars use alternate user brake msg (0x1BE) - if 0x1BE in fingerprint[CAN.pt] and candidate in (CAR.HONDA_ACCORD, CAR.HONDA_HRV_3G, *HONDA_BOSCH_CANFD): + if 0x1BE in fingerprint[CAN.pt] and candidate in (CAR.HONDA_ACCORD, CAR.HONDA_HRV_3G, CAR.ACURA_RDX_3G, CAR.ACURA_MDX_4G, + CAR.ACURA_ADX, *HONDA_BOSCH_CANFD): ret.flags |= HondaFlags.BOSCH_ALT_BRAKE.value if ret.flags & HondaFlags.BOSCH_ALT_BRAKE: ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.ALT_BRAKE.value if candidate in HONDA_NIDEC_ALT_SCM_MESSAGES: ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.NIDEC_ALT.value + if ret.enableGasInterceptorDEPRECATED: + ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.GAS_INTERCEPTOR.value if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH: ret.safetyConfigs[-1].safetyParam |= HondaSafetyFlags.BOSCH_LONG.value if candidate in HONDA_BOSCH_RADARLESS: @@ -240,8 +315,24 @@ class CarInterface(CarInterfaceBase): # min speed to enable ACC. if car can do stop and go, then set enabling speed # to a negative value, so it won't matter. Otherwise, add 0.5 mph margin to not # conflict with PCM acc - ret.autoResumeSng = candidate in (HONDA_BOSCH | {CAR.HONDA_CIVIC, CAR.HONDA_CLARITY}) - ret.minEnableSpeed = -1. if ret.autoResumeSng else 25.51 * CV.MPH_TO_MS + if candidate == CAR.HONDA_FIT_4G and not ret.openpilotLongitudinalControl: + ret.autoResumeSng = False + elif ret.transmissionType == TransmissionType.manual and not ret.openpilotLongitudinalControl: + ret.autoResumeSng = False + else: + ret.autoResumeSng = candidate in (HONDA_BOSCH | {CAR.HONDA_CIVIC, CAR.HONDA_CLARITY}) or ret.enableGasInterceptorDEPRECATED + + if ret.autoResumeSng: + ret.minEnableSpeed = -1. + elif candidate == CAR.HONDA_ODYSSEY_TWN: + ret.minEnableSpeed = 19. * CV.MPH_TO_MS + elif candidate == CAR.HONDA_FIT_4G: + ret.minEnableSpeed = 30. * CV.KPH_TO_MS + else: + ret.minEnableSpeed = 25.51 * CV.MPH_TO_MS + + if candidate == CAR.HONDA_PILOT_4G: + CarControllerParams.BOSCH_GAS_LOOKUP_V = [0, 2200] ret.steerLimitTimer = 0.8 ret.radarDelay = 0.1 diff --git a/opendbc_repo/opendbc/car/honda/tests/test_honda.py b/opendbc_repo/opendbc/car/honda/tests/test_honda.py index 66721b318..6c475134d 100644 --- a/opendbc_repo/opendbc/car/honda/tests/test_honda.py +++ b/opendbc_repo/opendbc/car/honda/tests/test_honda.py @@ -6,6 +6,7 @@ from opendbc.car.structs import CarParams from opendbc.car import gen_empty_fingerprint from opendbc.car.honda.interface import CarInterface from opendbc.car.honda.carcontroller import ( + CarController, get_civic_bosch_modified_steering_pressed, get_civic_bosch_modified_torque_lpf_tau, get_honda_bosch_wind_brake_mps2, @@ -13,11 +14,16 @@ from opendbc.car.honda.carcontroller import ( ) from opendbc.car.honda.hondacan import create_lkas_hud from opendbc.car.honda.fingerprints import FW_VERSIONS -from opendbc.car.honda.values import CAR, HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL, HondaFlags +from opendbc.car.honda.values import CAR, DBC, HONDA_BOSCH, HONDA_BOSCH_TJA_CONTROL, CarControllerParams, HondaFlags, HondaSafetyFlags, \ + HondaStarPilotFlags HONDA_FW_VERSION_RE = rb"[A-Z0-9]{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$" +def get_test_toggles() -> SimpleNamespace: + return SimpleNamespace(always_on_lateral_lkas=False, force_torque_controller=False, nnff=False, nnff_lite=False) + + class TestHondaFingerprint: def test_honda_lkas_hud_shows_lane_lines_when_lateral_only_is_active(self): class FakePacker: @@ -178,3 +184,54 @@ class TestHondaFingerprint: torque_cp = CarInterface.get_params(CAR.HONDA_CLARITY, gen_empty_fingerprint(), car_fw, False, False, False, torque_toggles) assert torque_cp.lateralTuning.which() == "torque" + + def test_canfd_bosch_alpha_long_is_available(self): + toggles = get_test_toggles() + + CP = CarInterface.get_params(CAR.HONDA_PILOT_4G, gen_empty_fingerprint(), [], True, False, False, toggles) + + assert CP.alphaLongitudinalAvailable + assert CP.openpilotLongitudinalControl + assert CP.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_CANFD + assert CP.safetyConfigs[-1].safetyParam & HondaSafetyFlags.BOSCH_LONG + + def test_nidec_pedal_detection_enables_interceptor_path(self): + toggles = get_test_toggles() + fingerprint = gen_empty_fingerprint() + fingerprint[0][0x201] = 6 + + CP = CarInterface.get_params(CAR.HONDA_CIVIC, fingerprint, [], False, False, False, toggles) + accel_limits = CarInterface.get_pid_accel_limits(CP, current_speed=5.0, cruise_speed=12.0) + + assert CP.enableGasInterceptorDEPRECATED + assert not CP.pcmCruise + assert accel_limits == (CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX) + + def test_honda_camera_message_flag_uses_fingerprint_detection(self): + toggles = get_test_toggles() + fingerprint = gen_empty_fingerprint() + fingerprint[0][0x35E] = 8 + + CP = CarInterface.get_params(CAR.HONDA_ACCORD, fingerprint, [], True, False, False, toggles) + FPCP = CarInterface.get_starpilot_params(CAR.HONDA_ACCORD, fingerprint, [], CP, toggles) + + assert FPCP.flags & HondaStarPilotFlags.HAS_CAMERA_MESSAGES + + def test_honda_live_learning_params_reload(self, monkeypatch): + toggles = get_test_toggles() + + class FakeParams: + def get_float(self, key, block=False, return_default=False, default=0.0): + if key == "HondaGasFactorParams": + return 1.25 + if key == "HondaWindFactorParams": + return 0.85 + return default + + monkeypatch.setattr("opendbc.car.honda.carcontroller.Params", lambda: FakeParams()) + + CP = CarInterface.get_params(CAR.HONDA_ACCORD, gen_empty_fingerprint(), [], True, False, False, toggles) + controller = CarController(DBC[CP.carFingerprint], CP) + + assert controller.bosch_gas_factor == pytest.approx(1.25) + assert controller.bosch_wind_factor == pytest.approx(0.85) diff --git a/opendbc_repo/opendbc/car/honda/values.py b/opendbc_repo/opendbc/car/honda/values.py index 7456642d5..078a5de53 100644 --- a/opendbc_repo/opendbc/car/honda/values.py +++ b/opendbc_repo/opendbc/car/honda/values.py @@ -56,6 +56,7 @@ class HondaSafetyFlags(IntFlag): NIDEC_ALT = 4 RADARLESS = 8 BOSCH_CANFD = 16 + GAS_INTERCEPTOR = 32 class HondaFlags(IntFlag): @@ -247,6 +248,12 @@ class CAR(Platforms): {Bus.pt: 'acura_rdx_2020_can_generated'}, flags=HondaFlags.BOSCH_ALT_BRAKE, ) + ACURA_RDX_3G_MMR = HondaBoschPlatformConfig( + [HondaCarDocs("Acura RDX 2022-26", "All", min_steer_speed=70. * CV.KPH_TO_MS)], + CarSpecs(mass=4079 * CV.LB_TO_KG, wheelbase=2.75, centerToFrontRatio=0.41, steerRatio=16.2), + {Bus.pt: 'acura_rdx_2020_can_generated'}, + flags=HondaFlags.BOSCH_ALT_BRAKE | HondaFlags.BOSCH_ALT_RADAR, + ) HONDA_INSIGHT = HondaBoschPlatformConfig( [HondaCarDocs("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS)], CarSpecs(mass=2987 * CV.LB_TO_KG, wheelbase=2.7, steerRatio=15.0, centerToFrontRatio=0.39, tireStiffnessFactor=0.82), # as spec @@ -257,6 +264,11 @@ class CAR(Platforms): CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82), {Bus.pt: 'acura_rdx_2020_can_generated'}, ) + HONDA_E_ADVANCE = HondaBoschPlatformConfig( + [], # don't show in docs, base trim already in docs + CarSpecs(mass=1527, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82), + {Bus.pt: 'honda_e_advance_2020_can_generated'}, + ) HONDA_PILOT_4G = HondaBoschCANFDPlatformConfig( [HondaCarDocs("Honda Pilot 2023-25", "All")], CarSpecs(mass=4660 * CV.LB_TO_KG, wheelbase=2.89, centerToFrontRatio=0.442, steerRatio=17.5), @@ -265,6 +277,12 @@ class CAR(Platforms): [HondaCarDocs("Honda Passport 2026", "All")], CarSpecs(mass=4620 * CV.LB_TO_KG, wheelbase=2.89, centerToFrontRatio=0.442, steerRatio=18.5), ) + ACURA_MDX_4G = HondaBoschPlatformConfig( + [HondaCarDocs("Acura MDX 2022-24", "All", min_steer_speed=70. * CV.KPH_TO_MS)], + CarSpecs(mass=4788 * CV.LB_TO_KG, wheelbase=2.89, steerRatio=15.8, centerToFrontRatio=0.428), + {Bus.pt: 'honda_common_canfd_generated'}, + flags=HondaFlags.BOSCH_ALT_RADAR | HondaFlags.BOSCH_TJA_CONTROL, + ) # mid-model refresh ACURA_MDX_4G_MMR = HondaBoschCANFDPlatformConfig( [HondaCarDocs("Acura MDX 2025", "All except Type S")], @@ -282,6 +300,34 @@ class CAR(Platforms): {Bus.pt: 'honda_civic_hatchback_ex_2017_can_generated'}, flags=HondaFlags.BOSCH_ALT_RADAR, ) + ACURA_TLX_2G_MMR = HondaBoschCANFDPlatformConfig( + [HondaCarDocs("Acura TLX 2024-25", "All")], + CarSpecs(mass=3990 * CV.LB_TO_KG, wheelbase=2.87, centerToFrontRatio=0.43, steerRatio=13.7), + ) + HONDA_FIT_4G = HondaBoschPlatformConfig( + [ + HondaCarDocs("Honda Fit (Taiwan) 2021", "All"), + HondaCarDocs("Honda Fit (Taiwan) 2024-25", "All"), + ], + CarSpecs(mass=1229, wheelbase=2.53, steerRatio=19.7, centerToFrontRatio=0.39, minSteerSpeed=23. * CV.KPH_TO_MS), + {Bus.pt: 'honda_bosch_radarless_generated'}, + flags=HondaFlags.BOSCH_RADARLESS, + ) + ACURA_INTEGRA = HondaBoschPlatformConfig( + [ + HondaCarDocs("Acura Integra 2023-26", "All"), + HondaCarDocs("Honda Prelude 2026", "All"), + ], + CarSpecs(mass=3338.8 * CV.LB_TO_KG, wheelbase=2.5, centerToFrontRatio=0.5, steerRatio=16.71, tireStiffnessFactor=0.82), + {Bus.pt: 'honda_bosch_radarless_generated'}, + flags=HondaFlags.BOSCH_RADARLESS, + ) + ACURA_ADX = HondaBoschPlatformConfig( + [HondaCarDocs("Acura ADX 2025-26", "All")], + CarSpecs(mass=3578 * CV.LB_TO_KG, wheelbase=2.65, steerRatio=16.6, centerToFrontRatio=0.43), + {Bus.pt: 'honda_bosch_radarless_generated'}, + flags=HondaFlags.BOSCH_RADARLESS, + ) # Nidec Cars ACURA_ILX = HondaNidecPlatformConfig( @@ -305,6 +351,12 @@ class CAR(Platforms): radar_dbc_dict('honda_crv_touring_2016_can_generated'), flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES, ) + HONDA_CRV_SA = HondaNidecPlatformConfig( + [], # South Africa version of CRV Touring, don't show in docs + HONDA_CRV.specs, + radar_dbc_dict('acura_rdx_2018_can_generated'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES, + ) HONDA_FIT = HondaNidecPlatformConfig( [HondaCarDocs("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS)], CarSpecs(mass=2644 * CV.LB_TO_KG, wheelbase=2.53, steerRatio=13.06, centerToFrontRatio=0.39, tireStiffnessFactor=0.75), @@ -335,6 +387,15 @@ class CAR(Platforms): radar_dbc_dict('honda_odyssey_exl_2018_generated'), flags=HondaFlags.NIDEC_ALT_PCM_ACCEL | HondaFlags.HAS_ALL_DOOR_STATES, ) + HONDA_ODYSSEY_TWN = HondaNidecPlatformConfig( + [ + HondaCarDocs("Honda Odyssey (Taiwan) 2018-19"), + HondaCarDocs("Honda Odyssey (Singapore) 2021"), + ], + CarSpecs(mass=1865, wheelbase=2.9, steerRatio=14.35, centerToFrontRatio=0.44, tireStiffnessFactor=0.82), + radar_dbc_dict('honda_odyssey_twn_2018_generated'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) ACURA_RDX = HondaNidecPlatformConfig( [HondaCarDocs("Acura RDX 2016-18", "AcuraWatch Plus or Advance Package", min_steer_speed=12. * CV.MPH_TO_MS)], CarSpecs(mass=3925 * CV.LB_TO_KG, wheelbase=2.68, steerRatio=15.0, centerToFrontRatio=0.38, tireStiffnessFactor=0.444), # as spec @@ -362,6 +423,43 @@ class CAR(Platforms): radar_dbc_dict('honda_civic_touring_2016_can_generated'), flags=HondaFlags.HAS_ALL_DOOR_STATES ) + HONDA_ACCORD_9G = HondaNidecPlatformConfig( + [ + HondaCarDocs("Honda Accord 2016-17"), + HondaCarDocs("Honda Accord Hybrid 2017", "All"), + ], + CarSpecs(mass=3343 * CV.LB_TO_KG, wheelbase=2.78, steerRatio=17.5, centerToFrontRatio=0.37), + radar_dbc_dict('honda_accord_2017_can_ext_generated'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES, + ) + ACURA_MDX_3G = HondaNidecPlatformConfig( + [ + HondaCarDocs("Acura MDX 2014-16", "Advance Package"), + HondaCarDocs("Acura MDX 2017-19", "All"), + HondaCarDocs("Acura MDX Hybrid 2017-19", "All"), + ], + CarSpecs(mass=4215 * CV.LB_TO_KG, wheelbase=2.82, steerRatio=16.8, centerToFrontRatio=0.428), + radar_dbc_dict('acura_mdx_2017_can_ext_generated'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + ACURA_MDX_3G_MMR = HondaNidecPlatformConfig( + [ + HondaCarDocs("Acura MDX 2020", "All"), + HondaCarDocs("Acura MDX Hybrid 2020", "All"), + ], + CarSpecs(mass=4215 * CV.LB_TO_KG, wheelbase=2.82, steerRatio=16.8, centerToFrontRatio=0.428), + radar_dbc_dict('acura_ilx_2016_can_generated'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES, + ) + ACURA_TLX_1G = HondaNidecPlatformConfig( + [ + HondaCarDocs("Acura TLX 2015-17", "Advance Package"), + HondaCarDocs("Acura TLX 2018-20", "All"), + ], + CarSpecs(mass=3680 * CV.LB_TO_KG, wheelbase=2.78, steerRatio=17.0, centerToFrontRatio=0.40, tireStiffnessFactor=0.18), + radar_dbc_dict('acura_mdx_2017_can_ext_generated'), + flags=HondaFlags.NIDEC_ALT_SCM_MESSAGES | HondaFlags.HAS_ALL_DOOR_STATES, + ) HONDA_NIDEC_ALT_PCM_ACCEL = CAR.with_flags(HondaFlags.NIDEC_ALT_PCM_ACCEL) @@ -396,6 +494,11 @@ STEER_THRESHOLD = { CAR.HONDA_CRV_6G: 600, CAR.HONDA_CITY_7G: 600, CAR.HONDA_NBOX_2G: 600, + CAR.HONDA_ODYSSEY_5G_MMR: 600, + CAR.HONDA_ACCORD_9G: 30, + CAR.ACURA_MDX_3G: 30, + CAR.ACURA_MDX_3G_MMR: 30, + CAR.ACURA_TLX_1G: 30, } @@ -440,9 +543,11 @@ FW_QUERY_CONFIG = FwQueryConfig( # Note that we still attempt to match with them when they are present # This is or'd with (ALL_ECUS - ESSENTIAL_ECUS) from fw_versions.py non_essential_ecus={ - Ecu.eps: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_E, *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD], + Ecu.eps: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_E, CAR.HONDA_E_ADVANCE, CAR.ACURA_MDX_4G, CAR.HONDA_CRV_SA, + CAR.ACURA_MDX_3G, CAR.HONDA_ACCORD_9G, *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD], Ecu.vsa: [CAR.ACURA_RDX_3G, CAR.HONDA_ACCORD, CAR.HONDA_CIVIC, CAR.HONDA_CIVIC_BOSCH, CAR.HONDA_CRV_5G, CAR.HONDA_CRV_HYBRID, - CAR.HONDA_E, CAR.HONDA_INSIGHT, CAR.HONDA_NBOX_2G, *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD], + CAR.HONDA_E, CAR.HONDA_E_ADVANCE, CAR.HONDA_INSIGHT, CAR.HONDA_NBOX_2G, CAR.ACURA_MDX_4G, CAR.HONDA_ACCORD_9G, + *HONDA_BOSCH_ALT_RADAR, *HONDA_BOSCH_RADARLESS, *HONDA_BOSCH_CANFD], }, extra_ecus=[ (Ecu.combinationMeter, 0x18da60f1, None), diff --git a/opendbc_repo/opendbc/car/interfaces.py b/opendbc_repo/opendbc/car/interfaces.py index e9e923f9c..e80aff328 100644 --- a/opendbc_repo/opendbc/car/interfaces.py +++ b/opendbc_repo/opendbc/car/interfaces.py @@ -18,7 +18,7 @@ from opendbc.car.common.basedir import BASEDIR from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.simple_kalman import KF1D, get_kalman_gain from opendbc.car.gm.values import CAR as GM -from opendbc.car.honda.values import CAR as HONDA, HONDA_BOSCH, HONDA_CAMERA_MESSAGE_CARS, HondaFlags, HondaSafetyFlags, HondaStarPilotFlags +from opendbc.car.honda.values import CAR as HONDA, HONDA_BOSCH, HondaFlags, HondaSafetyFlags, HondaStarPilotFlags from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.values import CAR as HYUNDAI, CANFD_CAR, HyundaiFlags, HyundaiStarPilotFlags, HyundaiStarPilotSafetyFlags from opendbc.car.mock.values import CAR as MOCK @@ -215,8 +215,7 @@ class CarInterfaceBase(ABC): elif platform in HONDA: fp_ret.canUsePedal = candidate not in HONDA_BOSCH - # Only enable TSR parsing on Hondas confirmed to publish CAMERA_MESSAGES. - if candidate in HONDA_CAMERA_MESSAGE_CARS: + if any(0x35E in bus_fingerprint for bus_fingerprint in fingerprint.values()): fp_ret.flags |= int(HondaStarPilotFlags.HAS_CAMERA_MESSAGES) elif platform in HYUNDAI: diff --git a/opendbc_repo/opendbc/safety/modes/honda.h b/opendbc_repo/opendbc/safety/modes/honda.h index 1848ee43f..73554bd78 100644 --- a/opendbc_repo/opendbc/safety/modes/honda.h +++ b/opendbc_repo/opendbc/safety/modes/honda.h @@ -6,7 +6,8 @@ #define HONDA_COMMON_NO_SCM_FEEDBACK_RX_CHECKS(pt_bus) \ {.msg = {{0x1A6, (pt_bus), 8, 25U, .max_counter = 3U, .ignore_quality_flag = true}, /* SCM_BUTTONS */ \ {0x296, (pt_bus), 4, 25U, .max_counter = 3U, .ignore_quality_flag = true}, { 0 }}}, \ - {.msg = {{0x158, (pt_bus), 8, 100U, .max_counter = 3U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, /* ENGINE_DATA */ \ + {.msg = {{0x158, (pt_bus), 8, 100U, .max_counter = 3U, .ignore_quality_flag = true}, /* ENGINE_DATA */ \ + {0x309, (pt_bus), 8, 10U, .max_counter = 3U, .ignore_quality_flag = true}, { 0 }}}, /* CAR_SPEED */ \ {.msg = {{0x17C, (pt_bus), 8, 100U, .max_counter = 3U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, /* POWERTRAIN_DATA */ \ #define HONDA_COMMON_RX_CHECKS(pt_bus) \ @@ -17,6 +18,17 @@ #define HONDA_ALT_BRAKE_ADDR_CHECK(pt_bus) \ {.msg = {{0x1BE, (pt_bus), 3, 50U, .max_counter = 3U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, /* BRAKE_MODULE */ \ +#define HONDA_GAS_INTERCEPTOR_ADDR_CHECK \ + {.msg = {{0x201, 0, 6, 50U, .max_counter = 15U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, \ + { 0 }, { 0 }}}, \ + +#define HONDA_N_COMMON_TX_MSGS \ + {0xE4, 0, 5, .check_relay = true}, \ + {0x194, 0, 4, .check_relay = true}, \ + {0x1FA, 0, 8, .check_relay = false}, \ + {0x30C, 0, 8, .check_relay = true}, \ + {0x33D, 0, 5, .check_relay = true}, \ + enum { HONDA_BTN_NONE = 0, HONDA_BTN_MAIN = 1, @@ -63,16 +75,26 @@ static uint32_t honda_compute_checksum(const CANPacket_t *msg) { } static uint8_t honda_get_counter(const CANPacket_t *msg) { + if (msg->addr == 0x201U) { + return msg->data[4] & 0xFU; + } + int counter_byte = GET_LEN(msg) - 1U; return (msg->data[counter_byte] >> 4U) & 0x3U; } +static int HONDA_GET_INTERCEPTOR(const CANPacket_t *msg) { + uint16_t val1 = ((uint16_t)msg->data[0] << 8U) | (uint16_t)msg->data[1]; + uint16_t val2 = ((uint16_t)msg->data[2] << 8U) | (uint16_t)msg->data[3]; + return (int)((val1 + val2) / 2U); +} + static void honda_rx_hook(const CANPacket_t *msg) { - const bool pcm_cruise = ((honda_hw == HONDA_BOSCH) && !honda_bosch_long) || (honda_hw == HONDA_NIDEC); + const bool pcm_cruise = ((honda_hw == HONDA_BOSCH) && !honda_bosch_long) || ((honda_hw == HONDA_NIDEC) && !enable_gas_interceptor); unsigned int pt_bus = honda_get_pt_bus(); // sample speed - if (msg->addr == 0x158U) { + if ((msg->addr == 0x158U) || (msg->addr == 0x309U)) { vehicle_moving = msg->data[0] | msg->data[1]; } @@ -139,7 +161,14 @@ static void honda_rx_hook(const CANPacket_t *msg) { } } - if (msg->addr == 0x17CU) { + if (msg->addr == 0x201U) { + const int honda_gas_interceptor_thrsld = 492; + int gas_interceptor = HONDA_GET_INTERCEPTOR(msg); + gas_pressed = gas_interceptor > honda_gas_interceptor_thrsld; + gas_interceptor_prev = gas_interceptor; + } + + if (!enable_gas_interceptor && (msg->addr == 0x17CU)) { gas_pressed = msg->data[0] != 0U; } @@ -168,7 +197,7 @@ static bool honda_tx_hook(const CANPacket_t *msg) { .max_accel = 200, // accel is used for brakes .min_accel = -350, - .max_gas = 2000, + .max_gas = 2200, .inactive_gas = -30000, }; @@ -269,6 +298,13 @@ static bool honda_tx_hook(const CANPacket_t *msg) { } } + // GAS: safety check (interceptor) + if (msg->addr == 0x200U) { + if (longitudinal_interceptor_checks(msg)) { + tx = false; + } + } + return tx; } @@ -276,10 +312,14 @@ static safety_config honda_nidec_init(uint16_t param) { // 0x1FA is dynamically forwarded based on stock AEB // 0xE4 is steering on all cars except CRV and RDX, 0x194 for CRV and RDX, // 0x1FA is brake control, 0x30C is acc hud, 0x33D is lkas hud - static CanMsg HONDA_N_TX_MSGS[] = {{0xE4, 0, 5, .check_relay = true}, {0x194, 0, 4, .check_relay = true}, {0x1FA, 0, 8, .check_relay = false}, - {0x30C, 0, 8, .check_relay = true}, {0x33D, 0, 5, .check_relay = true}}; + static CanMsg HONDA_N_TX_MSGS[] = {HONDA_N_COMMON_TX_MSGS}; + static CanMsg HONDA_N_INTERCEPTOR_TX_MSGS[] = { + HONDA_N_COMMON_TX_MSGS + {0x200, 0, 6, .check_relay = false}, + }; const uint16_t HONDA_PARAM_NIDEC_ALT = 4; + const uint16_t HONDA_PARAM_GAS_INTERCEPTOR = 32; honda_hw = HONDA_NIDEC; honda_brake = 0; @@ -289,12 +329,22 @@ static safety_config honda_nidec_init(uint16_t param) { honda_bosch_long = false; honda_bosch_radarless = false; honda_bosch_canfd = false; + enable_gas_interceptor = GET_FLAG(param, HONDA_PARAM_GAS_INTERCEPTOR); safety_config ret; bool enable_nidec_alt = GET_FLAG(param, HONDA_PARAM_NIDEC_ALT); - if (enable_nidec_alt) { + if (enable_nidec_alt && enable_gas_interceptor) { + static RxCheck honda_nidec_alt_interceptor_rx_checks[] = { + HONDA_COMMON_NO_SCM_FEEDBACK_RX_CHECKS(0) + {.msg = {{0x1FA, 2, 8, 50U, .max_counter = 3U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // BRAKE_COMMAND + HONDA_GAS_INTERCEPTOR_ADDR_CHECK + }; + + SET_RX_CHECKS(honda_nidec_alt_interceptor_rx_checks, ret); + SET_TX_MSGS(HONDA_N_INTERCEPTOR_TX_MSGS, ret); + } else if (enable_nidec_alt) { // For Nidecs with main on signal on an alternate msg (missing 0x326) static RxCheck honda_nidec_alt_rx_checks[] = { HONDA_COMMON_NO_SCM_FEEDBACK_RX_CHECKS(0) @@ -302,6 +352,16 @@ static safety_config honda_nidec_init(uint16_t param) { }; SET_RX_CHECKS(honda_nidec_alt_rx_checks, ret); + SET_TX_MSGS(HONDA_N_TX_MSGS, ret); + } else if (enable_gas_interceptor) { + static RxCheck honda_nidec_common_interceptor_rx_checks[] = { + HONDA_COMMON_RX_CHECKS(0) + {.msg = {{0x1FA, 2, 8, 50U, .max_counter = 3U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // BRAKE_COMMAND + HONDA_GAS_INTERCEPTOR_ADDR_CHECK + }; + + SET_RX_CHECKS(honda_nidec_common_interceptor_rx_checks, ret); + SET_TX_MSGS(HONDA_N_INTERCEPTOR_TX_MSGS, ret); } else { // Nidec includes BRAKE_COMMAND static RxCheck honda_nidec_common_rx_checks[] = { @@ -310,10 +370,9 @@ static safety_config honda_nidec_init(uint16_t param) { }; SET_RX_CHECKS(honda_nidec_common_rx_checks, ret); + SET_TX_MSGS(HONDA_N_TX_MSGS, ret); } - SET_TX_MSGS(HONDA_N_TX_MSGS, ret); - return ret; } @@ -332,6 +391,9 @@ static safety_config honda_bosch_init(uint16_t param) { {0x30C, 0, 8, .check_relay = true}}; // Bosch radarless w/ gas and brakes static CanMsg HONDA_CANFD_TX_MSGS[] = {{0xE4, 0, 5, .check_relay = true}, {0x296, 0, 4, .check_relay = false}, {0x33D, 0, 8, .check_relay = true}}; + static CanMsg HONDA_CANFD_LONG_TX_MSGS[] = {{0xE4, 0, 5, .check_relay = true}, {0x1DF, 0, 8, .check_relay = false}, {0x1EF, 0, 8, .check_relay = false}, + {0x30C, 0, 8, .check_relay = false}, {0x33D, 0, 8, .check_relay = true}, {0x18DAB0F1, 0, 8, .check_relay = false}, + {0x39F, 0, 8, .check_relay = false}}; const uint16_t HONDA_PARAM_ALT_BRAKE = 1; @@ -364,6 +426,7 @@ static safety_config honda_bosch_init(uint16_t param) { honda_bosch_canfd = GET_FLAG(param, HONDA_PARAM_BOSCH_CANFD); // Checking for alternate brake override from safety parameter honda_alt_brake_msg = GET_FLAG(param, HONDA_PARAM_ALT_BRAKE); + enable_gas_interceptor = false; // radar disabled so allow gas/brakes #ifdef ALLOW_DEBUG @@ -393,7 +456,11 @@ static safety_config honda_bosch_init(uint16_t param) { SET_TX_MSGS(HONDA_RADARLESS_TX_MSGS, ret); } } else if (honda_bosch_canfd) { - SET_TX_MSGS(HONDA_CANFD_TX_MSGS, ret); + if (honda_bosch_long) { + SET_TX_MSGS(HONDA_CANFD_LONG_TX_MSGS, ret); + } else { + SET_TX_MSGS(HONDA_CANFD_TX_MSGS, ret); + } } else { if (honda_bosch_long) { SET_TX_MSGS(HONDA_BOSCH_LONG_TX_MSGS, ret); diff --git a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h index f9dbcf592..c9152212a 100644 --- a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h +++ b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h @@ -77,7 +77,7 @@ static uint32_t hyundai_canfd_get_checksum(const CANPacket_t *msg) { } static void hyundai_canfd_rx_all_hook(const CANPacket_t *msg) { - UNUSED(msg); + SAFETY_UNUSED(msg); } static void hyundai_canfd_rx_hook(const CANPacket_t *msg) { diff --git a/opendbc_repo/opendbc/safety/safety.h b/opendbc_repo/opendbc/safety/safety.h index cd948e7dc..79b76712f 100644 --- a/opendbc_repo/opendbc/safety/safety.h +++ b/opendbc_repo/opendbc/safety/safety.h @@ -476,6 +476,14 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { current_safety_config.tx_msgs_len = 0; current_safety_config.disable_forwarding = false; + enable_gas_interceptor = false; + gas_interceptor_prev = 0; + + aol_allowed = false; + lkas_button_prev = false; + lkas_on = false; + main_button_prev = false; + int set_status = -1; // not set int hook_config_count = sizeof(safety_hook_registry) / sizeof(safety_hook_config); for (int i = 0; i < hook_config_count; i++) { @@ -499,14 +507,6 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { } } - enable_gas_interceptor = false; - gas_interceptor_prev = 0; - - aol_allowed = false; - lkas_button_prev = false; - lkas_on = false; - main_button_prev = false; - return set_status; } diff --git a/opendbc_repo/opendbc/safety/tests/common.py b/opendbc_repo/opendbc/safety/tests/common.py index 64fd83310..aa78a84bd 100644 --- a/opendbc_repo/opendbc/safety/tests/common.py +++ b/opendbc_repo/opendbc/safety/tests/common.py @@ -1003,7 +1003,7 @@ class SafetyTest(SafetyTestBase): tx = list(filter(lambda m: m[0] not in [0x30c, ], tx)) # Volkswagen MQB and Honda Bosch Radarless ACC HUD messages overlap - if attr == 'TestVolkswagenMqbLongSafety' and current_test.startswith('TestHondaBoschRadarless'): + if attr == 'TestVolkswagenMqbLongSafety' and current_test.startswith(('TestHondaBoschRadarless', 'TestHondaBoschCANFDLong')): tx = list(filter(lambda m: m[0] not in [0x30c, ], tx)) # TODO: Temporary, should be fixed in panda firmware, safety_honda.h diff --git a/opendbc_repo/opendbc/safety/tests/test_honda.py b/opendbc_repo/opendbc/safety/tests/test_honda.py index e65885774..0641e9cad 100755 --- a/opendbc_repo/opendbc/safety/tests/test_honda.py +++ b/opendbc_repo/opendbc/safety/tests/test_honda.py @@ -6,7 +6,7 @@ from opendbc.car.honda.values import HondaSafetyFlags from opendbc.safety.tests.libsafety import libsafety_py import opendbc.safety.tests.common as common from opendbc.car.structs import CarParams -from opendbc.safety.tests.common import CANPackerSafety, MAX_WRONG_COUNTERS +from opendbc.safety.tests.common import CANPackerSafety, GasInterceptorSafetyTest, MAX_WRONG_COUNTERS HONDA_N_COMMON_TX_MSGS = [[0xE4, 0], [0x194, 0], [0x1FA, 0], [0x30C, 0], [0x33D, 0]] @@ -18,6 +18,16 @@ class Btn: SET = 3 RESUME = 4 + +def honda_interceptor_msg(gas: int, addr: int, counter: int = 0): + to_send = common.make_msg(0, addr, 6) + to_send[0].data[0] = (gas >> 8) & 0xFF + to_send[0].data[1] = gas & 0xFF + to_send[0].data[2] = (gas >> 8) & 0xFF + to_send[0].data[3] = gas & 0xFF + to_send[0].data[4] = counter & 0xF + return to_send + # Honda safety has several different configurations tested here: # * Nidec # * normal (PCM-enable) @@ -366,6 +376,45 @@ class TestHondaNidecPcmAltSafety(TestHondaNidecPcmSafety): return self.packer.make_can_msg_safety("SCM_BUTTONS", bus, values) +class TestHondaNidecGasInterceptorSafety(GasInterceptorSafetyTest, HondaButtonEnableBase, TestHondaNidecSafetyBase): + TX_MSGS = HONDA_N_COMMON_TX_MSGS + [[0x200, 0]] + INTERCEPTOR_THRESHOLD = 492 + + def setUp(self): + self.packer = CANPackerSafety("honda_civic_touring_2016_can_generated") + self.safety = libsafety_py.libsafety + self.safety.set_safety_hooks(CarParams.SafetyModel.hondaNidec, HondaSafetyFlags.GAS_INTERCEPTOR) + self.safety.init_tests() + + def _interceptor_gas_cmd(self, gas: int): + self.__class__.cnt_gas_cmd += 1 + return honda_interceptor_msg(gas, 0x200) + + def _interceptor_user_gas(self, gas: int): + msg = honda_interceptor_msg(gas, 0x201, self.__class__.cnt_user_gas) + self.__class__.cnt_user_gas += 1 + return msg + + +class TestHondaNidecAltGasInterceptorSafety(TestHondaNidecGasInterceptorSafety): + def setUp(self): + self.packer = CANPackerSafety("acura_ilx_2016_can_generated") + self.safety = libsafety_py.libsafety + self.safety.set_safety_hooks(CarParams.SafetyModel.hondaNidec, HondaSafetyFlags.NIDEC_ALT | HondaSafetyFlags.GAS_INTERCEPTOR) + self.safety.init_tests() + + def _acc_state_msg(self, main_on): + values = {"MAIN_ON": main_on, "COUNTER": self.cnt_acc_state % 4} + self.__class__.cnt_acc_state += 1 + return self.packer.make_can_msg_safety("SCM_BUTTONS", self.PT_BUS, values) + + def _button_msg(self, buttons, main_on=False, bus=None): + bus = self.PT_BUS if bus is None else bus + values = {"CRUISE_BUTTONS": buttons, "MAIN_ON": main_on, "COUNTER": self.cnt_button % 4} + self.__class__.cnt_button += 1 + return self.packer.make_can_msg_safety("SCM_BUTTONS", bus, values) + + # ********************* Honda Bosch ********************** @@ -454,7 +503,7 @@ class TestHondaBoschLongSafety(HondaButtonEnableBase, TestHondaBoschSafetyBase): Covers the Honda Bosch safety mode with longitudinal control """ NO_GAS = -30000 - MAX_GAS = 2000 + MAX_GAS = 2200 MAX_ACCEL = 2.0 # accel is used for brakes, but openpilot can set positive values MIN_ACCEL = -3.5 @@ -491,7 +540,7 @@ class TestHondaBoschLongSafety(HondaButtonEnableBase, TestHondaBoschSafetyBase): def test_gas_safety_check(self): for controls_allowed in [True, False]: for gas in np.arange(self.NO_GAS, self.MAX_GAS + 2000, 100): - accel = 0 if gas < 0 else gas / 1000 + accel = 0 if gas < 0 else min(gas / 1000, self.MAX_ACCEL) self.safety.set_controls_allowed(controls_allowed) send = (controls_allowed and 0 <= gas <= self.MAX_GAS) or gas == self.NO_GAS self.assertEqual(send, self._tx(self._send_gas_brake_msg(gas, accel)), (controls_allowed, gas, accel)) @@ -604,6 +653,25 @@ class TestHondaBoschCANFDAltBrakeSafety(HondaPcmEnableBase, TestHondaBoschCANFDS self.safety.init_tests() +class TestHondaBoschCANFDLongSafety(TestHondaBoschLongSafety, TestHondaBoschCANFDSafetyBase): + """ + Covers the Honda Bosch CANFD safety mode with longitudinal control + """ + + PT_BUS = 0 + STEER_BUS = 0 + BUTTONS_BUS = 0 + + TX_MSGS = [[0xE4, 0], [0x1DF, 0], [0x1EF, 0], [0x30C, 0], [0x33D, 0], [0x39F, 0], [0x18DAB0F1, 0]] + FWD_BLACKLISTED_ADDRS = {2: [0xE4, 0x33D]} + RELAY_MALFUNCTION_ADDRS = {0: (0xE4, 0x33D)} + + def setUp(self): + super().setUp() + self.safety.set_safety_hooks(CarParams.SafetyModel.hondaBosch, HondaSafetyFlags.BOSCH_CANFD | HondaSafetyFlags.BOSCH_LONG) + self.safety.init_tests() + + if __name__ == "__main__": unittest.main() diff --git a/panda/board/obj/gitversion.h b/panda/board/obj/gitversion.h index 2ae2f29b4..19b9fc26f 100644 --- a/panda/board/obj/gitversion.h +++ b/panda/board/obj/gitversion.h @@ -1,2 +1,2 @@ extern const uint8_t gitversion[19]; -const uint8_t gitversion[19] = "DEV-334481c7-DEBUG"; +const uint8_t gitversion[19] = "DEV-471a33d6-DEBUG"; diff --git a/panda/board/obj/version b/panda/board/obj/version index cd3566090..abf219584 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-334481c7-DEBUG \ No newline at end of file +DEV-471a33d6-DEBUG \ No newline at end of file