Skip to content

Hyundai CAN-FD: stock-navigation speed cameras, lever lane change/turn, turn display, forced-SCC gate - #547

Closed
joongyu01 wants to merge 71 commits into
carrot-wipfrom
joongyu01/carrot-wip-dev
Closed

joongyu01 wants to merge 71 commits into
carrot-wipfrom
joongyu01/carrot-wip-dev

Conversation

@joongyu01

Copy link
Copy Markdown
Collaborator

Ioniq 5 PE (HDA II, CAN-FD) 실차로 09-27~10-13 동안 쓰면서 만든 joongyu01/carrot-wip-dev의 변경 전부입니다. carrot-wip 최신(7dd3c11e0)까지 merge되어 있고 충돌은 없습니다. 30커밋, 40파일.

순정 내비 과속카메라 (Hyundai CAN-FD 0x4BE/0x4A3)

  • 카메라 종류 판별 navi_speedcam.py: 고정식·신호·박스(이동식 카메라함)·이동식 구간·후면. 0x4A3 경고에 같은 속도의 0x4BE 예고를 붙여 종류를 정함.
  • 감속 중지 요청: 인게이지 중 + 또는 짧은 악셀톡으로 해당 경고 하나만 건너뜀. 박스·이동식 구간·방지턱·미탐지(실험)·조기경고 보정(실험). 고정식·신호·후면은 항상 감속.
  • AutoNaviSpeedCtrlMode ≤ 2에서 이동식 구간 경고 무시.
  • 구간단속 평균: 구간 제한 × factor 상한을 +/악셀톡으로 풀고 남은 시간으로 평균을 맞춤. − 길게 다시 잠금.
  • 10-11 실제 사례 수정(64f4d27e4): 한 경고 안에 이동식 구간 뒤 고정식(구간 시작 카메라)이 이어지면 경고 전체가 이동식으로 남아 고정식 앞에서 감속하지 않던 문제. 과거 31 route 재생에서 같은 패턴 3건 추가 확인, 판정은 감속 쪽으로만 바뀜.

차로 변경 · 레버

  • 레버 누름 횟수·위치를 carState로(leftBlinkerStalkCount/rightBlinkerStalkCount/blinkerLever).
  • 차로 변경 중 깜빡이 유지, 차 중심이 새 차로 폭 60%를 넘으면 해제.
  • 선을 넘은 뒤 레버를 다시 누르면 다음 차로 변경 요청.
  • BlinkerLatchedTurn(km/h, 기본 0=끔): 설정 속도 이하에서 레버 고정 = 좌/우회전, 원터치 = 차로 변경. LaneChangeLeverWait(기본 끔): 원터치 요청 대기.

회전 표시

  • 회전 desire(turnLeft/turnRight) 중 계기판 0x161 BACKGROUND=6 빨간 점멸(패들 유지와 같은 표시).
  • 조향 활성 중 회전 desire 0.2 s 유지 시 "Turn", 끝나고 0.5 s 후 "Straight" 음성 1회(selfdrived/turn_prompt.py, PERMANENT 알림 — 조향만 켜진 상태에서도 울림). 음성은 piper en_US-lessac-medium, sounds_eng.

레이더

  • EnableRadarTracks=3: 같은 차체의 전방·코너 트랙이 모두 경로 밖이면 강제 SCC를 채택하지 않음(1초 유지). 같은 거리 중앙 비전·경로 안 트랙·차로변경 중 저속은 예외. 33 route 강제 SCC 59,388프레임 중 7.6% 거부, 영상 확인 결과 옆 차로·주차차량.

스키마 · 설정

  • car.capnp: CarState @100~102(레버), AudibleAlert turnModel @38·turnModelEnd @39.
  • log.capnp: ModelDataV2.Meta laneChangeBlinkerHold @20·leverTurn @21, EventName turnModel @130·turnModelEnd @131.
  • 새 설정: VehicleNaviDecelCancel*(상위 기본 끔), VehicleNaviSectionAvgControl(끔), BlinkerLatchedTurn(0), LaneChangeLeverWait(끔).
  • 설정 없이 바뀌는 동작: 레이더 모드 3 게이트, 회전 빨간 점멸·음성, 깜빡이 유지·다음 차로 요청, 이동식+고정식 연속 경고 처리(VehicleNaviCanControl 사용 시).

검증

  • 데스크톱 테스트(Windows, Params·POSIX 스텁): hyundai 1873 통과(실패 6 = 생성 레이더 DBC 없음), 레이더 662(실패 4 = pyray 없음), 강제 SCC 게이트 9, 설정 schema 47, 레버 desire 25, 턴 음성 4. msgq 필요한 selfdrived 알림 테스트·test_radard_dpath는 이 환경에서 미실행.
  • 실차: 우리 차(dongle f54fcc4d13d575f6) 10-06·10-09·10-11 주행 rlog로 브랜치 커밋 확인 후 재생·영상 대조. 다른 차종 실차 검증은 없음.

🤖 Generated with Claude Code

joongyu01 and others added 30 commits October 4, 2026 12:30
Attach the 0x4BE preview kind to the current 0x4A3 warning (matched at the
warning lead of ~6.1 m per km/h) so stock CAN cameras can be handled by kind:

- fixed (kind 0), signal (kind 1) and rear (value >> 9 flag) always decelerate
- mobile enforcement zones (kind 3) follow AutoNaviSpeedCtrlMode: 3 decelerates,
  2 or lower ignores the zone warning
- VehicleNaviSkipBoxCamera / VehicleNaviSkipMobileZone: one cruise + press
  releases that camera's deceleration without raising the set speed
- VehicleNaviSectionAvgControl / VehicleNaviSectionAvgMargin: inside a section,
  allow up to the set speed only while the running average stays at or below
  limit - margin (time banked in congestion); start/end cameras keep spot decel

Kinds were matched against the stock navigation screen and labelled drives
(2026-09-27). All new settings default off; mobile-zone handling changes the
stock CAN behaviour when AutoNaviSpeedCtrlMode is below 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ret.buttonEvents = self._swallow_speedcam_accel(ret.buttonEvents)` re-assigned
the capnp list field from itself. pycapnp clears the field before copying, so
every button event became unknown/released: steering-wheel buttons stopped
working, cruiseState.available flickered (wrongCarMode) and Bluetooth remote
commands were dropped while those events were present.

Filter the Python list before the single assignment and add a regression test
on a real capnp CarState builder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A labelled rear speed camera in a 30 km/h school zone arrived as 0x64270:
kind 0 with the same 0x321 flag seen on rear signal-and-speed cameras
(kind 1). Record the flag as kind-independent and cover it in tests/docs.
Behaviour is unchanged: any flagged preview already always decelerates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A labelled mobile-camera box at 100 km/h arrived as preview 0x152 (kind 2)
and matched its warning within 1 m of the expected lead, ending at the
camera like other spot cameras. Drop the "presumed/experimental" wording
for box detection in the policy notes, settings descriptions and docs.
Behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Camera skip (box / mobile zone) now also accepts a gas tap, and only while
  engaged; the spent input raises neither +1 nor the gas-tap +10
  (card passes carstate's flag to cruise.py, which ignores that tap).
- Section average control is now manual: the section stays capped at
  limit x AutoNaviSpeedSafetyFactor as before until one cruise + or gas tap
  (engaged, set speed above the cap) unlocks it. Unlocked, banked time below
  the limit may be spent up to the set speed; a long cruise - press locks it
  again and leaving the section resets it.
- Move the section average bookkeeping into the Hyundai carstate (100 Hz
  inputs); carrot_serv returns to carrot-wip and keeps using
  vehicleNaviSpeed x factor as the section cap.
- Remove VehicleNaviSectionAvgMargin; the target average is the limit.

Verified by replaying real rlog CAN through the full CarInterface.update():
button events match the logged ones, a section replay is identical to the
feature-off run until the tap, then unlocks, spends the bank and resets at
the section end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a section is unlocked, cruise - is now held back from cruise.py the
same way the consumed + and gas tap are. A long press only relocks the
section; cruise.py never sees it, so the set speed no longer drops by the
long-press -10 step (and its repeats while held). A short press is handed
to cruise.py at release as a same-frame press + release pair, which
cruise.py already treats as one short press (the remote-button path), so
the -1 step is unchanged. Locked sections and non-section driving are
untouched.

Tests: hyundai test_navi_speedcam / test_section_average /
test_speed_camera 150 passed (3 new: long press never reaches cruise,
short press forwarded at release incl. capnp assignment, pass-through
while locked); test_steering_touch + test_settings_schema 104 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mobile-zone class suppresses deceleration (AutoNaviSpeedCtrlMode <= 2)
or makes it skippable, so it must be the zone that started the warning.
Before, any same-speed kind 3 preview within -50 m .. lead+tolerance of
the current position could classify it; a warning without its own preview
(signal cameras send none) that began as unknown/hard could therefore be
turned into a zone later in the warning and lose its deceleration.

Now an unflagged kind 3 preview counts only when its target is within
60 m of warning start + 6.1 m x limit, whenever it arrived. Fixed, signal,
box and rear previews keep the existing window.

Data (37 own drives, 1,099 km, 209 warnings, 2026-09-30): all 44 zone
warnings have their kind 3 target at start + lead -12..+17 m, including
two whose preview arrived 7-9 m after the warning began. Replaying the
policy over those drives gives identical classes before and after.

Tests: 153 passed (3 new: late zone preview at the start lead, unrelated
zone cannot release a running warning, off-lead zone ignored at start).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock navigation repeats each 0x4BE camera preview (374 frames for
122 announcements on 2026-09-12). add_preview appended every repeat and
only pruned by distance travelled, so copies piled up while stopped and
the per-frame classification loop grew with them.

A repeat with the same kind, speed and flag within 20 m of an existing
target (the carstate event-merge distance) now refreshes that entry
instead; a precise position replaces a horizon-bound (saturated) one and
is never replaced by a later saturated repeat. Classification is
unchanged: the bulk replay over 37 drives gives the same classes.

Tests: 155 passed (2 new: repeats while stopped stay one entry, precise
repeat replaces a saturated position).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The section allowance was computed from the vEgo odometer and handed to
carrot_serv as vehicleNaviSpeed. carrot_serv multiplies it by
AutoNaviSpeedSafetyFactor and treats it as a cluster speed, and the
planner converts cluster to vEgo with vCluRatio, so the car drove about
factor x vCluRatio = 1.07 x 0.968 = +3.6 % above the allowance while
spending banked time.

- Invert that chain: vehicleNaviSpeed = allowance / DISTANCE_SCALE /
  vCluRatio / factor, so the driven vEgo speed is the allowance.
- Scale the vEgo odometer to true distance in the bank (DISTANCE_SCALE
  1.015). GPS / vEgo on this car is 1.0054-1.0074 in five drives
  (08-07..09-2x, p90 1.012); the 0x4B4 display average runs 3.3 % above
  vEgo because it follows the cluster speed (1/vCluRatio 1.029-1.035), so
  it is not used as the reference.
- Without banked time the cap stays the locked one (limit x factor), as
  before; the docs now say this can exceed the limit by the factor.

Tests: 157 passed. New end-to-end test banks time in congestion, unlocks
and follows the target through carrot_serv/planner scaling with true
speed = vEgo x 1.0074; the true running average stays <= limit while the
bank is spent. It fails on the previous logic (80.10 > 80.1 km/h).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the separate VehicleNaviSkipBoxCamera / VehicleNaviSkipMobileZone
toggles with a single "deceleration cancel request":

- VehicleNaviDecelCancel (master, default off)
  - VehicleNaviDecelCancelBox, VehicleNaviDecelCancelMobileZone,
    VehicleNaviDecelCancelBump (per-kind, default on), shown in the
    master's detail screen via detail_parent like the ShareData ONNX
    settings. Turning on the master enables all kinds.

The old keys only ever existed on this branch, which no device has run.

Speed bumps get the same cancel as box cameras and mobile zones: while
engaged above 5 m/s and the nearest stock-navigation bump is actually
slowing the car (carrot_serv's bump profile from AutoNaviSpeedBumpSpeed /
Time / DecelRate is below the set speed and the bump is beyond
AutoNaviSpeedBumpEndDistance), cruise + or a gas tap marks that bump
cancelled and speedBumpDistance moves on to the next bump. The input is
swallowed from cruise.py as for cameras; a camera skip in the same frame
wins. The cancel survives a repeated announcement of the same bump.

Tests: 162 passed (5 new bump/master tests); settings schema test
rewritten for the master/child keys and the removed ones, 104 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add VehicleNaviDecelCancelFixed under the deceleration cancel request so
+ or a gas tap can also cancel a fixed speed camera warning, with the
same one-press-per-warning logic as box cameras and mobile zones.

- navi_speedcam: unflagged kind 0 gets its own class CLASS_FIXED (below
  CLASS_HARD), skippable only with skip_fixed. Signal, rear-flagged and
  unmatched warnings stay CLASS_HARD and never skip; a warning still
  never downgrades.
- carstate: read the toggle (gated by the master) and pass it to the
  policy, except while a section is active: section start/end cameras
  are kind 0 too, and + must stay the section unlock there.
- The new toggle defaults off even with the master on, since these are
  real enforcement cameras.

Tests: 165 passed (3 new: fixed skip needs its toggle; signal/rear/
unknown never skip; no fixed skip inside a section); settings schema
104 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… across

ADRV_0x1ea LEFT/RIGHT_BLINK_HOLD was set from the model's
desireState[laneChange] > 0.9. That probability falls about 2 s before
laneChangeStarting ends, so the lamp stopped mid-change: on the
2026-09-19 drive (22 changes) the hold covered 49-65 % of a normal change,
the lamp went out before the body was in the new lane in 6/13 measured
changes and once before the car centre crossed the line. The green
"changing" cluster lane, driven by the confirmed desire, stayed on 98-100 %.

Hold the bit for the confirmed desire (laneChangeLeft/Right = starting +
finishing, same source as the green lane) and release it once the model
lane lines show the body across: the target-side line came within 1.0 m
of the car centre and then moved 1.5 m further out (the relabel is spread
over 2-3 model frames), and the crossed line, now on the other side, is
1.0 m away. Without a visible crossing the hold lasts to the end of the
desire (2/22 on that drive).

Replay over the 22 changes: hold released at 3.6 s median (was 3.0 s,
desire ends ~6.0 s), i.e. the lamp now stops about one flash after the
body enters the new lane instead of before it.

Tests: test_blink_hold.py (no model message / short lists, crossing over
several frames, right change, no visible crossing, re-arm next change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hold now ends once the car centre has moved 60 % of a lane width into
the new lane: after the model relabels the crossed line, that line (now on
the other side) is 0.1 lane width (~0.35 m) from the centre. Lane width is
taken from the two model lines around the car. Previously the release
waited for the crossed line to be 1.0 m away (~80 % across).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add VehicleNaviDecelCancelUnknown ([experimental], default off) under the
deceleration cancel request: + or a gas tap may also cancel a warning that
no camera preview was matched to (preview not received or not
understood), with the same one-press-per-warning logic.

Field report 2026-10-04 (another user on this branch): a 60 km/h warning
with no matching camera preview (only a kind 7 speed-zone preview, the
kind 3 zone targets 100 m outside the zone lead window) stayed hard; the
driver could not cancel it and + only stacked the set speed.

Guards: never at 30 km/h or below (school/senior zones warn there, often
without a preview), never inside a section, and a later signal, rear or
fixed match switches back to that kind's rule (signal/rear decelerate).
Off by default and separate from the master because the kind is unknown:
over our 37 drives about 15 of 209 warnings are unmatched point warnings
above 30 km/h (likely signal/fixed cameras without a preview) that this
toggle would make cancellable.

Tests: 168 passed (3 new: toggle + 30 km/h guard, signal match ends the
skip, classified warnings untouched); settings schema 104 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Title the VehicleNaviDecelCancelUnknown toggle by what it does: skip speed
cameras whose kind was not detected ("과속카메라 종류 미탐지 건 건너뛰기
[실험]", en/zh to match). Key and behaviour unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prefix the VehicleNaviDecelCancelUnknown title like the other per-kind
toggles: "감속 중지: 과속카메라 종류 미탐지 건 [실험]" (en/zh to match).
Key and behaviour unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
carState.leftBlinker/rightBlinker on CAN-FD Hyundai is the lamp
(BLINKERS LEFT/RIGHT_LAMP_ALT, 0.4 s on/off, bridged 0.5 s), so a lever
press while the lamp is already flashing is invisible. BLINKER_STALKS
(0x3C1, PT bus, ~5 Hz) carries the lever itself: a 0.04-0.2 s pulse per
press (2026-09-19 drive: 37 presses, matching the driver's actions).
BLINKERS LEFT_STALK (0x413 bit 8) is not the lever but the ~4 s flash
session of a one-touch press.

Add carState.leftBlinkerStalkCount / rightBlinkerStalkCount (UInt8,
wrapping, @95/@96): +1 on each rising edge. A counter cannot be missed by
consumers that sample carState at 20 Hz (modeld's DesireHelper), unlike a
one-frame button event. Read BLINKER_STALKS on the PT bus only: the alt
bus carries an unrelated 24-byte frame with the same 0x3C1 id.

Tests: test_blinker_stalks.py (press vs hold, both sides, UInt8 wrap,
car without the message).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New lane-change input was ignored for the ~6 s of laneChangeStarting +
laneChangeFinishing (the green cluster lane). A lever press made then
starts a 4 s flash, so the lamp-based blinker was still on when the change
ended and the next one fell into preLaneChange with next_lane_change,
waiting for steering torque. Drivers had to wait for the green lane to go
off and press again, losing the gap.

With carState.*BlinkerStalkCount:
- presses while crossing (laneChangeStarting) are ignored, so re-lighting
  the lamp mid-change never asks for a second lane;
- a press in laneChangeFinishing after STALK_QUEUE_GUARD_S (0.3 s) is a
  request: when the change ends the next one waits in preLaneChange
  without the torque requirement, still behind every availability check
  (lane geometry, line type/colour, side objects, BSD) and one lane at a
  time;
- a press while waiting for torque releases that wait;
- a press while the lamp is still flashing starts a request from off.
Cars without the counters keep the lamp behaviour.

A/B replay of the 2026-09-19 drive (61 segments, 72,681 model frames,
logged inputs, A without / B with the logged lever presses): the only
difference is one change where a finishing-phase press cleared the torque
flag (the driver also applied torque, same start frame); the mid-change
press at 1842.86 s is ignored as intended.

Tests: test_desire_helper_stalk.py (7); disabling the lever input fails
the 3 behaviour tests, the 4 unchanged-behaviour tests still pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LatchedTurn)

The turn / lane-change choice (classify_maneuver_type) guesses from speed,
lane lines and the model, and often guesses wrong. The lever already says
it: BLINKER_STALKS (0x3C1) keeps LEFT/RIGHT_BLINKER set while latched, and
the undocumented bits 36/38 (now LEFT/RIGHT_BLINKER_TAP) are set only in the
one-touch detent (2026-10-04 lever test, route 0000048b: 12/12 inputs; a
right latch passed through the detent for ~0.1 s).

- carState.blinkerLever @97: 0 released, 1 one-touch detent, 2 latched.
- BlinkerLatchedTurn (default off, Hyundai CAN-FD only): at or below
  50 km/h with the driver's blinker, latched = turn, otherwise lane change.
  No lane change starts while the lever is still in the one-touch detent,
  so a latch on its way through cannot start one. Above 50 km/h the
  existing classifier is unchanged.

Tests: lever position (one-touch / latched / passing the detent); off keeps
the classifier, latched = turn, detent blocks the start, one-touch starts
after the lever returns, latched above 50 km/h stays a lane change.
Hyundai/desire/settings/DBC suites: same 22 pre-existing failures before
and after, +13 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BlinkerLatchedTurn becomes a speed (km/h, 0 = off, step 5, max 80) instead
of a fixed 50 km/h toggle. At or below it the lever decides as before
(latched = turn, one-touch = lane change, nothing starts in the one-touch
detent); above it every lever input, latched included, is a lane change, so
the classifier's navigation-turn guess no longer turns a latched lever at
80 km/h into a turn.

LaneChangeLeverWait (default off): a one-touch press asks for one lane change
for 5 flashes from the press (0.8 s per flash, 2026-10-04 lever test), plus
LaneChangeDelay, which the FSM waits out first. While that change waits in
preLaneChange - blocked by whatever the FSM already checks (BSD, side
objects, lines) - DesireHelper asks for the lamp through
modelV2.meta.laneChangeBlinkerHold (@20) and ADRV_0x1ea BLINK_HOLD keeps it
flashing, so a car set to fewer one-touch flashes still flashes 5 times. If
the change has not started by then the request is dropped and the driver
blinker is ignored until the lamp goes dark, so neither the last flashes nor
our own hold can start it later. A new press restarts the count; a latched
lever keeps waiting with no timeout.

Both rules live in desire_lib/lever.py; DesireHelper only calls them.
Settings: Driving > Steering > Lane change. Docs ko/en lane-change.md.

Tests: desire stalk/lever 19 passed (6 new wait tests incl. a LaneChangeDelay
longer than the wait, 2 new speed tests), hyundai 176 passed (blink hold
before the change), settings schema 46 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…xt lane

On the 2026-10-05 drive (route 00000492) all four next-lane presses were
ignored: each came 0.1-1.1 s after the car centre crossed the line (model
lane lines relabel at +3.5-3.8 s), but laneChangeStarting lasts ~5 s
because the model keeps the lane-change probability up ~1.5 s past the
crossing, and presses in laneChangeStarting were all treated as re-lighting
the lamp of the current change.

- LaneCrossing (desire_lib/lever.py): the change-side line came within 1 m of
  the centre and then jumped 1.5 m further out - the same rule as the
  turn-signal release. A press after it, in laneChangeStarting or
  laneChangeFinishing, queues the next change; a press before it is still
  ignored, so it never crosses two lanes at once. Without lane lines the old
  finishing-phase rule (0.3 s into finishing) remains.
- The lamp of a press made mid-change stopped ~2.5 s after it on that drive,
  i.e. about when the next change became due. A queued request now holds the
  lamp (modelV2.meta.laneChangeBlinkerHold, which hyundaicanfd now prefers
  over the current change's 60 % release) until the queued change starts;
  blocked in preLaneChange it is dropped after the same 5 flashes as a
  one-touch press, or when the lamp side changes.

Open-loop replay of route 00000492 with this code: 4/4 next-lane presses
queued (was 0/4), first-change presses unchanged.

Tests: desire 24 passed (crossing before/after, late press, lamp held until
the queued start, dropped after 5 flashes), hyundai 177 passed (request
overrides the release), settings schema 46 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With BlinkerLatchedTurn, a latched lever at or below the set speed makes a
turn desire. Show that on the cluster so the driver knows the lever was
taken as a turn: ADRV_0x1ea AUTOLANECHANGE_MSG = 1 ("Check surrounding
conditions"; the cluster has no turn message) while the turn desire is on.

DesireHelper sets lever_turn only when the lever decided the turn (not a
classifier or navigation turn, not a lane change) and the turn desire is
active; modeld publishes it as modelV2.meta.leverTurn (@21); hyundaicanfd
writes the message after the lane graphics. No new setting: it follows
BlinkerLatchedTurn.

Tests: desire 25 passed (flag only for a lever-made turn, cleared with the
turn), hyundai 178 passed (message only when flagged, older schema).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADRV_0x1ea AUTOLANECHANGE_MSG did not show on the cluster: on the
2026-10-05 drive (route 00000494) all three latched-lever turns sent it as 1
(with HDA_MODE2 1, LANE_CHANGING 1/2) and nothing appeared. The stock ADRV
only sends it next to lane-change-assist state changes (HDA_MODE2 4 or 0),
so the cluster likely shows it only in that context.

Use ADRV_0x161 ALERTS_3 = 18 (CHECK_SURROUNDINGS) instead, the alert slot the
cluster already shows for carrot, and only when no other ALERTS_3 is up. No
sound is added. Same trigger: modelV2.meta.leverTurn.

Tests: hyundai 178 passed, settings schema 46 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add VehicleNaviDecelCancelEarlyWarning ([experimental], default off, only
with the deceleration cancel request on). When on, the far end of every
warning-lead window (saturated and precise previews, and the mobile-zone
start window) grows to 1.35 x the 6.1 m/km/h lead; the near end and the
default behaviour are unchanged.

Field logs 2026-10-05 (another user, Gangwon, 60 km/h): a box camera, a
fixed camera and a mobile zone all started their warning 462-466 m before
the camera (7.6-7.7 m/km/h vs 6.1), so none matched and all three were
treated as an undetected kind (no cancel). Replaying those logs: off ->
unknown x3, on -> BOX, FIXED, ZONE. Replaying our 37 drives (209 warnings):
no warning changes class with the option on.

Risk, stated in the setting: a nearby mobile-zone preview can attach to
the wrong warning in the wider window; with the mobile-zone cancel on or
AutoNaviSpeedCtrlMode <= 2 (zones ignored) a real camera's deceleration
may then be skipped. Users are told to verify on the road.

Tests: 170 passed (2 new: early box match on/off; zone covered, late
start still unmatched); settings schema 104 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
carrot-wip added CarState @95-@99 (leftBlindspotOem, rightBlindspotOem,
leftBlindspotOnnx, rightBlindspotOnnx, blindspotSplitSourcesValid). This
branch already used @95-@97 for the lever fields, which git merges without
a conflict but cereal refuses to load. Move the lever fields to the next free
ordinals:

  leftBlinkerStalkCount  @95 -> @100
  rightBlinkerStalkCount @96 -> @101
  blinkerLever           @97 -> @102

Logs recorded before this commit keep the old ordinals for these three fields.

Verified: merged log.capnp/car.capnp load in pycapnp with both field sets
writable; hyundai speedcam/section/blinker tests 180 passed; settings schema
and steering touch 104 passed; desire_helper stalk 25 passed; params_keys
has no duplicates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No capnp changes upstream. Shared files auto-merged: hyundaicanfd.py (upstream
StopReq JerkUpper limit and GV70 popup suppression touch other messages than
this branch's ADRV_0x1ea lever-turn message), carrot_settings.json and docs.

Verified: hyundai tests incl. upstream stopping/cluster popup 370 passed,
settings schema and steering touch 104 passed, desire_helper stalk 25 passed,
settings JSON parses, params_keys has no duplicates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… lane

EnableRadarTracks 3 adopts the nearest OEM SCC object whenever front-radar/vision
matching fails, with its lateral position overwritten to 0. On our car that turned
an adjacent-lane car into leadOne and braked hard twice:
  2026-09-24 00000459 seg62: adjacent queue, 31 -> 0.9 km/h, aTarget -3.11
  2026-10-06 00000499 seg13: car left behind by a right lane change, 54 -> 5 km/h, -3.14
In both, the front and corner tracks of that same body (range/speed match through
_scc_physical_support) were 2.3-3.8 m beside the path.

Before adopting the SCC body, find its front and corner tracks. Refuse it only when
they put it beside the path: both path offset and raw lateral >= 2.0 m (close-range
radar read 1.3-2.0 m for a car straight ahead), at least one >= 2.5 m, and front and
corner agree on the body (or both >= 2.5 m). Keep SCC when vision sees a lead at the
same range near the path, when no track matches (SCC may be the only sensor on it),
and during a lane change for a body below 18 km/h (a stopped car can still be in the
lane being left). A refusal holds 1 s through support dropouts. Modes other than 3,
leadTwo SCC backup and the central vision fallback are unchanged.

Validation (33 routes, 59,388 forced-SCC frames): 7.6 % refused, none with same-range
in-lane vision; video-checked refusals were adjacent or parked cars. Replaying the
modified controller on the logs: forced-SCC leadOne frames 129 -> 8 (09-24) and
193 -> 12 (10-06); unchanged for a stopped truck in the lane being left (51), a stopped
car while changing lanes (79) and an in-lane car with 1.6 m radar lateral (11).
Tests: new test_radar_forced_scc_gate 9 passed (3 fail with the gate disabled);
radar suite 701 with the same 4 pyray-only failures as before; settings schema 104.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
happymaj11r pushed a commit that referenced this pull request Oct 13, 2026
Adapt PRs #541, #542 and #543; select only the existing cluster turn indicator from #547. Align cache paths, avoid external symlink cleanup, preserve inactive steering display, and add regression checks.

Docs-Not-Needed: no settings change; startup repair and the existing cluster indicator are covered in the technical review.

Co-authored-by: 봉정령 <myer.oh@gmail.com>

Co-authored-by: Joongyu <268551435+joongyu01@users.noreply.github.com>
(cherry picked from commit 62fa1ac)
joongyu01 and others added 27 commits October 14, 2026 00:55
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py
Reverts 71162d3 and 2bec4fd at the user's request (the carrot-wip review of
PR #547 deferred them for the extra 100 Hz tracker, schema and sound; the
cluster turn indication stays). Drops AudibleAlert turnModel @38 / turnModelEnd
@39, EventName turnModel @130 / turnModelEnd @131, turn_prompt.py and both clips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Below BlinkerLatchedTurn a plain latch was a turn, so latching the lever to hold
the signal for a further lane change turned the car instead. A turn now needs a
deliberate gesture: one-touch, the lamp lit -> dark -> lit (~0.8 s), then the
lever latched (LeverForcedTurn); a plain latch or a one-touch is a lane change.
The speed limit still follows BlinkerLatchedTurn; above it every lever input is
a lane change.

carState.leftBlinker/rightBlinker hold the lamp for 0.5 s and stay on through a
whole flash, so the raw lamp goes out as carState.blinkerLamp @103 (0x413
*_LAMP_ALT; 2026-10-11 log: 0.4 s lit / 0.4 s dark). A latch passes the
one-touch detent for ~0.1 s and never collects two lightings.

Replay of 442 segments (10-09, 10-11) with the gesture alone: 5 activations,
all latches during consecutive lane changes, 4 of them above 50 km/h, which the
speed limit now keeps lane changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
carrot-wip adopted PR 547 at 67de82c as f513c8f. The three conflicts
(forced-SCC hold, its test, stalk test setup) take the owner's version,
which adds same-target continuity to the one-second refusal hold. The
merged tree equals carrot-wip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Back to 6491bee to check whether the screen got too bright after this
update. Revert this revert before merging carrot-wip again, or the
reverted changes will not come back.

This reverts commit ea286a0, reversing changes made to 6491bee.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Back to the latest carrot-wip at the user's request. This reverts f6b8945.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lead box and path are projections of radarState/model positions, so
they follow the shown camera through the wide calibration transform; no
second detection runs. Previously C4 showed wide only in Experimental Mode
below 5 m/s, which this car never uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot archive/carrot-wip-dev-20261016

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and backlight

Gates Device wakefulness, so the panel stays off onroad and offroad until the
setting is turned back on (re-read every 5 s, e.g. from Carrot Web). An unset
key reads the default and keeps the screen on. mici only; C3/C3X are unaffected.
Sounds and controls keep running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d alerts keep it on

Alerts include a stale selfdriveState, which the alert renderer also draws as critical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne slots

Test setting (0 off, 1-15) to find whether the cluster has dashed-line codes beyond
the known 0 gray / 1 hidden / 2 white / 4 orange / 6 green. Display only; cleared
on manager start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…P is on

HDP no longer forces DISTANCE to 4; the cluster keeps its cyan HDP colors with the
actual bars. A gap change under HDP shows the stock ALERTS_3 DISTANCE_N popup for 2 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ged standstill

controlsd pauses steering at standstill, which turned LFA/LKA/LCA icons off at every
stop. Display only; steering itself is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ClusterLaneTest on the Ioniq 5 PE showed 0x161 LANELINE 8 = white dashed and 10 =
green dashed (no orange dashed; 14 shows nothing). Per side, a dashed or DLM inner
dashed camera line turns white/green into 8/10; orange warnings, solid lines and
missing lane info are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…not see

controlsd hard-codes left/rightLaneVisible, so the cluster always drew a lane. Per
side, a model laneLineProbs below 0.08 for 1 s hides the line until it reaches 0.12
(night rlogs: parking lots 0.01-0.04, city stops 0.13-0.22, highway ~1.0). 14 is
undocumented on purpose: a cluster update that changes it will be visible.
ClusterLaneTest docs list the codes found on the car (8 white dashed, 10 green
dashed, 14 nothing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It found the dashed-line codes (8 white, 10 green, 14 nothing), which the cluster code
now uses directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… see the lane

Orange (do-not-cross line or blind-spot warning) is exempt from the unseen-lane 14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…display test

- Stock MAIN is pressed whenever it is off while driving, engaged or not: the
  cluster's side objects only arrive from stock ADAS with MAIN on (10-11 rlog: none
  for 66 s until the first engagement). openpilot's MAIN still follows the driver's
  button and manual-off latch.
- The lead car (DISTANCE_LEAD) and target are shown whenever a lead is visible.
- ClusterLaneTest = field x 10000 + value overrides 0x162 SPEEDLIMIT, SPEEDLIMIT_FLASH,
  SIGNS, SPEEDLIMIT_WEATHER, COUNTRY, 0x161 SLA_ICON, or 0x162 bit groups the DBC does
  not name (checksum recomputed). Test only, cleared on manager start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… m/s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lds (12-26)

HDA/LFA icons, set-speed colors, car circle, gap drawing (DISTANCE, CAR, SPACING,
LEAD), ALERTS_4/5 texts, background, lane highlight/zoom and center line. Applied
last, right before packing. Test only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Steps the field's value every 0.5 s (bit groups one bit at a time) and shows the
step in the cluster set-speed number, so a phone video of the cluster labels itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aneTestValue

Carrot Web steps settings by at most 100 per press, so field x 10000 + value could not
reach the HDP fields. ClusterLaneTestValue -1 is the 1 s sweep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@joongyu01

Copy link
Copy Markdown
Collaborator Author

Closing: the content was adopted into carrot-wip (f513c8f). The newer cluster/C4 changes are split into #553–#557.

@joongyu01 joongyu01 closed this Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant