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.