mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-18 07:42:08 +08:00
Ioniq 6 BSM: gate side-detect on front-radar lane position
The raw SIDE_DETECT_STATE bit lit the mirrors and UI for cars two lanes over and for far-ahead traffic, because it has no lane discrimination. Gate it on a front-radar track actually in the adjacent lane (LONG<15m, 1.9<=|LAT|<=5.5m), latched while side-detect stays asserted with a 0.5s release debounce and 1s track-near memory, computed in carstate so the UI path, lane-change veto, and mirror/cluster spoof all consume the same gated signal. card stashes track adjacency from RadarData; fails open to the raw bit if the stash goes stale. Validated on onelane/twolanes/ trafficonleft/standstillmirror rlogs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,6 @@ MAX_ANGLE = 85
|
||||
MAX_ANGLE_FRAMES = 89
|
||||
MAX_ANGLE_CONSECUTIVE_FRAMES = 2
|
||||
CANFD_BLINDSPOT_STATUS_STALE_NS = 200_000_000
|
||||
# fail-open window for the radar-track BSM gate; tracks refresh at 20Hz from card
|
||||
IONIQ_6_BLINDSPOT_TRACK_STALE_NS = 1_000_000_000
|
||||
CANFD_CAMERA_LEAD_STALE_NS = 300_000_000
|
||||
CANFD_LEAD_MIN_DISTANCE = 0.1
|
||||
CANFD_FALLBACK_LEAD_DISTANCE = 20.0
|
||||
@@ -344,8 +342,6 @@ class CarController(CarControllerBase):
|
||||
self.long_active_ecu = self.CP.openpilotLongitudinalControl
|
||||
self._ioniq_6_lane_change_ui_side = None
|
||||
self._ioniq_6_lane_change_ui_frames = 0
|
||||
self._ioniq_6_bsm_gate_left = False
|
||||
self._ioniq_6_bsm_gate_right = False
|
||||
self._ioniq_6_long_tuning = Ioniq6LongitudinalTuningState()
|
||||
self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState()
|
||||
self._dash_lat_disengage_blink_frame = 0
|
||||
@@ -765,22 +761,9 @@ class CarController(CarControllerBase):
|
||||
CS.out.gasPressed, self.CP.carFingerprint))
|
||||
elif not ccnc_non_hda2:
|
||||
can_sends.extend(hyundaicanfd.create_fca_warning_light(self.packer, self.CAN, self.frame))
|
||||
left_blindspot, right_blindspot = CS.left_blindspot_from_radar, CS.right_blindspot_from_radar
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
# The raw SIDE_DETECT_STATE bit also fires on far and two-lanes-over traffic, so gate
|
||||
# the mirror/cluster spoof on a front radar track actually in the adjacent lane, then
|
||||
# latch while side-detect stays asserted so the lamp holds as a passed car slides into
|
||||
# the rear quarter (validated on onelane/twolanes rlogs 2026-07-16). Fails open to the
|
||||
# ungated bit if the track stash goes stale (radar dead / ECU disable failed).
|
||||
tracks_fresh = CS.blindspot_track_update_ts > 0 and \
|
||||
now_nanos - CS.blindspot_track_update_ts < IONIQ_6_BLINDSPOT_TRACK_STALE_NS
|
||||
if tracks_fresh:
|
||||
self._ioniq_6_bsm_gate_left = left_blindspot and (self._ioniq_6_bsm_gate_left or CS.left_blindspot_track_near)
|
||||
self._ioniq_6_bsm_gate_right = right_blindspot and (self._ioniq_6_bsm_gate_right or CS.right_blindspot_track_near)
|
||||
left_blindspot, right_blindspot = self._ioniq_6_bsm_gate_left, self._ioniq_6_bsm_gate_right
|
||||
else:
|
||||
self._ioniq_6_bsm_gate_left = False
|
||||
self._ioniq_6_bsm_gate_right = False
|
||||
# gated + latched in carstate (_update_ioniq_6_bsm_gate): only asserts once a front
|
||||
# radar track confirms the side-detect car is actually in the adjacent lane
|
||||
left_blindspot, right_blindspot = CS.left_blindspot_gated, CS.right_blindspot_gated
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and self.frame % 5 == 0:
|
||||
rear_stale = now_nanos - CS.blindspots_rear_corners_ts > CANFD_BLINDSPOT_STATUS_STALE_NS
|
||||
front_stale = now_nanos - CS.blindspots_front_corner_1_ts > CANFD_BLINDSPOT_STATUS_STALE_NS
|
||||
|
||||
@@ -24,6 +24,13 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu
|
||||
|
||||
IONIQ_6_BLINDSPOT_RIGHT_MASK = 0x08
|
||||
IONIQ_6_BLINDSPOT_LEFT_MASK = 0x10
|
||||
# BSM gate: SIDE_DETECT_STATE fires on far and two-lanes-over traffic, so a side only
|
||||
# asserts after a front radar track confirms a car in the adjacent lane, then latches
|
||||
# while side-detect stays on (rear-quarter hold). Frame counts assume the ~100Hz
|
||||
# carstate update rate. Validated on lanes rlogs 2026-07-16.
|
||||
IONIQ_6_BSM_NEAR_MEMORY_FRAMES = 100 # 1.0s: in-zone track counts toward arming this long
|
||||
IONIQ_6_BSM_SD_RELEASE_FRAMES = 50 # 0.5s: side-detect must stay quiet this long to release
|
||||
IONIQ_6_BSM_STASH_STALE_FRAMES = 100 # 1.0s without a radar stash update -> fail open to raw
|
||||
CANFD_CAMERA_LEAD_MIN_DISTANCE = 0.1
|
||||
ALT_BUS_LDA_BUTTON_BURST_DEBOUNCE_NS = int(1.3e9)
|
||||
|
||||
@@ -68,6 +75,35 @@ class CarState(CarStateBase):
|
||||
return "LEFT_LAMP_ALT", "RIGHT_LAMP_ALT"
|
||||
return "LEFT_LAMP", "RIGHT_LAMP"
|
||||
|
||||
def _update_ioniq_6_bsm_gate(self):
|
||||
# Arm a side once side-detect and a recent adjacent-lane radar track agree, hold while
|
||||
# side-detect stays asserted (front radar loses cars that slide into the rear quarter),
|
||||
# release only after side-detect stays quiet for the debounce window (raw bit has
|
||||
# single-frame dropouts, seen in standstillmirror rlog). Fail open to raw side-detect
|
||||
# when the track stash stops updating (radar dead / ECU disable failed).
|
||||
if self.blindspot_track_stash_seq != self._bsm_stash_seq_prev:
|
||||
self._bsm_stash_seq_prev = self.blindspot_track_stash_seq
|
||||
self._bsm_stash_stale_frames = 0
|
||||
else:
|
||||
self._bsm_stash_stale_frames = min(self._bsm_stash_stale_frames + 1, IONIQ_6_BSM_STASH_STALE_FRAMES + 1)
|
||||
tracks_fresh = self.blindspot_track_stash_seq > 0 and self._bsm_stash_stale_frames <= IONIQ_6_BSM_STASH_STALE_FRAMES
|
||||
|
||||
for side, sd, near in (("left", self.left_blindspot_from_radar, self.left_blindspot_track_near),
|
||||
("right", self.right_blindspot_from_radar, self.right_blindspot_track_near)):
|
||||
self._bsm_near_gap_frames[side] = 0 if near else min(self._bsm_near_gap_frames[side] + 1,
|
||||
IONIQ_6_BSM_NEAR_MEMORY_FRAMES + 1)
|
||||
self._bsm_sd_gap_frames[side] = 0 if sd else min(self._bsm_sd_gap_frames[side] + 1,
|
||||
IONIQ_6_BSM_SD_RELEASE_FRAMES + 1)
|
||||
if not tracks_fresh:
|
||||
self._bsm_latched[side] = sd
|
||||
elif sd and self._bsm_near_gap_frames[side] <= IONIQ_6_BSM_NEAR_MEMORY_FRAMES:
|
||||
self._bsm_latched[side] = True
|
||||
elif self._bsm_sd_gap_frames[side] > IONIQ_6_BSM_SD_RELEASE_FRAMES:
|
||||
self._bsm_latched[side] = False
|
||||
|
||||
self.left_blindspot_gated = self._bsm_latched["left"]
|
||||
self.right_blindspot_gated = self._bsm_latched["right"]
|
||||
|
||||
def __init__(self, CP, FPCP):
|
||||
super().__init__(CP, FPCP)
|
||||
can_define = CANDefine(DBC[CP.carFingerprint][Bus.pt])
|
||||
@@ -136,12 +172,22 @@ class CarState(CarStateBase):
|
||||
self.blindspots_front_corner_1_ts = 0
|
||||
self.left_blindspot_from_radar = False
|
||||
self.right_blindspot_from_radar = False
|
||||
# Radar-track adjacency for the spoofed BSM mirror gate, stashed by card from
|
||||
# RadarData (front radar tracks). ts stays 0 when never updated, which makes
|
||||
# the carcontroller gate fail open to the ungated side-detect bit.
|
||||
# Radar-track adjacency stashed by card from RadarData (front radar tracks);
|
||||
# card bumps stash_seq on every radar update so staleness is clock-free.
|
||||
self.left_blindspot_track_near = False
|
||||
self.right_blindspot_track_near = False
|
||||
self.blindspot_track_update_ts = 0
|
||||
self.blindspot_track_stash_seq = 0
|
||||
# Gated + latched BSM state (single source for ret.left/rightBlindspot and the
|
||||
# carcontroller mirror/cluster spoofs)
|
||||
self.left_blindspot_gated = False
|
||||
self.right_blindspot_gated = False
|
||||
self._bsm_stash_seq_prev = 0
|
||||
self._bsm_stash_stale_frames = IONIQ_6_BSM_STASH_STALE_FRAMES + 1
|
||||
self._bsm_near_gap_frames = {"left": IONIQ_6_BSM_NEAR_MEMORY_FRAMES + 1,
|
||||
"right": IONIQ_6_BSM_NEAR_MEMORY_FRAMES + 1}
|
||||
self._bsm_sd_gap_frames = {"left": IONIQ_6_BSM_SD_RELEASE_FRAMES + 1,
|
||||
"right": IONIQ_6_BSM_SD_RELEASE_FRAMES + 1}
|
||||
self._bsm_latched = {"left": False, "right": False}
|
||||
|
||||
# On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz
|
||||
self.cluster_speed = 0
|
||||
@@ -472,12 +518,16 @@ class CarState(CarStateBase):
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
self.left_blindspot_from_radar, self.right_blindspot_from_radar = decode_ioniq_6_blindspot_radar_state(
|
||||
cp.vl["BLINDSPOTS_FRONT_CORNER_2"]["SIDE_DETECT_STATE"])
|
||||
self._update_ioniq_6_bsm_gate()
|
||||
if self.CP.enableBsm:
|
||||
if self.CP.carFingerprint == CAR.HYUNDAI_IONIQ_6:
|
||||
# gated side-detect: raw bit also fires on far and two-lanes-over traffic. With OP
|
||||
# long the ADAS ECU is disabled so the lane-filtered BCW verdict is gone entirely;
|
||||
# with stock long the BCW verdict stays primary and the gate only cleans the OR-in.
|
||||
ret.leftBlindspot = (bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtIndSta"]) or
|
||||
self.left_blindspot_from_radar)
|
||||
self.left_blindspot_gated)
|
||||
ret.rightBlindspot = (bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_RtIndSta"]) or
|
||||
self.right_blindspot_from_radar)
|
||||
self.right_blindspot_gated)
|
||||
elif self.CP.flags & HyundaiFlags.CCNC:
|
||||
ret.leftBlindspot = bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_LtIndSta"])
|
||||
ret.rightBlindspot = bool(cp.vl["BLINDSPOTS_REAR_CORNERS"]["BCW_RtIndSta"])
|
||||
|
||||
@@ -238,7 +238,6 @@ class Car:
|
||||
if can_rcv_valid and REPLAY:
|
||||
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime
|
||||
|
||||
# RD is only non-None on frames with valid CAN, so can_log_mono_time is set by here
|
||||
self._update_blindspot_track_state(RD)
|
||||
|
||||
preap_software_cruise = (
|
||||
@@ -389,10 +388,9 @@ class Car:
|
||||
self.CI.CS.openpilot_lead_rel_speed = lead_rel_speed
|
||||
|
||||
def _update_blindspot_track_state(self, RD: structs.RadarDataT | None) -> None:
|
||||
# Stash radar-track adjacency for the Hyundai CAN-FD spoofed-BSM mirror gate
|
||||
# (yRel > 0 is left). Skipped frames leave the previous stash in place; if the
|
||||
# radar stops updating entirely, the timestamp goes stale and the carcontroller
|
||||
# gate fails open to its ungated side-detect input.
|
||||
# Stash radar-track adjacency for the Hyundai CAN-FD BSM gate (yRel > 0 is left).
|
||||
# Skipped frames leave the previous stash in place; the seq bump lets carstate
|
||||
# detect staleness without cross-clock math and fail open to raw side-detect.
|
||||
if RD is None or any(RD.errors.to_dict().values()):
|
||||
return
|
||||
|
||||
@@ -406,7 +404,7 @@ class Car:
|
||||
|
||||
self.CI.CS.left_blindspot_track_near = left
|
||||
self.CI.CS.right_blindspot_track_near = right
|
||||
self.CI.CS.blindspot_track_update_ts = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9)
|
||||
self.CI.CS.blindspot_track_stash_seq = getattr(self.CI.CS, "blindspot_track_stash_seq", 0) + 1
|
||||
|
||||
def _update_redneck_cruise(self, CS: car.CarState, CC: car.CarControl) -> None:
|
||||
if self.redneck_cruise is None:
|
||||
|
||||
Reference in New Issue
Block a user