diff --git a/docs/user/en/settings.md b/docs/user/en/settings.md
index 457b961e94a..bdf14a84585 100644
--- a/docs/user/en/settings.md
+++ b/docs/user/en/settings.md
@@ -256,7 +256,7 @@ These settings describe the car, harness, and device hardware configuration. Do
| Group | Parameters | Purpose |
|---|---|---|
| Hyundai/Kia | `HyundaiCameraSCC`, `IsLdwsCar`, `HapticFeedbackWhenSpeedCamera` | SCC connection, LDWS behavior, and speed-event haptics |
-| CAN FD/HDA | `CanfdHDA2`, `HyundaiCanfdClusterDirectTx`, `CanfdDebug`, `HDPuse` | HDA2 selection, cluster direct send, CAN FD diagnostics, and HDP |
+| CAN FD/HDA | `CanfdHDA2`, `HyundaiCanfdClusterDirectTx`, `CanfdDebug`, `HDPuse`, `ClusterLaneTest`, `ClusterLaneTestValue` | HDA2 selection, cluster direct send, CAN FD diagnostics, HDP, and a cluster display test |
| Radar | `EnableRadarTracks`, `RadarTrackFlip`, `EnableCornerRadar`, `CarrotRadarMode`, `CarrotRadarCutInSensitivity` | SCC radar, front-track orientation, corner radar, and Carrot Radar processing and cut-in sensitivity |
| Driver monitoring | `DriverMonitoringEnabled` (search only), `DriverMonitoringMode`, `CarrotVisionEnabled`, `MuteDoor`, `MuteSeatbelt` | Driver monitoring and selected vehicle alerts |
| Vehicle assistance | `MaxAngleFrames`, `SpeedFromPCM` | Steering-angle frames and stock-SCC speed control |
@@ -296,7 +296,7 @@ In modes `EnableRadarTracks=1`–`3`, a confirmed departing front lead can recei
## Display
-Display contains 34 settings. External-HUD settings control the layout and output of separate display hardware.
+Display contains 35 settings. External-HUD settings control the layout and output of separate display hardware.
`ClusterHud` (External HUD Display) switches a **direct USB HUD or a registered ESP32-S3 Wi-Fi HUD** on or off. A HUD connected to Jetson starts automatically even when this value is `0`, and normally turns its display off with ignition. Use the existing always-on `ClusterHudDebug` modes to keep it visible offroad. Brightness and layout settings also apply to the Jetson HUD.
@@ -313,7 +313,7 @@ With `CarrotVisionEnabled` on, the external HUD and web camera view can be used
|---|---|---|
| Information | `ShowDebugUI`, `ShowTpms`, `ShowDateTime`, `ShowPathEnd`, `ShowDeviceState`, `ShowLaneInfo`, `ShowRadarInfo`, `ShowRouteInfo`, `ShowPlotMode` | Debug, tire, time, lane, radar, and route information |
| Path | `ShowPathMode`, `ShowPathColor`, `ShowPathColorCruiseOff`, `ShowPathModeLane`, `ShowPathColorLane` | Path shape and color by driving state |
-| Brightness/on-road view | `ShowCustomBrightness`, `ShowModelView`, `ShowCameraWithCluster` | Brightness, camera/model composition, and the on-device camera while the external HUD is connected |
+| Brightness/on-road view | `ShowCustomBrightness`, `ScreenOn`, `ShowModelView`, `ShowCameraWithCluster` | Brightness, C4 screen on/off, camera/model composition, and the on-device camera while the external HUD is connected |
| External HUD | `ClusterHud`, `ClusterHudBrightness`, `ClusterHudOrientation`, and related `ClusterHud*` settings | Supported TURZX HUD layout, live brightness, screen rotation, camera, radar, and encoder options |
For RAV4 2022 with the ARS display stream, `ShowRadarInfo=3` on the standard device
diff --git a/docs/user/ko/settings.md b/docs/user/ko/settings.md
index a6f4a8ae4ea..e0a0ec67fbc 100644
--- a/docs/user/ko/settings.md
+++ b/docs/user/ko/settings.md
@@ -260,7 +260,7 @@ VW MEB(ID.4 포함)에도 수동 조향비와 학습 비율이 적용됩니다.
| 중분류 | 파라미터 | 용도 |
|---|---|---|
| 현대·기아 | `HyundaiCameraSCC`, `IsLdwsCar`, `HapticFeedbackWhenSpeedCamera` | SCC 연결 방식, LDWS 차량과 카메라 구간 햅틱 |
-| CANFD·HDA | `CanfdHDA2`, `HyundaiCanfdClusterDirectTx`, `CanfdDebug`, `HDPuse` | HDA2 차량, 계기판 직접 송신과 CAN FD 디버그·HDP 기능 |
+| CANFD·HDA | `CanfdHDA2`, `HyundaiCanfdClusterDirectTx`, `CanfdDebug`, `HDPuse`, `ClusterLaneTest`, `ClusterLaneTestValue` | HDA2 차량, 계기판 직접 송신과 CAN FD 디버그·HDP 기능, 계기판 표시 테스트 |
| 레이더 | `EnableRadarTracks`, `RadarTrackFlip`, `EnableCornerRadar`, `CarrotRadarMode`, `CarrotRadarCutInSensitivity` | SCC 레이더, 전방 트랙 좌우 보정, 코너 레이더와 당근레이더 처리·컷인 감도 |
| 운전자 모니터링 | `DriverMonitoringEnabled`(검색 전용), `DriverMonitoringMode`, `CarrotVisionEnabled`, `MuteDoor`, `MuteSeatbelt` | 운전자 모니터링과 일부 차량 경고음 처리 |
| 차량 보조 | `MaxAngleFrames`, `SpeedFromPCM` | 최대 조향각 관련 프레임과 순정 SCC 속도 제어 방식 |
@@ -295,7 +295,7 @@ Bosch MRRevo14F 수신을 지원하는 버전에서 Bosch MRRevo14F 후보 차
## 화면 표시
-화면 표시에는 34개 항목이 있습니다. 외부 HUD 항목은 별도 하드웨어의 화면 구성과 출력 방식을 조정합니다.
+화면 표시에는 35개 항목이 있습니다. 외부 HUD 항목은 별도 하드웨어의 화면 구성과 출력 방식을 조정합니다.
`ClusterHud`(외부 HUD 활성화)는 **본체 USB에 직접 연결한 HUD 또는 등록된 ESP32-S3 Wi-Fi HUD**를 켜고 끄는 설정입니다. Jetson에 연결한 HUD는 이 값이 `0`이어도 자동 실행되며, 기본적으로 시동 OFF 시 화면을 끕니다. 시동 OFF에도 표시하려면 기존 `ClusterHudDebug`의 항상 켜기를 사용합니다. 밝기·화면 구성 설정은 Jetson HUD에도 적용됩니다.
@@ -312,7 +312,7 @@ Jetson HUD에는 `jetSON` 아래에 Jetson 내부 센서의 최고 온도가 표
|---|---|---|
| 정보 표시 | `ShowDebugUI`, `ShowTpms`, `ShowDateTime`, `ShowPathEnd`, `ShowDeviceState`, `ShowLaneInfo`, `ShowRadarInfo`, `ShowRouteInfo`, `ShowPlotMode` | 주행 화면의 디버그, 타이어, 시간, 차선, 레이더와 경로 정보 |
| 경로 표시 | `ShowPathMode`, `ShowPathColor`, `ShowPathColorCruiseOff`, `ShowPathModeLane`, `ShowPathColorLane` | 레인리스·레인모드·크루즈 OFF 상태의 경로 모양과 색상 |
-| 밝기·주행화면 | `ShowCustomBrightness`, `ShowModelView`, `ShowCameraWithCluster` | 주행 중 밝기, 카메라·모델 표시 조합과 외부 HUD 연결 중 본체 카메라 표시 |
+| 밝기·주행화면 | `ShowCustomBrightness`, `ScreenOn`, `ShowModelView`, `ShowCameraWithCluster` | 주행 중 밝기, C4 화면 켜기·끄기, 카메라·모델 표시 조합과 외부 HUD 연결 중 본체 카메라 표시 |
| 외부 HUD·기본 | `ClusterHud`, `ClusterHudBrightness`, `ClusterHudOrientation`, `ClusterHudMirror`, `ClusterHudTheme`, `ClusterNaviMapTheme`, `ClusterNaviMapType`, `ClusterNaviMapFps` | TURZX 외부 HUD, 밝기, 화면 회전, 미러링과 지도 테마 |
| 외부 HUD·화면·카메라 | `ClusterHudEncoder`, `ClusterHudScreenMode`, `ClusterHudPanelLayout`, `ClusterHudCameraViewMode` | 인코더와 화면·카메라·좌우 패널 구성 |
| 외부 HUD·레이더 표시 | `ClusterHudRadarInfo`, `ClusterHudRadarDisplay`, `ClusterHudRadarSourceColor` | 외부 HUD의 레이더 정보와 색상 |
diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py
index ae07d3d8b7b..a8e01cdaab9 100644
--- a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py
+++ b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py
@@ -864,6 +864,78 @@ def _hide_replaced_adas_service_warning(values):
values["FAULT_DAS"] = 0
+LANE_LINE_UNSEEN = 14 # draws nothing on the Ioniq 5 PE cluster; undocumented on purpose, so a cluster update that changes it is noticed
+# model laneLineProbs at low speed (10-18 rlogs, night): parking lots 0.01~0.04, city stops 0.13~0.22, highway ~1.0
+LANE_HIDE_PROB, LANE_SHOW_PROB, LANE_HIDE_FRAMES = 0.08, 0.12, 100 # hide after 1 s below 0.08, show again from 0.12
+
+
+def _lane_unseen(state, side, prob, frame):
+ hidden, low_since = state.get(side, (False, None))
+ if prob >= LANE_SHOW_PROB:
+ hidden, low_since = False, None
+ elif prob < LANE_HIDE_PROB:
+ low_since = frame if low_since is None else low_since
+ hidden = hidden or frame - low_since >= LANE_HIDE_FRAMES
+ else:
+ low_since = None # in between: keep the current state
+ state[side] = (hidden, low_since)
+ return hidden
+
+
+# ClusterLaneTest = field, ClusterLaneTestValue = raw value (test only): DBC fields, or 0x162 bits the DBC does not name
+CLUSTER_TEST_FIELDS = {1: "SPEEDLIMIT", 2: "SPEEDLIMIT_FLASH", 3: "SIGNS", 4: "SPEEDLIMIT_WEATHER", 5: "COUNTRY"} # 0x162
+CLUSTER_TEST_FIELDS_161 = {6: "SLA_ICON", 12: "HDA_ICON", 13: "LFA_ICON", 14: "SETSPEED", 15: "SETSPEED_HUD", 16: "CAR_CIRCLE",
+ 17: "DISTANCE", 18: "DISTANCE_CAR", 19: "DISTANCE_SPACING", 20: "DISTANCE_LEAD",
+ 21: "ALERTS_4", 22: "ALERTS_5", 23: "BACKGROUND", 24: "LANE_HIGHLIGHT", 25: "LANE_ZOOM", 26: "CENTERLINE"}
+CLUSTER_TEST_BITS = {7: tuple(range(53, 64)), 8: tuple(range(159, 163)), 9: tuple(range(183, 192)), 10: tuple(range(252, 256)),
+ 11: (87, 111, 135, 212)}
+
+
+CLUSTER_TEST_SWEEP = -1 # ClusterLaneTestValue -1: step the field every 1 s and show the step in the set speed number
+
+
+def _cluster_test():
+ params = Params()
+ field = params.get_int("ClusterLaneTest")
+ return (field, params.get_int("ClusterLaneTestValue")) if field > 0 else (0, 0)
+
+
+def _cluster_sweep(packer, field, frame):
+ # returns (value to send, number to show); bit groups step one bit at a time and show the bit number
+ state = create_ccnc_messages.__dict__.setdefault('_sweep', {})
+ if state.get('field') != field:
+ state.update(field=field, start=frame)
+ step = (frame - state['start']) // 100
+ if field in CLUSTER_TEST_BITS:
+ bits = CLUSTER_TEST_BITS[field]
+ i = step % (len(bits) + 1)
+ return (0, 0) if i == 0 else (1 << (i - 1), bits[i - 1])
+ if field in CLUSTER_TEST_FIELDS:
+ sig = packer.dbc.name_to_msg['CCNC_0x162'].sigs[CLUSTER_TEST_FIELDS[field]]
+ elif field in CLUSTER_TEST_FIELDS_161:
+ sig = packer.dbc.name_to_msg['ADRV_0x161'].sigs[CLUSTER_TEST_FIELDS_161[field]]
+ else:
+ return 0, None
+ value = step % (1 << sig.size)
+ return value, value
+
+
+def _set_raw_bits(msg, bits, value):
+ addr, dat, bus = msg
+ d = bytearray(dat)
+ for i, b in enumerate(bits):
+ d[b // 8] = (d[b // 8] & ~(1 << (b % 8))) | (((value >> i) & 1) << (b % 8))
+ d[0:2] = hkg_can_fd_checksum(addr, None, d).to_bytes(2, 'little')
+ return addr, bytes(d), bus
+
+
+def _dashed_lane_line(color, lane_line):
+ # 0x161 dashed codes seen on the Ioniq 5 PE cluster: 8 white, 10 green (no orange dashed)
+ if lane_line // 10 > 0 and lane_line % 10 in (0, 5): # camera: dashed / DLM inner dashed (color 0 = no lane info)
+ return {2: 8, 6: 10}.get(color, color)
+ return color
+
+
def _select_cluster_background(cruise_enabled, lat_active, paddle_pressed, paddle_mode, turning=False):
# Reuse the existing flashing-red background to identify an active model turn.
if (lat_active and turning) or (paddle_mode > 0 and paddle_pressed):
@@ -992,10 +1064,14 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
if HDA_LFA_SymSta == 0 and 0 < frame % 200 < 12:
values["LFA_BTN"] = 1
+ # The cluster's side objects, lead car and target come from stock SCC MAIN; keep it on from ignition.
+ # openpilot's own MAIN still follows the driver's button (manual_main_off_latched).
+ if not CS.MainMode_ACC and not interlock_active and 10 < frame % 200 <= 16:
+ values["ADAPTIVE_CRUISE_MAIN_BTN"] = 1
+
if CC.enabled and not interlock_active:
if not CS.MainMode_ACC:
- if 10 < frame % 200 <= 16 and CS.out.vEgo > 3.:
- values["ADAPTIVE_CRUISE_MAIN_BTN"] = 1
+ pass # pressed above
elif CS.ACCMode in [0, 4]:
if 10 < frame % 200 <= 16 and CS.out.vEgo > 3.:
values["CRUISE_BUTTONS"] = 2
@@ -1012,11 +1088,19 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
# --- 0x161/0x200/0x1ea/0x162 (frame%5) ---
if frame % 5 == 0:
lat_active = CC.latActive
+ test_field, test_value = _cluster_test()
+ sweep_display = None
+ if test_value == CLUSTER_TEST_SWEEP:
+ test_value, sweep_display = _cluster_sweep(packer, test_field, frame)
+ else:
+ create_ccnc_messages.__dict__.pop('_sweep', None)
if CS.adrv_0x161 is not None:
main_enabled = CS.out.cruiseState.available
cruise_enabled = CC.enabled
lat_enabled = CS.out.latEnabled
+ # steering pauses at standstill; keep the cluster's steering/lane-change icons as if it were active
+ lat_icon = lat_active or (cruise_enabled and lat_enabled and CS.out.standstill)
nav_active = hud_control.activeCarrot > 1
vehicle_navi_available = CS.out.vehicleNaviAvailable
nav_icon_available = nav_active or vehicle_navi_available
@@ -1038,12 +1122,12 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_KPH if CS.is_metric else CV.MS_TO_MPH)
values["vSetDis"] = int(set_speed_in_units + 0.5)
- values["DISTANCE"] = 4 if hdp_active else hud_control.leadDistanceBars
- values["DISTANCE_LEAD"] = 2 if cruise_enabled and lead_visible else 1 if main_enabled and lead_visible else 0
+ values["DISTANCE"] = hud_control.leadDistanceBars # real gap bars under the HDP colors too
+ values["DISTANCE_LEAD"] = (2 if cruise_enabled else 1) if lead_visible else 0
values["DISTANCE_CAR"] = 3 if hdp_active else 2 if cruise_enabled else 1 if main_enabled else 0
values["DISTANCE_SPACING"] = 5 if hdp_active else 1 if cruise_enabled else 0
- values["TARGET"] = 1 if lead_visible and cruise_enabled else 0
+ values["TARGET"] = (1 if cruise_enabled else 3) if lead_visible else 0 # blue when engaged, white otherwise
values["TARGET_DISTANCE"] = lead_distance
values["BACKGROUND"] = _select_cluster_background(
@@ -1054,8 +1138,8 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
values["NAV_ICON"] = 2 if nav_icon_available and cruise_enabled else 1 if main_enabled and nav_icon_available else 0
values["HDA_ICON"] = 5 if hdp_active else 2 if cruise_enabled else 1 if main_enabled else 0
- values["LFA_ICON"] = 5 if hdp_active else 2 if lat_active else 1 if lat_enabled else 0
- values["LKA_ICON"] = 4 if lat_active else 3 if lat_enabled else 0
+ values["LFA_ICON"] = 5 if hdp_active else 2 if lat_icon else 1 if lat_enabled else 0
+ values["LKA_ICON"] = 4 if lat_icon else 3 if lat_enabled else 0
values["FCA_ALT_ICON"] = 0
if values["ALERTS_2"] in [1, 2, 5, 6, 10, 21, 22]:
@@ -1071,6 +1155,14 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
values["ALERTS_3"] = 0
values["SOUNDS_3"] = 0
+ # carrot: with HDP on, a gap change shows the stock "distance N" popup (ALERTS_3 7~10) for 2 s
+ gap_bars = hud_control.leadDistanceBars
+ if hdp_active and gap_bars != getattr(create_ccnc_messages, '_gap_bars', gap_bars):
+ create_ccnc_messages._gap_popup_until = frame + 200
+ create_ccnc_messages._gap_bars = gap_bars
+ if frame < getattr(create_ccnc_messages, '_gap_popup_until', 0) and values["ALERTS_3"] == 0 and 1 <= gap_bars <= 4:
+ values["ALERTS_3"] = 6 + gap_bars
+
if values["ALERTS_5"] in [1, 2, 3, 4, 5]:
values["ALERTS_5"] = 0
@@ -1085,6 +1177,10 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
values["LANELINE_CURVATURE_DIRECTION"] = 1 if curvature < 0 and lat_active else 0
trailer_lane_change_blocked = CS.trailer_connected
+ lane_probs = md.laneLineProbs if md is not None and len(md.laneLineProbs) >= 3 else None
+ unseen = create_ccnc_messages.__dict__.setdefault('_lane_unseen', {})
+ left_seen = hud_control.leftLaneVisible and not (lane_probs is not None and _lane_unseen(unseen, 'L', lane_probs[1], frame))
+ right_seen = hud_control.rightLaneVisible and not (lane_probs is not None and _lane_unseen(unseen, 'R', lane_probs[2], frame))
if trailer_lane_change_blocked:
values["LANELINE_LEFT"] = 2 if hud_control.leftLaneVisible else 0
values["LANELINE_RIGHT"] = 2 if hud_control.rightLaneVisible else 0
@@ -1098,7 +1194,7 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
if hud_control.leftLaneDepart:
values["LANELINE_LEFT"] = 4 if (frame // 50) % 2 == 0 else 1
else:
- values["LANELINE_LEFT"] = lane_color if hud_control.leftLaneVisible else 0
+ values["LANELINE_LEFT"] = _dashed_lane_line(lane_color, CS.out.leftLaneLine) if left_seen or lane_color == 4 else LANE_LINE_UNSEEN # orange stays
lane_color = 6 if md is not None and md.meta.laneChangeAvailableRight else 2
if lane_line_check >= 1:
@@ -1109,22 +1205,26 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
if hud_control.rightLaneDepart:
values["LANELINE_RIGHT"] = 4 if (frame // 50) % 2 == 0 else 1
else:
- values["LANELINE_RIGHT"] = lane_color if hud_control.rightLaneVisible else 0
+ values["LANELINE_RIGHT"] = _dashed_lane_line(lane_color, CS.out.rightLaneLine) if right_seen or lane_color == 4 else LANE_LINE_UNSEEN
values["LCA_LEFT_ARROW"] = 2 if CS.out.leftBlinker else 0
values["LCA_RIGHT_ARROW"] = 2 if CS.out.rightBlinker else 0
if trailer_lane_change_blocked:
- values["LCA_LEFT_ICON"] = 1 if lat_active else 0
- values["LCA_RIGHT_ICON"] = 1 if lat_active else 0
+ values["LCA_LEFT_ICON"] = 1 if lat_icon else 0
+ values["LCA_RIGHT_ICON"] = 1 if lat_icon else 0
else:
- values["LCA_LEFT_ICON"] = (1 if CS.out.leftBlindspot else 2) if lat_active else 0
- values["LCA_RIGHT_ICON"] = (1 if CS.out.rightBlindspot else 2) if lat_active else 0
+ values["LCA_LEFT_ICON"] = (1 if CS.out.leftBlindspot else 2) if lat_icon else 0
+ values["LCA_RIGHT_ICON"] = (1 if CS.out.rightBlindspot else 2) if lat_icon else 0
values["LANE_LEFT"] = 0 if trailer_lane_change_blocked else 1 if desire in (1, 3) else 0
values["LANE_RIGHT"] = 0 if trailer_lane_change_blocked else 1 if desire in (2, 4) else 0
_apply_driver_monitoring_alert(values, hud_control, CS.adrv_0x161)
+ if test_field in CLUSTER_TEST_FIELDS_161:
+ values[CLUSTER_TEST_FIELDS_161[test_field]] = test_value
+ if sweep_display is not None:
+ values["vSetDis"] = min(sweep_display, 255)
ret.append(packer.make_can_msg("ADRV_0x161", CAN.ECAN, values, rx_counter = rx_counter))
if CS.adrv_0x200 is not None:
@@ -1165,7 +1265,10 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control,
values["FAULT_LSS"] = 0
values["FAULT_DAS"] = 0
- ret.append(packer.make_can_msg("CCNC_0x162", CAN.ECAN, values, rx_counter=rx_counter))
+ if test_field in CLUSTER_TEST_FIELDS:
+ values[CLUSTER_TEST_FIELDS[test_field]] = test_value
+ msg = packer.make_can_msg("CCNC_0x162", CAN.ECAN, values, rx_counter=rx_counter)
+ ret.append(_set_raw_bits(msg, CLUSTER_TEST_BITS[test_field], test_value) if test_field in CLUSTER_TEST_BITS else msg)
# --- NEW_MSG_4B9 (corner radar keep-alive?) ---
if enable_corner_radar > 0:
diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_ccnc_cluster.py b/opendbc_repo/opendbc/car/hyundai/tests/test_ccnc_cluster.py
index 6696d7f49fe..589746ac711 100644
--- a/opendbc_repo/opendbc/car/hyundai/tests/test_ccnc_cluster.py
+++ b/opendbc_repo/opendbc/car/hyundai/tests/test_ccnc_cluster.py
@@ -136,3 +136,155 @@ def test_cluster_objects_restore_corner_state_without_blinking_or_distance_clamp
else:
assert decoded["LANELINE_CURVATURE"] == 2
assert source == original
+
+
+def test_hdp_gap_bars_and_popup(monkeypatch):
+ from openpilot.cereal import log
+ monkeypatch.setattr(hyundaicanfd, 'Params', lambda: SimpleNamespace(
+ get_int=lambda key: 0, get=lambda key: '2' if key == 'HDPuse' else '0'))
+ for attr in ('_gap_bars', '_gap_popup_until'):
+ monkeypatch.delattr(hyundaicanfd.create_ccnc_messages, attr, raising=False)
+ packer = CANPacker('hyundai_canfd_generated')
+ definition = packer.dbc.name_to_msg['ADRV_0x161']
+ state = SimpleNamespace(
+ adrv_0x161={key: 0 for key in definition.sigs}, out=structs.CarState(), modelV2=log.ModelDataV2.new_message(),
+ lfahda_cluster=None, cruise_buttons_msg=None, adrv_0x200=None, adrv_0x1ea=None, ccnc_0x162=None,
+ is_metric=True, paddle_button_prev=0, softHoldActive=0, trailer_connected=False,
+ )
+ control = structs.CarControl(latActive=True, enabled=True)
+
+ def send(frame, bars):
+ control.hudControl.leadDistanceBars = bars
+ msg = hyundaicanfd.create_ccnc_messages(
+ SimpleNamespace(flags=HyundaiFlags.CAMERA_SCC), packer, SimpleNamespace(ECAN=0, CAM=2), frame, control, state,
+ control.hudControl, 0, False, False, 0, False, 0, 0)[0]
+ return {key: get_raw_value(msg[1], sig) for key, sig in definition.sigs.items()}
+
+ first = send(5, 2)
+ assert (first['DISTANCE'], first['ALERTS_3'], first['HDA_ICON']) == (2, 0, 5) # HDP colors keep the real bars
+ assert send(10, 3)['ALERTS_3'] == 9 # DISTANCE_3 popup after a change
+ assert send(205, 3)['ALERTS_3'] == 9
+ assert send(215, 3)['ALERTS_3'] == 0 # gone after 2 s
+
+
+@pytest.mark.parametrize(('standstill', 'lka', 'lca'), ((True, 4, 2), (False, 3, 0)))
+def test_engaged_standstill_keeps_steering_and_lane_change_icons(monkeypatch, standstill, lka, lca):
+ from openpilot.cereal import log
+ monkeypatch.setattr(hyundaicanfd, 'Params', lambda: SimpleNamespace(get_int=lambda key: 0, get=lambda key: '0'))
+ packer = CANPacker('hyundai_canfd_generated')
+ definition = packer.dbc.name_to_msg['ADRV_0x161']
+ out = structs.CarState(standstill=standstill, latEnabled=True)
+ state = SimpleNamespace(
+ adrv_0x161={key: 0 for key in definition.sigs}, out=out, modelV2=log.ModelDataV2.new_message(),
+ lfahda_cluster=None, cruise_buttons_msg=None, adrv_0x200=None, adrv_0x1ea=None, ccnc_0x162=None,
+ is_metric=True, paddle_button_prev=0, softHoldActive=0, trailer_connected=False,
+ )
+ control = structs.CarControl(latActive=False, enabled=True) # controlsd turns steering off at standstill
+ msg = hyundaicanfd.create_ccnc_messages(
+ SimpleNamespace(flags=HyundaiFlags.CAMERA_SCC), packer, SimpleNamespace(ECAN=0, CAM=2), 5, control, state,
+ control.hudControl, 0, False, False, 0, False, 0, 0)[0]
+ values = {key: get_raw_value(msg[1], sig) for key, sig in definition.sigs.items()}
+ assert (values['LKA_ICON'], values['LFA_ICON'] > 1, values['LCA_LEFT_ICON'], values['LCA_RIGHT_ICON']) == (lka, standstill, lca, lca)
+
+
+@pytest.mark.parametrize(('color', 'lane_line', 'expected'), (
+ (6, 10, 10), (2, 10, 8), (6, 15, 10), # white dashed / DLM inner dashed -> green or white dashed
+ (2, 11, 2), (4, 10, 4), (6, 0, 6), # solid line, orange warning, no lane info stay as they were
+))
+def test_dashed_lane_line_codes(color, lane_line, expected):
+ assert hyundaicanfd._dashed_lane_line(color, lane_line) == expected
+
+
+@pytest.mark.parametrize(('blindspot', 'left'), ((False, 14), (True, 4))) # orange (BSD) stays even when unseen
+def test_unseen_lane_lines_send_14(monkeypatch, blindspot, left):
+ from openpilot.cereal import log
+ monkeypatch.setattr(hyundaicanfd, 'Params', lambda: SimpleNamespace(get_int=lambda key: 0, get=lambda key: '0'))
+ packer = CANPacker('hyundai_canfd_generated')
+ definition = packer.dbc.name_to_msg['ADRV_0x161']
+ state = SimpleNamespace(
+ adrv_0x161={key: 0 for key in definition.sigs}, out=structs.CarState(leftBlindspot=blindspot), modelV2=log.ModelDataV2.new_message(),
+ lfahda_cluster=None, cruise_buttons_msg=None, adrv_0x200=None, adrv_0x1ea=None, ccnc_0x162=None,
+ is_metric=True, paddle_button_prev=0, softHoldActive=0, trailer_connected=False,
+ )
+ control = structs.CarControl(latActive=True, enabled=True)
+ control.hudControl.rightLaneVisible = True
+ msg = hyundaicanfd.create_ccnc_messages(
+ SimpleNamespace(flags=HyundaiFlags.CAMERA_SCC), packer, SimpleNamespace(ECAN=0, CAM=2), 5, control, state,
+ control.hudControl, 0, False, False, 0, False, 0, 0)[0]
+ values = {key: get_raw_value(msg[1], sig) for key, sig in definition.sigs.items()}
+ assert (values['LANELINE_LEFT'], values['LANELINE_RIGHT']) == (left, 2)
+
+
+def test_lane_unseen_hysteresis():
+ state = {}
+ assert not hyundaicanfd._lane_unseen(state, 'L', 0.02, 0)
+ assert not hyundaicanfd._lane_unseen(state, 'L', 0.02, 95) # parking lot, under 1 s
+ assert hyundaicanfd._lane_unseen(state, 'L', 0.02, 100) # hidden after 1 s
+ assert hyundaicanfd._lane_unseen(state, 'L', 0.10, 120) # between thresholds keeps it hidden
+ assert not hyundaicanfd._lane_unseen(state, 'L', 0.13, 125) # city stop level shows it again
+ assert not hyundaicanfd._lane_unseen(state, 'L', 0.05, 130)
+ assert not hyundaicanfd._lane_unseen(state, 'L', 0.10, 200) # a dip that does not last 1 s resets
+ assert not hyundaicanfd._lane_unseen(state, 'L', 0.05, 205)
+ assert not hyundaicanfd._lane_unseen(state, 'R', 0.02, 205) # sides are independent
+
+
+@pytest.mark.parametrize(('main_on', 'v_ego', 'pressed'), ((False, 5.0, 1), (True, 5.0, 0), (False, 0.0, 1)))
+def test_stock_main_kept_on_without_engagement(monkeypatch, main_on, v_ego, pressed):
+ from openpilot.cereal import log
+ monkeypatch.setattr(hyundaicanfd, 'Params', lambda: SimpleNamespace(get_int=lambda key: 0, get=lambda key: '0'))
+ packer = CANPacker('hyundai_canfd_generated')
+ buttons = packer.dbc.name_to_msg['CRUISE_BUTTONS']
+ state = SimpleNamespace(
+ adrv_0x161=None, out=structs.CarState(vEgo=v_ego), modelV2=log.ModelDataV2.new_message(), lfahda_cluster=None,
+ cruise_buttons_msg={key: 0 for key in buttons.sigs}, cruise_btns_msg_canfd='CRUISE_BUTTONS', MainMode_ACC=main_on,
+ ACCMode=0, scc_control=None, adrv_0x200=None, adrv_0x1ea=None, ccnc_0x162=None, is_metric=True,
+ paddle_button_prev=0, softHoldActive=0, trailer_connected=False,
+ )
+ control = structs.CarControl(enabled=False) # not engaged: stock MAIN is still kept on for the cluster
+ msgs = hyundaicanfd.create_ccnc_messages(
+ SimpleNamespace(flags=HyundaiFlags.CAMERA_SCC), packer, SimpleNamespace(ECAN=0, CAM=2), 12, control, state,
+ control.hudControl, 0, False, False, 0, False, 0, 0)
+ msg = next(m for m in msgs if m[0] == buttons.address)
+ assert get_raw_value(msg[1], buttons.sigs['ADAPTIVE_CRUISE_MAIN_BTN']) == pressed
+
+
+def test_cluster_test_raw_bits_keep_a_valid_checksum():
+ packer = CANPacker('hyundai_canfd_generated')
+ definition = packer.dbc.name_to_msg['CCNC_0x162']
+ msg = packer.make_can_msg('CCNC_0x162', 0, {'SPEEDLIMIT': 80})
+ addr, dat, _ = hyundaicanfd._set_raw_bits(msg, hyundaicanfd.CLUSTER_TEST_BITS[7], 0b101)
+ value = int.from_bytes(dat, 'little')
+ assert [(value >> b) & 1 for b in range(53, 64)] == [1, 0, 1] + [0] * 8
+ assert get_raw_value(dat, definition.sigs['SPEEDLIMIT']) == 80 # named fields untouched
+ assert get_raw_value(dat, definition.sigs['CHECKSUM']) == hyundaicanfd.hkg_can_fd_checksum(addr, None, bytearray(dat))
+
+
+@pytest.mark.parametrize(('slot', 'field', 'value'), ((21, 'ALERTS_4', 40), (17, 'DISTANCE', 2), (12, 'HDA_ICON', 7)))
+def test_cluster_test_overrides_0x161_fields_last(monkeypatch, slot, field, value):
+ from openpilot.cereal import log
+ monkeypatch.setattr(hyundaicanfd, 'Params', lambda: SimpleNamespace(
+ get_int=lambda key: {'ClusterLaneTest': slot, 'ClusterLaneTestValue': value}.get(key, 0),
+ get=lambda key: '2' if key == 'HDPuse' else '0'))
+ packer = CANPacker('hyundai_canfd_generated')
+ definition = packer.dbc.name_to_msg['ADRV_0x161']
+ state = SimpleNamespace(
+ adrv_0x161={key: 0 for key in definition.sigs}, out=structs.CarState(), modelV2=log.ModelDataV2.new_message(),
+ lfahda_cluster=None, cruise_buttons_msg=None, adrv_0x200=None, adrv_0x1ea=None, ccnc_0x162=None,
+ is_metric=True, paddle_button_prev=0, softHoldActive=0, trailer_connected=False,
+ )
+ control = structs.CarControl(latActive=True, enabled=True)
+ control.hudControl.leadDistanceBars = 3
+ msg = hyundaicanfd.create_ccnc_messages(
+ SimpleNamespace(flags=HyundaiFlags.CAMERA_SCC), packer, SimpleNamespace(ECAN=0, CAM=2), 5, control, state,
+ control.hudControl, 0, False, False, 0, False, 0, 0)[0]
+ assert get_raw_value(msg[1], definition.sigs[field]) == value
+
+
+def test_cluster_sweep_steps_every_second(monkeypatch):
+ monkeypatch.delattr(hyundaicanfd.create_ccnc_messages, '_sweep', raising=False)
+ packer = CANPacker('hyundai_canfd_generated')
+ assert [hyundaicanfd._cluster_sweep(packer, 3, f) for f in (1000, 1095, 1100, 1200)] == [(0, 0), (0, 0), (1, 1), (2, 2)]
+ assert hyundaicanfd._cluster_sweep(packer, 3, 1000 + 256 * 100) == (0, 0) # SIGNS is 8 bits: wraps after 255
+ assert hyundaicanfd._cluster_sweep(packer, 7, 2000) == (0, 0) # new field restarts at 0
+ assert hyundaicanfd._cluster_sweep(packer, 7, 2100) == (1, 53) # first unnamed bit, shown as its bit number
+ assert hyundaicanfd._cluster_sweep(packer, 7, 2000 + 11 * 100) == (1 << 10, 63)
diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h
index 6275f271c5c..cc1fb3a09a5 100644
--- a/openpilot/common/params_keys.h
+++ b/openpilot/common/params_keys.h
@@ -192,6 +192,7 @@ inline static std::unordered_map keys = {
{"TimezoneSource", {PERSISTENT, STRING, ""}}, // 타임존 출처 우선순위 추적 ("app" > "wifi" > "gps")
{"CarrotTireTrajectory", {PERSISTENT, INT, "0"}},
{"ShowCustomBrightness", {PERSISTENT, INT, "100"}},
+ {"ScreenOn", {PERSISTENT, BOOL, "1"}},
{"ShowModelView", {PERSISTENT, INT, "0"}},
{"ShowCameraWithCluster", {PERSISTENT, INT, "0"}},
{"ClusterHud", {PERSISTENT, INT, "0"}},
@@ -214,6 +215,8 @@ inline static std::unordered_map keys = {
{"ClusterHudRadarSourceColor", {PERSISTENT, INT, "0"}},
{"RecordRoadCam", {PERSISTENT, INT, "0"}},
{"HDPuse", {PERSISTENT, INT, "0"}},
+ {"ClusterLaneTest", {CLEAR_ON_MANAGER_START, INT, "0"}},
+ {"ClusterLaneTestValue", {CLEAR_ON_MANAGER_START, INT, "0"}},
{"AutoCruiseControl", {PERSISTENT, INT, "0"}},
{"SoftHoldOnCancel", {PERSISTENT, BOOL, "0"}},
diff --git a/openpilot/selfdrive/carrot/server/tests/test_settings_schema.py b/openpilot/selfdrive/carrot/server/tests/test_settings_schema.py
index 5ab512ff4e4..7caf8ec7978 100644
--- a/openpilot/selfdrive/carrot/server/tests/test_settings_schema.py
+++ b/openpilot/selfdrive/carrot/server/tests/test_settings_schema.py
@@ -484,6 +484,7 @@ def test_cluster_camera_preference_is_in_brightness_and_view(settings, params):
brightness = next(group for group in display["groups"] if group["id"] == "DISP_BRIGHT")
assert brightness["params"] == [
"ShowCustomBrightness",
+ "ScreenOn",
"ShowModelView",
"ShowCameraWithCluster",
]
diff --git a/openpilot/selfdrive/carrot_settings.json b/openpilot/selfdrive/carrot_settings.json
index 6d180854d7c..4934567c84f 100644
--- a/openpilot/selfdrive/carrot_settings.json
+++ b/openpilot/selfdrive/carrot_settings.json
@@ -418,7 +418,9 @@
"CanfdHDA2",
"HyundaiCanfdClusterDirectTx",
"CanfdDebug",
- "HDPuse"
+ "HDPuse",
+ "ClusterLaneTest",
+ "ClusterLaneTestValue"
]
},
{
@@ -510,6 +512,7 @@
"zh": "亮度与视图",
"params": [
"ShowCustomBrightness",
+ "ScreenOn",
"ShowModelView",
"ShowCameraWithCluster"
]
@@ -1856,6 +1859,38 @@
"default": 0,
"unit": 1
},
+ {
+ "group": "시작",
+ "name": "ClusterLaneTest",
+ "title": "계기판 표시 테스트: 칸(CCNC)",
+ "descr": "테스트용. 0: 끔. 바꿀 칸 번호를 고르고, 보낼 값은 「계기판 표시 테스트: 값」에서 정합니다. 재부팅하면 0.\n1 속도제한 숫자 · 2 제한표시 색/깜빡임 · 3 표지판 종류 · 4 날씨 보조 · 5 국가 · 6 SLA 아이콘 · 7~11 0x162 이름 없는 비트(7: 53~63, 8: 159~162, 9: 183~191, 10: 252~255, 11: 87/111/135/212) · 12 HDA아이콘 · 13 LFA아이콘 · 14 설정속도 색 · 15 설정속도HUD · 16 차 원 · 17 차간 DISTANCE · 18 차간 차 · 19 차간 간격 · 20 앞차 · 21 ALERTS_4 문구 · 22 ALERTS_5 문구 · 23 배경 · 24 차로강조 · 25 차로줌 · 26 중앙선\n표시만 바뀌고 제어 영향 없음.",
+ "egroup": "START",
+ "etitle": "Cluster Display Test: Field (CCNC)",
+ "edescr": "Test only. 0: off. Pick the field; the value comes from \"Cluster Display Test: Value\". Resets to 0 on reboot.\n1 speed limit · 2 limit color/flash · 3 sign type · 4 weather · 5 country · 6 SLA icon · 7-11 unnamed 0x162 bits (7: 53-63, 8: 159-162, 9: 183-191, 10: 252-255, 11: 87/111/135/212) · 12 HDA icon · 13 LFA icon · 14 set speed color · 15 set speed HUD · 16 car circle · 17 gap DISTANCE · 18 gap car · 19 gap spacing · 20 lead · 21 ALERTS_4 text · 22 ALERTS_5 text · 23 background · 24 lane highlight · 25 lane zoom · 26 center line\nDisplay only; no effect on control.",
+ "cgroup": "启动设置",
+ "ctitle": "仪表显示测试: 字段 (CCNC)",
+ "cdescr": "仅用于测试。0: 关闭。选择字段,发送值在「仪表显示测试: 值」中设定。重启后恢复为 0。\n1 限速数字 · 2 限速显示颜色/闪烁 · 3 标志类型 · 4 天气 · 5 国家 · 6 SLA 图标 · 7~11 0x162 未命名位(7: 53~63, 8: 159~162, 9: 183~191, 10: 252~255, 11: 87/111/135/212) · 12 HDA图标 · 13 LFA图标 · 14 设定速度颜色 · 15 设定速度HUD · 16 车圈 · 17 车距DISTANCE · 18 车距车 · 19 车距间隔 · 20 前车 · 21 ALERTS_4 文字 · 22 ALERTS_5 文字 · 23 背景 · 24 车道高亮 · 25 车道缩放 · 26 中心线\n仅改变显示,不影响控制。",
+ "min": 0,
+ "max": 26,
+ "default": 0,
+ "unit": 1
+ },
+ {
+ "group": "시작",
+ "name": "ClusterLaneTestValue",
+ "title": "계기판 표시 테스트: 값(CCNC)",
+ "descr": "「계기판 표시 테스트: 칸」에 보낼 값. 칸의 비트 수를 넘는 부분은 잘립니다(표지판 0~255, 4비트 칸 0~15 등).\n-1: 자동 순환 — 1초마다 0부터 끝까지 올리고 현재 값을 계기판 설정속도 숫자에 표시(7~11 비트 칸은 한 비트씩, 비트 번호 표시). 재부팅하면 0.",
+ "egroup": "START",
+ "etitle": "Cluster Display Test: Value (CCNC)",
+ "edescr": "Value sent to the field chosen in \"Cluster Display Test: Field\". Bits beyond the field width are dropped (sign 0-255, 4-bit fields 0-15, etc.).\n-1: sweep, stepping from 0 to the field's max every 1 s and showing it in the set speed number (bit fields 7-11 step one bit at a time and show the bit number). Resets to 0 on reboot.",
+ "cgroup": "启动设置",
+ "ctitle": "仪表显示测试: 值 (CCNC)",
+ "cdescr": "发送到「仪表显示测试: 字段」的值。超出字段位数的部分被截断(标志 0~255,4 位字段 0~15 等)。\n-1: 自动循环 — 每 1 秒从 0 递增到最大值,并在设定速度数字处显示当前值(7~11 位字段逐位切换并显示位号)。重启后恢复为 0。",
+ "min": -1,
+ "max": 2047,
+ "default": 0,
+ "unit": 1
+ },
{
"group": "시작",
"name": "MapboxStyle",
@@ -3913,6 +3948,23 @@
"unit": 5,
"display_unit": "percent"
},
+ {
+ "group": "화면",
+ "name": "ScreenOn",
+ "title": "화면 켜기(C4)",
+ "descr": "기본 켜짐. 끄면 C4 화면과 백라이트를 끕니다(오프로드·주행 모두). 화면을 터치하면 5초간 켜지고, 주행 중 경고가 뜨면 경고가 떠 있는 동안 켜집니다. 경고음과 주행 기능은 그대로 동작합니다.\n다시 켜면 약 5초 안에 화면이 켜집니다. C3/C3X는 영향 없음.",
+ "egroup": "DISPLAY",
+ "etitle": "Screen On (C4)",
+ "edescr": "On by default. Turning it off switches the C4 screen and backlight off (offroad and onroad). A touch wakes the screen for 5 seconds, and it stays on while an onroad alert is shown. Alert sounds and driving functions keep working.\nTurning it back on brings the screen back within about 5 seconds. No effect on C3/C3X.",
+ "cgroup": "显示",
+ "ctitle": "开启屏幕(C4)",
+ "cdescr": "默认开启。关闭后关闭 C4 屏幕和背光(停车和行车时均如此)。触摸屏幕会点亮 5 秒,行车中出现警报时在警报显示期间点亮。提示音和驾驶功能照常工作。\n重新开启后约 5 秒内屏幕点亮。对 C3/C3X 无影响。",
+ "min": 0,
+ "max": 1,
+ "default": 1,
+ "unit": 1,
+ "control": "toggle"
+ },
{
"group": "화면",
"name": "ShowModelView",
diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py
index b7fade7d46b..33cc8db980e 100644
--- a/openpilot/selfdrive/ui/ui_state.py
+++ b/openpilot/selfdrive/ui/ui_state.py
@@ -17,7 +17,9 @@
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.hardware import HARDWARE, PC
-BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50
+IS_MICI = HARDWARE.get_device_type() == "mici"
+BACKLIGHT_OFFROAD = 65 if IS_MICI else 50
+SCREEN_OFF_TOUCH_WAKE_S = 5.0
class UIStatus(Enum):
@@ -109,6 +111,7 @@ def _initialize(self):
# Brightness and UI options
self.show_brightness_ratio: float = 1.0
+ self.screen_off: bool = False
self.show_model_view: int = 0
self.share_data: bool = False
self.show_camera_with_cluster: bool = False
@@ -225,6 +228,7 @@ def update_params(self) -> None:
self.show_date_time = self.params.get_int("ShowDateTime")
self.show_radar_info = self.params.get_int("ShowRadarInfo")
self.show_brightness_ratio: float = self.params.get_int("ShowCustomBrightness") / 100.0
+ self.screen_off = IS_MICI and self.params.get("ScreenOn", return_default=True) is False # unset key keeps the screen on
self.show_model_view = self.params.get_int("ShowModelView")
self.share_data = self.params.get_bool("ShareData")
self.show_camera_with_cluster = self.params.get_int("ShowCameraWithCluster") == 1
@@ -259,6 +263,7 @@ def __init__(self):
self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 2.00, 1 / gui_app.target_fps)
self._brightness_thread: threading.Thread | None = None
self._brightness_timer: int = 20
+ self._screen_off_wake_until: float = 0.0
@property
def awake(self) -> bool:
@@ -340,7 +345,8 @@ def _update_wakefulness(self):
ignition_just_turned_off = not ui_state.ignition and self._ignition
self._ignition = ui_state.ignition
- if ignition_just_turned_off or any(ev.left_down for ev in gui_app.mouse_events):
+ touched = any(ev.left_down for ev in gui_app.mouse_events)
+ if ignition_just_turned_off or touched:
self._reset_interactive_timeout()
self._brightness_timer = 20
@@ -350,7 +356,18 @@ def _update_wakefulness(self):
callback()
self._prev_timed_out = interaction_timeout
- self._set_awake(ui_state.ignition or not interaction_timeout or PC)
+ if touched:
+ self._screen_off_wake_until = time.monotonic() + SCREEN_OFF_TOUCH_WAKE_S
+ screen_off = ui_state.screen_off and time.monotonic() > self._screen_off_wake_until and not self._alert_visible()
+ self._set_awake((ui_state.ignition or not interaction_timeout or PC) and not screen_off)
+
+ @staticmethod
+ def _alert_visible() -> bool:
+ if not ui_state.started:
+ return False
+ sm = ui_state.sm
+ # a stale selfdriveState is drawn as a critical alert too
+ return time.monotonic() - sm.recv_time['selfdriveState'] > 1.0 or sm['selfdriveState'].alertSize != 0
def _set_awake(self, on: bool):
if on != self._awake:
diff --git a/tools/docs/wiki_settings/tests/test_ci_check.py b/tools/docs/wiki_settings/tests/test_ci_check.py
index 1ba2fa27a03..15f0aaa985f 100644
--- a/tools/docs/wiki_settings/tests/test_ci_check.py
+++ b/tools/docs/wiki_settings/tests/test_ci_check.py
@@ -47,7 +47,7 @@ def test_read_only_check_writes_reports_without_touching_wiki(self):
self.assertTrue((output / "summary.md").is_file())
self.assertTrue((output / "pages.diff").is_file())
stored = json.loads((output / "summary.json").read_text(encoding="utf-8"))
- self.assertEqual(stored["result"]["settings"], 195)
+ self.assertEqual(stored["result"]["settings"], 199)
self.assertEqual(stored["validationIssues"], [])
self.assertIn("Settings-Catalog.md", (output / "pages.diff").read_text(encoding="utf-8"))
diff --git a/tools/docs/wiki_settings/tests/test_generate.py b/tools/docs/wiki_settings/tests/test_generate.py
index f0607c27ced..bde12d4227a 100644
--- a/tools/docs/wiki_settings/tests/test_generate.py
+++ b/tools/docs/wiki_settings/tests/test_generate.py
@@ -85,7 +85,7 @@ def test_current_catalog_generates_all_settings_and_valid_markdown(self):
catalog_commit=COMMIT,
generated_at=STAMP,
)
- self.assertEqual(len(result.generated_settings), 196)
+ self.assertEqual(len(result.generated_settings), 199)
self.assertIn("NNFF", result.index["settings"])
self.assertNotIn("SteerHandoverMode", result.index["settings"])
self.assertIn("SignalAssistMode", result.index["settings"])
@@ -98,15 +98,15 @@ def test_current_catalog_generates_all_settings_and_valid_markdown(self):
for retired in ("ClusterHudPriority", "ClusterHudCoreMode", "ClusterHudLiveFps", "CanfdStopRetry", "DisableDM",
"CarrotYouTubeLive", "CarrotYouTubeQuality", "CarrotYouTubeTimestamp"):
self.assertNotIn(retired, result.index["settings"])
- self.assertEqual(result.index["review"], {"current": 0, "needs_review": 196})
+ self.assertEqual(result.index["review"], {"current": 0, "needs_review": 199})
self.assertEqual(result.index["locales"], ["ko", "en", "zh"])
- self.assertEqual(len(result.pages), (196 * 3) + 2)
+ self.assertEqual(len(result.pages), (199 * 3) + 2)
setting_pages = {
name: text
for name, text in result.pages.items()
if GENERATOR.GENERATED_PAGE_RE.fullmatch(name)
}
- self.assertEqual(len(setting_pages), 196 * 3)
+ self.assertEqual(len(setting_pages), 199 * 3)
self.assertTrue(all(text.count("