diff --git a/opendbc/car/car.capnp b/opendbc/car/car.capnp index d2b8d758a7c..d8ed3232ca8 100644 --- a/opendbc/car/car.capnp +++ b/opendbc/car/car.capnp @@ -358,6 +358,7 @@ struct CarControl { cruiseControl @4 :CruiseControl; hudControl @5 :HUDControl; + forceDecel @18 :Bool; struct Actuators { # lateral commands, mutually exclusive diff --git a/opendbc/car/nissan/carcontroller.py b/opendbc/car/nissan/carcontroller.py index 16f990a8241..ac73d3d1fc4 100644 --- a/opendbc/car/nissan/carcontroller.py +++ b/opendbc/car/nissan/carcontroller.py @@ -39,8 +39,7 @@ def update(self, CC, CS, now_nanos): else: # Scale max torque based on how much torque the driver is applying to the wheel lkas_max_torque = max( - # Scale max torque down to half LKAX_MAX_TORQUE as a minimum - CarControllerParams.LKAS_MAX_TORQUE * 0.5, + 0.2, # Start scaling torque at STEER_THRESHOLD CarControllerParams.LKAS_MAX_TORQUE - 0.6 * max(0, abs(CS.out.steeringTorque) - CarControllerParams.STEER_THRESHOLD) ) @@ -58,6 +57,10 @@ def update(self, CC, CS, now_nanos): can_sends.append(nissancan.create_steering_control( self.packer, self.apply_angle_last, self.frame, CC.latActive, lkas_max_torque)) + # Use stock driver attentiveness warning when forcing a deceleration + for steer_torque_sensor_msg in CS.steer_torque_sensor_msgs: + can_sends.append(nissancan.create_steer_torque_sensor(self.packer, steer_torque_sensor_msg, CC.latActive, CC.forceDecel)) + # Below are the HUD messages. We copy the stock message and modify if self.CP.carFingerprint != CAR.NISSAN_ALTIMA: if self.frame % 2 == 0: diff --git a/opendbc/car/nissan/carstate.py b/opendbc/car/nissan/carstate.py index 1bd5ad178b4..6f06cf356bb 100644 --- a/opendbc/car/nissan/carstate.py +++ b/opendbc/car/nissan/carstate.py @@ -18,6 +18,7 @@ def __init__(self, CP): self.lkas_hud_msg = {} self.lkas_hud_info_msg = {} + self.steer_torque_sensor_msgs = [{}] self.steeringTorqueSamples = deque(TORQUE_SAMPLES*[0], TORQUE_SAMPLES) self.shifter_values = can_define.dv["GEARBOX"]["GEAR_SHIFTER"] @@ -91,9 +92,13 @@ def update(self, can_parsers) -> structs.CarState: if self.CP.carFingerprint == CAR.NISSAN_ALTIMA: ret.steeringTorque = cp_cam.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"] ret.steerFaultTemporary = cp_cam.vl["STEER_TORQUE_SENSOR"]["LKAS_STATUS"] == 9 + # self.steer_torque_sensor_msgs = cp_cam.vl_all["STEER_TORQUE_SENSOR"] + adas_status_msgs = cp_cam.vl_all["STEER_TORQUE_SENSOR"] + self.steer_torque_sensor_msgs = [dict(zip(adas_status_msgs, vals, strict=True)) for vals in zip(*adas_status_msgs.values(), strict=True)] else: ret.steeringTorque = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"] ret.steerFaultTemporary = cp.vl["STEER_TORQUE_SENSOR"]["LKAS_STATUS"] == 9 + self.steer_torque_sensor_msgs = cp.vl_all["STEER_TORQUE_SENSOR"] self.steeringTorqueSamples.append(ret.steeringTorque) # Filtering driver torque to prevent steeringPressed false positives @@ -114,12 +119,11 @@ def update(self, can_parsers) -> structs.CarState: can_gear = int(cp.vl["GEARBOX"]["GEAR_SHIFTER"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) - # stock lkas should be off - # TODO: is this needed? + # stock lkas should be on to allow stock driver monitoring system to progress when desired if self.CP.carFingerprint == CAR.NISSAN_ALTIMA: - ret.invalidLkasSetting = bool(cp.vl["LKAS_SETTINGS"]["LKAS_ENABLED"]) + ret.invalidLkasSetting = not bool(cp.vl["LKAS_SETTINGS"]["LKAS_ENABLED"]) else: - ret.invalidLkasSetting = bool(cp_adas.vl["LKAS_SETTINGS"]["LKAS_ENABLED"]) + ret.invalidLkasSetting = not bool(cp_adas.vl["LKAS_SETTINGS"]["LKAS_ENABLED"]) self.cruise_throttle_msg = copy.copy(cp.vl["CRUISE_THROTTLE"]) diff --git a/opendbc/car/nissan/nissancan.py b/opendbc/car/nissan/nissancan.py index 24a49e02714..1694f322727 100644 --- a/opendbc/car/nissan/nissancan.py +++ b/opendbc/car/nissan/nissancan.py @@ -5,6 +5,24 @@ nissan_checksum = mk_crc8_fun(CRC8J1850, init_crc=0x00, xor_out=0xFF) +def create_steer_torque_spoof(packer, bus, steer_torque_msg, driver_torque): + # Re-emit EPS's STEER_TORQUE_SENSOR on the camera-side bus with a spoofed + # STEER_TORQUE_DRIVER to satisfy ProPilot's hands-on check. The real message + # from bus 0 must be blocked in safety so only this one reaches the ADAS ECU. + values = {s: steer_torque_msg[s] for s in [ + "LKAS_ACTIVE", + "LKAS_STATUS", + "STEER_TORQUE_LKAS", + "STEER_ANGLE", + "COUNTER", + ]} + values["STEER_TORQUE_DRIVER"] = driver_torque + + dat = packer.make_can_msg("STEER_TORQUE_SENSOR", bus, values)[1] + values["CHECKSUM"] = nissan_checksum(dat[:7]) + return packer.make_can_msg("STEER_TORQUE_SENSOR", bus, values) + + def create_steering_control(packer, apply_torque, frame, steer_on, lkas_max_torque): values = { "COUNTER": frame % 0x10, @@ -21,6 +39,30 @@ def create_steering_control(packer, apply_torque, frame, steer_on, lkas_max_torq return packer.make_can_msg("LKAS", 0, values) +def create_steer_torque_sensor(packer, steer_torque_sensor_msg: dict, lat_active: bool, force_decel: bool): + values = {s: steer_torque_sensor_msg[s] for s in [ + "STEER_TORQUE_DRIVER", + "STEER_ANGLE", + "LKAS_ACTIVE", + "STEER_TORQUE_LKAS", + "COUNTER", + "LKAS_STATUS", + "CHECKSUM", + ]} + + # Starts stock driver monitoring progression by setting driver torque to 0 + if force_decel: + values["STEER_TORQUE_DRIVER"] = 0.0 + elif lat_active: + # When steering normally we need to silence stock DM system + values["STEER_TORQUE_DRIVER"] = 1.0 + + dat = packer.make_can_msg("STEER_TORQUE_SENSOR", 2, values)[1] + + values["CHECKSUM"] = nissan_checksum(dat[:7]) + return packer.make_can_msg("STEER_TORQUE_SENSOR", 2, values) + + def create_acc_cancel_cmd(packer, car_fingerprint, cruise_throttle_msg): values = {s: cruise_throttle_msg[s] for s in [ "COUNTER", diff --git a/opendbc/car/tests/car_diff.py b/opendbc/car/tests/car_diff.py index b4424be8975..6bfe4914a78 100755 --- a/opendbc/car/tests/car_diff.py +++ b/opendbc/car/tests/car_diff.py @@ -293,8 +293,16 @@ def main(platform: str | None = None, segments_per_platform: int = 10, update_re icon = "⚠️" if with_diffs else "✅" print(f"\n{icon} {len(with_diffs)} changed, {n_passed} passed, {len(errors)} errors") - for plat, seg, err in errors: - print(f"\nERROR {plat} - {seg}: {err}") + if errors: + # Group identical errors so a single bug doesn't spam the report with one traceback per segment + by_err: dict[str, list[str]] = defaultdict(list) + for plat, seg, err in errors: + by_err[err].append(f"{plat} - {seg}") + print("
Show errors\n\n```") + for err, segs in sorted(by_err.items(), key=lambda kv: -len(kv[1])): + affected = ", ".join(segs[:3]) + (f" (+{len(segs) - 3} more)" if len(segs) > 3 else "") + print(f"\nERROR ({len(segs)}x) {affected}:\n{err.rstrip()}") + print("```\n
") if with_diffs: print("
Show changes\n\n```") diff --git a/opendbc/dbc/generator/nissan/_nissan_common.dbc b/opendbc/dbc/generator/nissan/_nissan_common.dbc index a2ddd30905b..49e65a05a18 100644 --- a/opendbc/dbc/generator/nissan/_nissan_common.dbc +++ b/opendbc/dbc/generator/nissan/_nissan_common.dbc @@ -26,6 +26,9 @@ BO_ 645 WHEEL_SPEEDS_REAR: 8 XXX SG_ WHEEL_SPEED_RR : 7|16@0+ (0.005,0) [0|65535] "KPH" XXX SG_ WHEEL_SPEED_RL : 23|16@0+ (0.005,0) [0|65535] "KPH" XXX +BO_ 658 ACCEL: 8 XXX + SG_ EGO_ACCEL : 7|16@0+ (0.0005,-16.5) [-16.5|16.27] "m/s^2" XXX + BO_ 689 PROPILOT_HUD: 8 XXX SG_ LARGE_WARNING_FLASHING : 9|1@0+ (1,0) [0|1] "" XXX SG_ SIDE_RADAR_ERROR_FLASHING1 : 10|1@0+ (1,0) [0|1] "" XXX diff --git a/opendbc/safety/modes/nissan.h b/opendbc/safety/modes/nissan.h index 9289935f80c..8cd0851a531 100644 --- a/opendbc/safety/modes/nissan.h +++ b/opendbc/safety/modes/nissan.h @@ -104,7 +104,8 @@ static safety_config nissan_init(uint16_t param) { {0x4cc, 0, 8, .check_relay = true}, // PROPILOT_HUD_INFO_MSG {0x20b, 2, 6, .check_relay = false}, // CRUISE_THROTTLE (X-Trail) {0x20b, 1, 6, .check_relay = false}, // CRUISE_THROTTLE (Altima) - {0x280, 2, 8, .check_relay = true} // CANCEL_MSG (Leaf) + {0x280, 2, 8, .check_relay = true}, // CANCEL_MSG (Leaf) + {0x185, 2, 8, .check_relay = true}, // STEER_TORQUE_SENSOR }; // Signals duplicated below due to the fact that these messages can come in on either CAN bus, depending on car model.