IQ.Pilot Release Commit @ 2f37564

This commit is contained in:
IQ.Lvbs CI [bot]
2026-07-27 01:30:19 -05:00
parent 6a4096e52e
commit 15c14e1369
236 changed files with 2475 additions and 990 deletions

View File

@@ -214,9 +214,10 @@ class NoEntryCard(AlertCard):
def __init__(self,
alert_text_2: str,
alert_text_1: str = "openpilot Unavailable",
visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none):
visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none,
priority: Tier = Tier.LOW):
primary, secondary, size = _mici_reframe(alert_text_1, alert_text_2)
super().__init__(primary, secondary, AlertStatus.normal, size, Tier.LOW, visual_alert, AudibleAlert.refuse, 3.0)
super().__init__(primary, secondary, AlertStatus.normal, size, priority, visual_alert, AudibleAlert.refuse, 3.0)
class GentleDisableCard(AlertCard):

View File

@@ -14,12 +14,14 @@ from openpilot.common.basedir import BASEDIR
from openpilot.iqpilot.iq_maps import VENDOR_MAPD_PATH
from openpilot.iqpilot.iq_maps.vendor_mapd_installer import (
VENDOR_RELEASE_TAG,
get_file_hash,
sha256_of_file,
)
_RELEASE_SYMBOL = "VENDOR_RELEASE_TAG"
_INSTALLER_SRC = os.path.join(BASEDIR, "iqpilot", "iq_maps", "vendor_mapd_installer.py")
_HASH_FILE = os.path.join(BASEDIR, "iqpilot", "iq_maps", "tests", "mapd_hash")
# public: the checked-in hash the version test compares the installed binary against
HASH_FILE = os.path.join(BASEDIR, "iqpilot", "iq_maps", "tests", "mapd_hash")
_HASH_FILE = HASH_FILE
_TAG_ASSIGN = re.compile(rf'^{_RELEASE_SYMBOL}\s*=\s*["\'][^"\']*["\']', re.MULTILINE)
@@ -39,7 +41,7 @@ def rewrite_pinned_tag(new_tag: str) -> bool:
def refresh_hash_file() -> None:
digest = get_file_hash(VENDOR_MAPD_PATH)
digest = sha256_of_file(VENDOR_MAPD_PATH)
with open(_HASH_FILE, "w") as f:
f.write(digest)
print(f"wrote binary hash {digest} -> {_HASH_FILE}")

View File

@@ -34,7 +34,7 @@ _NET_PROBE_ATTEMPTS = 10
_NET_PROBE_INTERVAL_S = 2
def get_file_hash(path: str) -> str:
def sha256_of_file(path: str) -> str:
"""Hex SHA-256 digest of a file on disk."""
digest = hashlib.sha256()
with open(path, "rb") as handle:
@@ -103,7 +103,7 @@ class VendorMapdInstaller:
if not reference:
return True
try:
return get_file_hash(VENDOR_MAPD_PATH) == reference
return sha256_of_file(VENDOR_MAPD_PATH) == reference
except OSError:
return False

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -69,7 +69,7 @@ def _cleanup_unsupported_params(cp, cp_iq, store=None) -> None:
set_speed_limit_controller_availability(cp, cp_iq, store)
def setup_interfaces(ci, store=None) -> None:
def apply_iq_car_config(ci, store=None) -> None:
store = store or _Store()
if _stamp_lateral_model(ci.CP, ci.CP_IQ, store):
ci.configure_torque_tune(ci.CP.carFingerprint, ci.CP.lateralTuning)

View File

@@ -81,5 +81,5 @@ def _write(vehicles: dict[str, dict], basedir: str = BASEDIR) -> str:
if __name__ == "__main__":
from iqdbc.iqpilot.car.platform_list import get_car_list
from iqdbc.lvbs.car.platform_list import get_car_list
print("wrote", _write(get_car_list()))

View File

@@ -4,152 +4,140 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from cereal import messaging, custom
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
from openpilot.iqpilot.selfdrive.selfdrived.events import IQEvents
PATH_QUEUE_GATE = 30
LEAD_GROWTH_GATE = 1.0
TRIGGER_HOLD_S = 0.3
PARAM_PATH = "EndToEndAlert"
PARAM_LEAD = "EndToEndLeadAlert"
PARAM_STRIDE_S = 2.0
SETTLE_S = 1.0
CONFIRM_S = 0.4
ROLL_MPS = 0.3
HORIZON_TAIL = 5
PATH_SPEED_MPS = 3.0
LEAD_QUEUE_M = 12.0
LEAD_SPEED_MPS = 1.0
LEAD_GAP_M = 0.5
class TriggerPhase:
IDLE = 0
ARMED = 1
FIRED = 2
class _Confirm:
def __init__(self, window_s: float):
self._window = window_s
self._held = 0.0
self._spent = False
def clear(self) -> None:
self._held = 0.0
self._spent = False
def poll(self, holds: bool) -> bool:
if self._spent:
return False
self._held = self._held + DT_MDL if holds else 0.0
if self._held < self._window:
return False
self._spent = True
return True
class StopAndLeadChimeEngine:
class _Dwell:
def __init__(self):
self._frame = -1
self._toggle = {"path_queue": False, "lead_depart": False}
self._trigger = {
"path_queue": {"phase": TriggerPhase.IDLE, "last_phase": TriggerPhase.IDLE, "fired": False, "hold_ticks": 0, "anchor": -1.0},
"lead_depart": {"phase": TriggerPhase.IDLE, "last_phase": TriggerPhase.IDLE, "fired": False, "hold_ticks": 0, "anchor": -1.0},
}
self._standstill_gate = {"open": False, "was_open": False, "moving_frame": -1}
self._lead_state = {"visible": False, "primed": False, "armed": False, "warmup_ticks": 0}
self.seconds = 0.0
self.lead_floor = float('inf')
def _read_toggles(self) -> None:
self._toggle["path_queue"] = False
self._toggle["lead_depart"] = False
def clear(self) -> None:
self.seconds = 0.0
self.lead_floor = float('inf')
def _mark_motion(self, standstill: bool, speed_mps: float) -> bool:
is_moving = (not standstill) and speed_mps > 0.1
if is_moving:
self._standstill_gate["moving_frame"] = self._frame
return is_moving
def tick(self, lead_range: float | None) -> None:
self.seconds += DT_MDL
if lead_range is not None:
self.lead_floor = min(self.lead_floor, lead_range)
def _within_motion_grace(self) -> bool:
stamp = self._standstill_gate["moving_frame"]
if stamp == -1:
return True
return (self._frame - stamp) * DT_MDL < 2.0
@property
def settled(self) -> bool:
return self.seconds >= SETTLE_S
def _update_gate(self, standstill: bool, speed_mps: float, gas_pressed: bool, controls_enabled: bool) -> None:
moving = self._mark_motion(standstill, speed_mps)
self._standstill_gate["open"] = (not moving) and (not gas_pressed) and (not controls_enabled) and (not self._within_motion_grace())
@property
def queued(self) -> bool:
return self.lead_floor < LEAD_QUEUE_M
@staticmethod
def _path_end_x(path_x):
return float(path_x[-1]) if len(path_x) else 0.0
def _path_queue_signal(self, path_x) -> bool:
node = self._trigger["path_queue"]
if node["phase"] != TriggerPhase.ARMED:
node["hold_ticks"] = 0
return False
node["hold_ticks"] = node["hold_ticks"] + 1 if self._path_end_x(path_x) > PATH_QUEUE_GATE else 0
return node["hold_ticks"] * DT_MDL > TRIGGER_HOLD_S
class EndToEndAlertEngine:
def __init__(self):
self._params = Params()
self._on = {"path": False, "lead": False}
self._elapsed_since_read = PARAM_STRIDE_S
self._dwell = _Dwell()
self._confirm = {"path": _Confirm(CONFIRM_S), "lead": _Confirm(CONFIRM_S)}
self._fired = {"path": False, "lead": False}
def _update_lead_prime(self, seen: bool, distance_m: float) -> None:
near_lead = seen and distance_m < 8.0
opened_now = self._standstill_gate["open"] and not self._standstill_gate["was_open"]
if opened_now and near_lead:
self._lead_state["primed"] = True
elif not self._standstill_gate["open"]:
self._lead_state["primed"] = False
if self._standstill_gate["open"] and self._lead_state["primed"] and near_lead:
self._lead_state["warmup_ticks"] += 1
self._lead_state["armed"] = self._lead_state["warmup_ticks"] * DT_MDL >= 1.0
def _refresh_params(self) -> None:
self._elapsed_since_read += DT_MDL
if self._elapsed_since_read < PARAM_STRIDE_S:
return
self._lead_state["warmup_ticks"] = 0
self._lead_state["armed"] = False
def _lead_depart_signal(self, distance_m: float) -> bool:
node = self._trigger["lead_depart"]
if node["phase"] != TriggerPhase.ARMED:
node["anchor"] = -1.0
node["hold_ticks"] = 0
return False
if node["anchor"] == -1.0 or distance_m < node["anchor"]:
node["anchor"] = distance_m
grew = node["anchor"] != -1.0 and (distance_m - node["anchor"]) > LEAD_GROWTH_GATE
node["hold_ticks"] = node["hold_ticks"] + 1 if grew else 0
return node["hold_ticks"] * DT_MDL > TRIGGER_HOLD_S
def _sample_inputs(self, sm: messaging.SubMaster):
cs = sm['carState']
cc = sm['carControl']
lead_one = sm['radarState'].leadOne
self._lead_state["visible"] = lead_one.status
lead_distance = lead_one.dRel
self._update_gate(cs.standstill, cs.vEgo, cs.gasPressed, cc.enabled)
queue_fire = self._path_queue_signal(sm['modelV2'].position.x)
self._update_lead_prime(self._lead_state["visible"], lead_distance)
lead_fire = self._lead_depart_signal(lead_distance)
self._standstill_gate["was_open"] = self._standstill_gate["open"]
return queue_fire, lead_fire
self._elapsed_since_read = 0.0
self._on["path"] = self._params.get_bool(PARAM_PATH)
self._on["lead"] = self._params.get_bool(PARAM_LEAD)
@staticmethod
def _step_phase(phase: int, enabled: bool, allowed: bool, fired: bool) -> tuple[int, bool]:
if phase == TriggerPhase.IDLE:
return (TriggerPhase.ARMED, fired) if (allowed and enabled) else (phase, fired)
if not allowed or not enabled:
return TriggerPhase.IDLE, fired
if phase == TriggerPhase.ARMED and fired:
return TriggerPhase.FIRED, fired
return phase, fired
def _car_holds_long(sm: messaging.SubMaster) -> bool:
# AOL steers without raising selfdriveState.enabled, so the pair reads as long authority
return bool(sm['selfdriveState'].enabled or sm['carState'].cruiseState.enabled)
def _advance_trigger(self, key: str, enabled: bool, allowed: bool, fired: bool) -> None:
node = self._trigger[key]
node["last_phase"] = node["phase"]
node["phase"], node["fired"] = self._step_phase(node["phase"], enabled, allowed, fired)
@staticmethod
def _halted(cs) -> bool:
return bool(cs.standstill) or abs(cs.vEgo) < ROLL_MPS
@staticmethod
def _horizon_speed(model) -> float:
# capnp list readers reject slices
samples = model.velocity.x
count = len(samples)
if count < HORIZON_TAIL:
return 0.0
return sum(samples[i] for i in range(count - HORIZON_TAIL, count)) / HORIZON_TAIL
def _rearm(self) -> None:
self._dwell.clear()
for gate in self._confirm.values():
gate.clear()
def update(self, sm: messaging.SubMaster, iq_events: IQEvents) -> None:
self._read_toggles()
queue_fire, lead_fire = self._sample_inputs(sm)
self._refresh_params()
self._fired["path"] = self._fired["lead"] = False
self._advance_trigger("path_queue", self._toggle["path_queue"], self._standstill_gate["open"] and not self._lead_state["visible"], queue_fire)
self._advance_trigger("lead_depart", self._toggle["lead_depart"], self._standstill_gate["open"] and self._lead_state["armed"], lead_fire)
cs = sm['carState']
lead = sm['radarState'].leadOne
lead_range = float(lead.dRel) if lead.status else None
if self._trigger["path_queue"]["fired"] or self._trigger["lead_depart"]["fired"]:
if not self._halted(cs) or cs.gasPressed or self._car_holds_long(sm):
self._rearm()
return
self._dwell.tick(lead_range)
if not self._dwell.settled:
return
if self._on["path"] and lead_range is None:
opened = self._horizon_speed(sm['modelV2']) > PATH_SPEED_MPS
self._fired["path"] = self._confirm["path"].poll(opened)
if self._on["lead"] and lead_range is not None and self._dwell.queued:
pulling = lead.vLead > LEAD_SPEED_MPS and (lead_range - self._dwell.lead_floor) > LEAD_GAP_M
self._fired["lead"] = self._confirm["lead"].poll(pulling)
if self._fired["path"] or self._fired["lead"]:
iq_events.add(custom.IQOnroadEvent.EventName.e2eChime)
self._frame += 1
@property
def path_alert(self) -> bool:
return self._fired["path"]
@property
def queue_alert(self):
return self._trigger["path_queue"]["fired"]
@queue_alert.setter
def queue_alert(self, payload):
self._trigger["path_queue"]["fired"] = bool(payload)
@property
def lead_alert(self):
return self._trigger["lead_depart"]["fired"]
@lead_alert.setter
def lead_alert(self, payload):
self._trigger["lead_depart"]["fired"] = bool(payload)
E2EAlertsHelper = StopAndLeadChimeEngine
def lead_alert(self) -> bool:
return self._fired["lead"]

View File

@@ -0,0 +1,133 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
from types import SimpleNamespace
import pytest
import cereal.messaging as messaging
from cereal import custom
from openpilot.common.realtime import DT_MDL
from openpilot.iqpilot.selfdrive.controls.lib.helpers.e2e_alerts import (
EndToEndAlertEngine, CONFIRM_S, SETTLE_S, HORIZON_TAIL, PATH_SPEED_MPS, LEAD_SPEED_MPS, LEAD_GAP_M)
E2E_CHIME = custom.IQOnroadEvent.EventName.e2eChime
class _Events(list):
def add(self, name):
self.append(name)
def _model(horizon):
msg = messaging.new_message('modelV2')
msg.modelV2.velocity.x = [0.0] * (33 - HORIZON_TAIL) + [horizon] * HORIZON_TAIL
return msg.as_reader().modelV2
def _sm(*, v_ego=0.0, standstill=True, gas=False, enabled=False, cruise=False,
horizon=0.0, lead=None):
lead = lead or SimpleNamespace(status=False, dRel=0.0, vLead=0.0)
return {
'carState': SimpleNamespace(vEgo=v_ego, standstill=standstill, gasPressed=gas,
cruiseState=SimpleNamespace(enabled=cruise)),
'selfdriveState': SimpleNamespace(enabled=enabled),
'radarState': SimpleNamespace(leadOne=lead),
'modelV2': _model(horizon),
}
def _engine(path=True, lead=True):
engine = EndToEndAlertEngine()
engine._refresh_params = lambda: None
engine._on = {"path": path, "lead": lead}
return engine
def _run(engine, sm, seconds):
events = _Events()
chimes = 0
for _ in range(int(seconds / DT_MDL)):
engine.update(sm, events)
chimes += events.count(E2E_CHIME)
events.clear()
return chimes
def _lead(d_rel, v_lead=0.0):
return SimpleNamespace(status=True, dRel=d_rel, vLead=v_lead)
def test_path_opens_chimes_once():
engine = _engine()
assert _run(engine, _sm(horizon=0.0), SETTLE_S + 1.0) == 0
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), CONFIRM_S + 1.0) == 1
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), 5.0) == 0
def test_path_needs_the_settle_dwell():
engine = _engine()
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), SETTLE_S - 0.2) == 0
def test_path_silent_while_openpilot_long_is_engaged():
engine = _engine()
_run(engine, _sm(horizon=0.0, enabled=True), SETTLE_S + 1.0)
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0, enabled=True), 5.0) == 0
def test_path_silent_while_stock_acc_holds_the_car():
engine = _engine()
_run(engine, _sm(horizon=0.0, cruise=True), SETTLE_S + 1.0)
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0, cruise=True), 5.0) == 0
def test_path_chimes_under_aol():
engine = _engine()
_run(engine, _sm(horizon=0.0), SETTLE_S + 1.0)
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), CONFIRM_S + 1.0) == 1
def test_path_ignores_a_visible_lead():
engine = _engine(lead=False)
_run(engine, _sm(horizon=0.0, lead=_lead(6.0)), SETTLE_S + 1.0)
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0, lead=_lead(6.0)), 5.0) == 0
def test_lead_pullaway_chimes_once():
engine = _engine()
assert _run(engine, _sm(lead=_lead(6.0)), SETTLE_S + 1.0) == 0
moving = _sm(lead=_lead(6.0 + LEAD_GAP_M + 0.5, LEAD_SPEED_MPS + 1.0))
assert _run(engine, moving, CONFIRM_S + 1.0) == 1
assert _run(engine, moving, 5.0) == 0
def test_lead_creep_inside_the_gap_stays_silent():
engine = _engine()
_run(engine, _sm(lead=_lead(6.0)), SETTLE_S + 1.0)
assert _run(engine, _sm(lead=_lead(6.0 + LEAD_GAP_M / 2, LEAD_SPEED_MPS + 1.0)), 5.0) == 0
def test_lead_far_ahead_is_not_a_queue():
engine = _engine()
_run(engine, _sm(lead=_lead(40.0)), SETTLE_S + 1.0)
assert _run(engine, _sm(lead=_lead(44.0, LEAD_SPEED_MPS + 1.0)), 5.0) == 0
def test_gas_and_motion_rearm_the_dwell():
engine = _engine()
_run(engine, _sm(horizon=0.0), SETTLE_S + 1.0)
_run(engine, _sm(v_ego=5.0, standstill=False, horizon=8.0), 2.0)
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), SETTLE_S - 0.2) == 0
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), CONFIRM_S + 1.0) == 1
@pytest.mark.parametrize("param_off", ["path", "lead"])
def test_each_param_gates_only_its_own_trigger(param_off):
engine = _engine(path=param_off != "path", lead=param_off != "lead")
if param_off == "path":
_run(engine, _sm(horizon=0.0), SETTLE_S + 1.0)
assert _run(engine, _sm(horizon=PATH_SPEED_MPS + 2.0), 5.0) == 0
else:
_run(engine, _sm(lead=_lead(6.0)), SETTLE_S + 1.0)
assert _run(engine, _sm(lead=_lead(8.0, LEAD_SPEED_MPS + 1.0)), 5.0) == 0

View File

@@ -13,7 +13,7 @@ from openpilot.selfdrive.car.cruise import V_CRUISE_MAX
from openpilot.iqpilot.selfdrive.controls.lib.custom_stop_distance import CustomStopDistance
from openpilot.iqpilot.selfdrive.controls.lib.iq_dynamic.engine import IQDynamicController
from openpilot.iqpilot.selfdrive.controls.lib.iq_dynamic.imahelper import IQConstants
from openpilot.iqpilot.selfdrive.controls.lib.helpers.e2e_alerts import E2EAlertsHelper
from openpilot.iqpilot.selfdrive.controls.lib.helpers.e2e_alerts import EndToEndAlertEngine
from openpilot.iqpilot.selfdrive.controls.lib.slc_vcruise import SLCVCruise
from openpilot.iqpilot.selfdrive.controls.lib.speed_limit_controller import LIMIT_ADAPT_ACC
from openpilot.iqpilot.selfdrive.selfdrived.events import IQEvents
@@ -34,7 +34,7 @@ class LongitudinalPlannerIQ:
self.slimit = SLCVCruise()
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
self.source = LongitudinalPlanSource.cruise
self.iqmodeloutput = E2EAlertsHelper()
self.e2e_alerts = EndToEndAlertEngine()
self.output_v_target = 0.
self.output_a_target = 0.
self.speed_limit_last = 0.
@@ -146,7 +146,7 @@ class LongitudinalPlannerIQ:
for event_name in getattr(self.slimit, 'pending_events', []):
self.events_iq.add(event_name)
self.custom_stop_distance.update()
self.iqmodeloutput.update(sm, self.events_iq)
self.e2e_alerts.update(sm, self.events_iq)
if bool(getattr(sm["iqCarState"], "alcOverrideAlert", False)):
self.events_iq.add(custom.IQOnroadEvent.EventName.steeringOverrideReengageAlc)
@@ -244,10 +244,9 @@ class LongitudinalPlannerIQ:
assist.vTarget = float(self.output_v_target if assist.active else 255.)
assist.aTarget = float(self.slimit.slc_a_target if assist.active else 0.)
# E2E Alerts
e2eAlerts = plan_msg.e2eAlerts
e2eAlerts.greenLightAlert = self.iqmodeloutput.queue_alert
e2eAlerts.leadDepartAlert = self.iqmodeloutput.lead_alert
e2eAlerts.pathOpen = self.e2e_alerts.path_alert
e2eAlerts.leadPullaway = self.e2e_alerts.lead_alert
valid = sm.all_checks(service_list=['carState', 'controlsState'])

View File

@@ -1,6 +0,0 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Shared speed-limit enums and helpers. The live resolver is IQSpeedLimitResolver
in speed_limit_controller.py.
"""

View File

@@ -95,8 +95,17 @@ _CAMERA_LABELS = {
def speed_camera_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
nav = sm['iqNavState']
label = _CAMERA_LABELS.get(int(getattr(nav.cameraType, "raw", nav.cameraType)), "Speed Camera")
ctype = int(getattr(nav.cameraType, "raw", nav.cameraType))
label = _CAMERA_LABELS.get(ctype, "Speed Camera")
distance = float(nav.cameraDistance)
# RF (BLE/WiFi) Flock detection is a live proximity hit with no meaningful
# distance — flockd/navd flag it with distance 0 on the alpr camera type.
if ctype == int(custom.IQNavState.CameraType.alpr) and distance <= 0.0:
return Alert(
"Flock Camera Detected",
"",
AlertStatus.normal, AlertSize.small,
Priority.HIGH, VisualAlert.none, AudibleAlert.prompt, .2)
if metric:
dist_str = f"{distance:.0f} m" if distance < 1000.0 else f"{distance / 1000.0:.1f} km"
else:

View File

@@ -8,8 +8,8 @@ import pyray as rl
from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, IQToggleAction, SafeIQToggleAction
from openpilot.system.ui.iqpilot.widgets.list_view import OptionControl
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, IQToggleAction, SafeIQToggleAction
from openpilot.system.ui.iqwidgets.widgets.list_view import OptionControl
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import NavButton
from openpilot.system.ui.widgets.scroller_tici import Scroller

View File

@@ -18,17 +18,17 @@ from openpilot.selfdrive.ui.layouts.settings.software import time_ago
from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.hardware.hw import Paths
from openpilot.system.ui.iqpilot.lib.styles import style
from openpilot.system.ui.iqpilot.lib.utils import NoElideButtonAction
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, toggle_item
from openpilot.system.ui.iqpilot.widgets.list_view import OptionControl
from openpilot.system.ui.iqpilot.widgets.list_view import multiple_button_item, toggle_item
from openpilot.system.ui.iqpilot.widgets.list_view import option_item, toggle_item, ToggleAction
from openpilot.system.ui.iqpilot.widgets.list_view import progress_item
from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, multiple_button_item, IQListItem, IQLineSeparator
from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, option_item, IQLineSeparator
from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, simple_button_item, option_item, IQLineSeparator
from openpilot.system.ui.iqpilot.widgets.list_view import TreeFolder, TreeNode, TreeOptionDialog
from openpilot.system.ui.iqwidgets.lib.styles import style
from openpilot.system.ui.iqwidgets.lib.utils import WideButtonAction
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, toggle_item
from openpilot.system.ui.iqwidgets.widgets.list_view import OptionControl
from openpilot.system.ui.iqwidgets.widgets.list_view import multiple_button_item, toggle_item
from openpilot.system.ui.iqwidgets.widgets.list_view import option_item, toggle_item, ToggleAction
from openpilot.system.ui.iqwidgets.widgets.list_view import progress_item
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, multiple_button_item, IQListItem, IQLineSeparator
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, option_item, IQLineSeparator
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, simple_button_item, option_item, IQLineSeparator
from openpilot.system.ui.iqwidgets.widgets.list_view import PickerGroup, PickerItem, PickerDialog
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.multilang import tr, tr_noop
@@ -60,8 +60,8 @@ from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import CUS
from openpilot.selfdrive.ui.layouts.settings import settings as OP
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.iqpilot.lib.styles import metrics
from openpilot.system.ui.iqpilot.widgets.list_view import (
from openpilot.system.ui.iqwidgets.lib.styles import metrics
from openpilot.system.ui.iqwidgets.widgets.list_view import (
option_item as option_item,
multiple_button_item as multiple_button_item,
button_item as button_item,
@@ -69,11 +69,11 @@ from openpilot.system.ui.iqpilot.widgets.list_view import (
NavSectionButton,
Spacer,
)
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, IQMultipleButtonAction, IQToggleAction, IQLineSeparator
from openpilot.system.ui.iqpilot.widgets.list_view import button_item, toggle_item
from openpilot.system.ui.iqpilot.widgets.list_view import NoticeModal
from openpilot.system.ui.iqpilot.widgets.list_view import TreeOptionDialog, TreeNode, TreeFolder
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, IQMultipleButtonAction, IQToggleAction, IQLineSeparator
from openpilot.system.ui.iqwidgets.widgets.list_view import button_item, toggle_item
from openpilot.system.ui.iqwidgets.widgets.list_view import NoticeModal
from openpilot.system.ui.iqwidgets.widgets.list_view import PickerDialog, PickerItem, PickerGroup
from openpilot.system.ui.lib.application import gui_app, MousePos
from openpilot.system.ui.lib.multilang import tr_noop
from openpilot.system.ui.lib.text_measure import measure_text_cached
@@ -96,9 +96,9 @@ from iqdbc.car.hyundai.values import CAR, CANFD_UNSUPPORTED_LONGITUDINAL_CAR, UN
from iqdbc.car.subaru.values import CAR, SubaruFlags
from iqdbc.car.volkswagen.values import CAR, VolkswagenFlags
from openpilot.common.basedir import BASEDIR
from openpilot.system.ui.iqpilot.lib.styles import ink
from openpilot.system.ui.iqpilot.widgets.list_view import multiple_button_item
from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item
from openpilot.system.ui.iqwidgets.lib.styles import ink
from openpilot.system.ui.iqwidgets.widgets.list_view import multiple_button_item
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.widgets.list_view import ButtonAction
import json
@@ -116,9 +116,9 @@ def _fmt_seconds(value) -> str:
def onroad_brightness_label(val) -> str:
if val == OnroadBrightness.AUTO:
return tr("Auto (Default)")
return tr("Auto")
if val == OnroadBrightness.AUTO_DARK:
return tr("Auto (Dark)")
return tr("Auto — Dim")
return f"{(val - 1) * 5} %"
@@ -140,17 +140,17 @@ class DisplayLayout(Widget):
label_callback=lambda v: tr("Default") if v == 0 else f"{v} %", inline=True,
)
self._onroad_brightness_row = option_item(
param="OnroadScreenOffBrightness", title=lambda: tr("Onroad Brightness"), description="",
param="OnroadScreenOffBrightness", title=lambda: tr("Driving Brightness"), description="",
min_value=0, max_value=21, value_change_step=1,
label_callback=onroad_brightness_label, inline=True,
)
self._dim_delay_row = option_item(
param="OnroadScreenOffTimer", title=lambda: tr("Onroad Brightness Delay"), description="",
param="OnroadScreenOffTimer", title=lambda: tr("Dim Delay"), description="",
min_value=0, max_value=11, value_change_step=1,
value_map=ONROAD_BRIGHTNESS_TIMER_VALUES, label_callback=_fmt_seconds, inline=True,
)
self._idle_close_row = option_item(
param="InteractivityTimeout", title=lambda: tr("Interactivity Timeout"),
param="InteractivityTimeout", title=lambda: tr("Auto-Close Settings"),
description=lambda: tr("How long the settings screen may sit untouched before it closes itself."),
min_value=0, max_value=120, value_change_step=10,
label_callback=lambda v: tr("Default") if not v else _fmt_seconds(v), inline=True,
@@ -195,7 +195,7 @@ CHEVRON_INFO_DESCRIPTION = {
# param key -> (title fn, description fn)
_HUD_TOGGLES = {
"BlindSpot": (
lambda: tr("Show Blind Spot Warnings"),
lambda: tr("Blind Spot Alerts"),
lambda: tr("Flashes a side warning whenever the car reports something sitting in your blind spot (BSM-equipped cars only)."),
),
"IQExpandedStatus": (
@@ -203,20 +203,20 @@ _HUD_TOGGLES = {
lambda: tr("Bring back the classic UI's wide offroad status strip: temperature, vehicle, and Konn3kt state at a glance."),
),
"TorqueBar": (
lambda: tr("Steering Arc"),
lambda: tr("Steering Effort Arc"),
lambda: tr("Trace an arc over the road view showing how much steering IQ.Pilot is applying while lateral control runs."),
),
"RoadNameToggle": (
lambda: tr("Display Road Name"),
lambda: tr("Road Name Overlay"),
lambda: tr("Show the current road's name over the driving view."
"<br>Requires offline map data for your region to be installed."),
),
"ShowTurnSignals": (
lambda: tr("Display Turn Signals"),
lambda: tr("Blinker Indicators"),
lambda: tr("Mirror the car's blinkers as arrows on the driving screen."),
),
"RocketFuel": (
lambda: tr("Real-time Acceleration Bar"),
lambda: tr("Acceleration Meter"),
lambda: tr("Draw a bar along the left edge tracking measured acceleration and braking — what the car is actually "
"doing right now, not the planner's request."),
),
@@ -240,7 +240,7 @@ class VisualsLayout(Widget):
}
self._chevron_info = multiple_button_item(
title=lambda: tr("Display Metrics Below Chevron"),
title=lambda: tr("Lead Vehicle Readouts"),
description="",
buttons=[lambda: tr("Off"), lambda: tr("Distance"), lambda: tr("Speed"), lambda: tr("Time"), lambda: tr("All")],
param="ChevronInfo",
@@ -319,7 +319,7 @@ class IQNetworkUI(NetworkUI):
def _set_phase(self, phase: ScanPhase):
self._phase = phase
running = phase == ScanPhase.RUNNING
self.scan_button.set_text(tr("Scanning...") if running else tr("Scan"))
self.scan_button.set_text(tr("Searching...") if running else tr("Scan"))
self.scan_button.set_enabled(not running)
# keep the manager's spinner in step with the button, not just the quick scan call
self._wifi_manager._scanning = running
@@ -397,7 +397,7 @@ class IQMapsLayout(Widget):
self._scroller = Scroller(self.items, line_separator=True, spacing=0)
def _build_rows(self):
self._mapd_version = text_item(tr("Mapd Version"), lambda: ui_state.params.get("MapdVersion") or "Loading...")
self._mapd_version = text_item(tr("Routing Engine Version"), lambda: ui_state.params.get("MapdVersion") or "Loading...")
self._online_maps_toggle = toggle_item(
tr("Online On-Screen Maps"),
tr("Pull live Mapbox tiles for the on-screen map whenever the device has internet."),
@@ -409,15 +409,15 @@ class IQMapsLayout(Widget):
"take over the moment connectivity drops. Tiles come down with your selected region."),
param="OfflineOSMaps",
)
self._delete_maps_btn = IQListItem(tr("Downloaded Maps"), action_item=NoElideButtonAction(tr("DELETE"), enabled=True),
self._delete_maps_btn = IQListItem(tr("Stored Maps"), action_item=WideButtonAction(tr("DELETE"), enabled=True),
callback=self._confirm_wipe)
self._progress = progress_item(tr("Downloading Map"))
self._progress = progress_item(tr("Fetching Map"))
self._tile_progress = progress_item(tr("Downloading Map Tiles"))
self._update_btn = IQListItem(tr("Database Update"), action_item=NoElideButtonAction(tr("CHECK"), enabled=True),
self._update_btn = IQListItem(tr("Map Database"), action_item=WideButtonAction(tr("CHECK"), enabled=True),
callback=self._confirm_db_refresh)
self._country_btn = IQListItem(tr("Country"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True),
self._country_btn = IQListItem(tr("Country"), action_item=WideButtonAction(tr("SELECT"), enabled=True),
callback=lambda: self._open_region_picker("Country"))
self._state_btn = IQListItem(tr("State"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True),
self._state_btn = IQListItem(tr("State"), action_item=WideButtonAction(tr("SELECT"), enabled=True),
callback=lambda: self._open_region_picker("State"))
self.items = [self._mapd_version, self._online_maps_toggle, self._offline_maps_toggle, self._delete_maps_btn,
@@ -436,7 +436,7 @@ class IQMapsLayout(Widget):
def _start_wipe(self):
self._delete_maps_btn.action_item.set_enabled(False)
self._delete_maps_btn.action_item.set_text(tr("DELETING..."))
self._delete_maps_btn.action_item.set_text(tr("REMOVING..."))
threading.Thread(target=self._wipe_worker, daemon=True).start()
def _wipe_worker(self):
@@ -475,7 +475,7 @@ class IQMapsLayout(Widget):
def _open_region_picker(self, region_type):
btn = self._country_btn if region_type == "Country" else self._state_btn
btn.action_item.set_enabled(False)
btn.action_item.set_text(tr("FETCHING..."))
btn.action_item.set_text(tr("LOADING..."))
threading.Thread(target=self._region_picker_worker, args=(region_type, btn), daemon=True).start()
@staticmethod
@@ -483,7 +483,7 @@ class IQMapsLayout(Widget):
url = _BOUNDS_BASE_URL + ("nation_bounding_boxes.json" if region_type == "Country" else "us_states_bounding_boxes.json")
try:
data = requests.get(url, timeout=10).json()
return sorted((TreeNode(ref=k, data={'display_name': v['full_name']}) for k, v in data.items()),
return sorted((PickerItem(ref=k, data={'display_name': v['full_name']}) for k, v in data.items()),
key=lambda n: n.data['display_name'])
except Exception:
return []
@@ -491,14 +491,14 @@ class IQMapsLayout(Widget):
def _region_picker_worker(self, region_type, btn):
locations = self._fetch_regions(region_type)
if region_type == "State":
locations.insert(0, TreeNode(ref="All", data={'display_name': tr("All states (~6.0 GB)")}))
locations.insert(0, PickerItem(ref="All", data={'display_name': tr("All states (~6.0 GB)")}))
btn.action_item.set_enabled(True)
btn.action_item.set_text(tr("SELECT"))
key = "OsmLocation" if region_type == "Country" else "OsmState"
current = ui_state.params.get(f"{key}Name") or ""
dialog = TreeOptionDialog(tr(f"Select {region_type}"), [TreeFolder(folder="", nodes=locations)],
dialog = PickerDialog(tr(f"Select {region_type}"), [PickerGroup(folder="", nodes=locations)],
current_ref=current, search_prompt=tr("Perform a search"))
done = lambda res: self._on_region_picked(region_type, locations, key, res, dialog.selection_ref) # noqa: E731
dialog.on_exit = done
@@ -554,14 +554,14 @@ class IQMapsLayout(Widget):
if failed:
self._current_percent = 0.0
bar_text, btn_text = "0% - Downloading Maps", tr("Error: Invalid download. Retry.")
bar_text, btn_text = "0% - Downloading Maps", tr("Download failed — try again.")
elif total > 0 and downloading:
self._current_percent = max(0.0, min(100.0, done / total * 100.0))
pct = int(self._current_percent)
bar_text, btn_text = f"{pct}% - Downloading Maps", f"{done}/{total} ({pct}%)"
else:
self._current_percent = 0.0
bar_text, btn_text = "0% - Downloading Maps", tr("Downloading Maps...")
bar_text, btn_text = "0% - Downloading Maps", tr("Fetching Maps...")
self._progress.action_item.update(self._current_percent, bar_text,
show_progress=total > 0 and downloading and not failed)
@@ -599,7 +599,7 @@ class IQMapsLayout(Widget):
dt = datetime.datetime.fromtimestamp(float(ts), tz=datetime.UTC)
except (ValueError, TypeError):
dt = None
self._update_btn.action_item.set_value(tr("Last checked {}").format(time_ago(dt)))
self._update_btn.action_item.set_value(tr("Checked {}").format(time_ago(dt)))
def _update_state(self):
now = monotonic()
@@ -618,7 +618,7 @@ class IQMapsLayout(Widget):
_TIMER_LABELS = {
-1: lambda: tr("Off"),
0: lambda: tr("Nudge"),
1: lambda: tr("Nudgeless"),
1: lambda: tr("No Nudge"),
2: lambda: f"0.5 {tr('s')}",
3: lambda: f"1 {tr('s')}",
4: lambda: f"2 {tr('s')}",
@@ -635,7 +635,7 @@ class LaneChangeSettingsLayout(Widget):
def _build_rows(self):
self._lane_change_timer = option_item(
title=lambda: tr("Auto Lane Change by Blinker"),
title=lambda: tr("Blinker-Initiated Lane Change"),
param="IQLaneChangeTimer",
description=lambda: tr("Delay before a blinker-triggered lane change starts on its own — no wheel nudge "
"needed once a delay is set (default is Nudge).<br>Use the blinker for this only "
@@ -645,7 +645,7 @@ class LaneChangeSettingsLayout(Widget):
)
self._bsm_delay = toggle_item(
param="IQLaneChangeBsmDelay",
title=lambda: tr("Auto Lane Change: Delay with Blind Spot"),
title=lambda: tr("Hold Lane Change for Blind Spot"),
description=lambda: tr("Hold the automatic lane change while blind spot monitoring reports a car in the "
"target lane, releasing it once the lane is clear."),
)
@@ -685,7 +685,7 @@ class LaneChangeSettingsLayout(Widget):
# (segment label, explanation) — order matches the AolSteeringMode param values
SAB_BRAKE_RESPONSE_OPTIONS = [
(tr("Remain Active"), tr_noop("Remain Active: braking never interrupts steering assistance.")),
(tr("Stay Engaged"), tr_noop("Remain Active: braking never interrupts steering assistance.")),
(tr("Standby"), tr_noop("Standby: braking parks steering assistance; it rejoins once you're off the pedal.")),
(tr("Disengage"), tr_noop("Disengage: braking shuts steering assistance off entirely.")),
]
@@ -849,7 +849,7 @@ class SteeringLayout(Widget):
callback=lambda: self._set_current_panel(PanelType.SAB)
)
self._lane_change_settings_button = simple_button_item(
button_text=lambda: tr("Customize Lane Change"),
button_text=lambda: tr("Lane Change Options"),
button_width=800,
callback=lambda: self._set_current_panel(PanelType.LANE_CHANGE)
)
@@ -859,7 +859,7 @@ class SteeringLayout(Widget):
description=""
)
self._lane_turn_desire_toggle = toggle_item(
tr("Use Lane Turn Desires"),
tr("Low-Speed Turn Planning"),
tr("If you're driving at 20 mph (32 km/h) or below and have your blinker on,"
" the car will plan a turn in that direction at the nearest drivable path."
" This prevents situations (like at red lights) where the car might plan the wrong turn direction."),
@@ -1177,7 +1177,7 @@ class IQDeveloperLayout(DeveloperLayout):
self._scroller.add_widget(item)
def _initialize_items(self):
self.error_log_btn = button_item(tr("Error Log"), tr("VIEW"), tr("View the error log for IQ.Pilot crashes."), callback=self._on_error_log_clicked)
self.error_log_btn = button_item(tr("Crash Log"), tr("VIEW"), tr("View the error log for IQ.Pilot crashes."), callback=self._on_error_log_clicked)
self.items: list = [self.error_log_btn]
@@ -1188,7 +1188,7 @@ class IQDeveloperLayout(DeveloperLayout):
def _on_error_log_closed(self, result, log_exists):
if result == DialogResult.CONFIRM and log_exists:
dialog2 = ConfirmDialog(tr("Would you like to delete this log?"), tr("Yes"), tr("No"), rich=False)
dialog2 = ConfirmDialog(tr("Delete this log?"), tr("Yes"), tr("No"), rich=False)
gui_app.set_modal_overlay(dialog2, callback=self._on_delete_confirm)
def _on_error_log_clicked(self):
@@ -1240,7 +1240,7 @@ class IQSoftwareLayout(SoftwareLayout):
def __init__(self):
super().__init__()
self.disable_updates_toggle = toggle_item(
lambda: tr("Disable Updates"),
lambda: tr("Pause Updates"),
description="",
initial_state=ui_state.params.get_bool("DisableUpdates"),
callback=self._on_disable_updates_toggled,
@@ -1273,7 +1273,7 @@ class IQSoftwareLayout(SoftwareLayout):
self.disable_updates_toggle.action_item.set_state(ui_state.params.get_bool("DisableUpdates"))
def _on_disable_updates_toggled(self, enabled):
dialog = ConfirmDialog(tr("System reboot required for changes to take effect. Reboot now?"), tr("Reboot"))
dialog = ConfirmDialog(tr("This needs a reboot to apply. Restart now?"), tr("Reboot"))
gui_app.set_modal_overlay(dialog, callback=self._handle_reboot)
def _on_change_install_mode(self):
@@ -1319,11 +1319,11 @@ class IQSoftwareLayout(SoftwareLayout):
# single flat list: pinned branches first, then the rest alphabetically.
# prebuilt ("-prebuilt") branches are internal build outputs, not user-selectable.
pinned_nodes = [TreeNode(b, {'display_name': b}) for b in top_level_branches if b in branches]
other_nodes = [TreeNode(b, {'display_name': b}) for b in sorted(branches)
pinned_nodes = [PickerItem(b, {'display_name': b}) for b in top_level_branches if b in branches]
other_nodes = [PickerItem(b, {'display_name': b}) for b in sorted(branches)
if b not in top_level_branches and not b.endswith("-prebuilt")]
folders = [TreeFolder("", pinned_nodes + other_nodes)]
folders = [PickerGroup("", pinned_nodes + other_nodes)]
def _on_branch_selected(result):
if result == DialogResult.CONFIRM and self._branch_dialog is not None:
@@ -1334,7 +1334,7 @@ class IQSoftwareLayout(SoftwareLayout):
os.system("pkill -SIGUSR1 -f system.updated.updated")
self._branch_dialog = None
self._branch_dialog = TreeOptionDialog(tr("Select a branch"), folders, current_target, "",
self._branch_dialog = PickerDialog(tr("Select a branch"), folders, current_target, "",
on_exit=_on_branch_selected)
gui_app.set_modal_overlay(self._branch_dialog, callback=_on_branch_selected)
@@ -1388,11 +1388,11 @@ class IQDeviceLayout(DeviceLayout):
DeviceLayout._initialize_items(self)
# Using dual button with no right button for better alignment
self._always_offroad_btn = self._left_button(lambda: tr("Enable Always Offroad"), self._handle_always_offroad)
self._always_offroad_btn = self._left_button(lambda: tr("Force Offroad Mode"), self._handle_always_offroad)
self._force_onroad_btn = self._left_button(lambda: tr("Force On-Road (10 min)"), self._handle_force_onroad)
self._max_time_offroad = option_item(
title=lambda: tr("Max Time Offroad"),
title=lambda: tr("Offroad Shutdown Timer"),
description=lambda: tr("Powers the device down once it has sat this long with the engine off.\n(30h by default)"),
param="MaxTimeOffroad",
min_value=0,
@@ -1409,7 +1409,7 @@ class IQDeviceLayout(DeviceLayout):
)
self._device_wake_mode = multiple_button_item(
title=lambda: tr("Wake Up Behavior"),
title=lambda: tr("Boot State"),
description=self.wake_mode_description,
param="DeviceBootMode",
buttons=[lambda: tr("Default"), lambda: tr("Offroad")],
@@ -1420,7 +1420,7 @@ class IQDeviceLayout(DeviceLayout):
self._change_language_btn = button_item(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog)
# Silent Mode moved to the settings-hub top bar (bell bubble); this is just the dcam preview now.
self._driver_camera_btn = button_item(lambda: tr("Driver Camera Preview"), lambda: tr("PREVIEW"),
self._driver_camera_btn = button_item(lambda: tr("Driver Camera Check"), lambda: tr("PREVIEW"),
callback=self._show_driver_camera)
self._reg_and_training = dual_button_item(
@@ -1432,9 +1432,9 @@ class IQDeviceLayout(DeviceLayout):
self._reg_and_training.action_item.right_button.set_button_style(ButtonStyle.NORMAL)
self._onroad_uploads_and_reset_settings = dual_button_item(
left_text=lambda: tr("Onroad Uploads"),
left_text=lambda: tr("Upload While Driving"),
left_callback=lambda: ui_state.params.put_bool("OnroadUploads", not ui_state.params.get_bool("OnroadUploads")),
right_text=lambda: tr("Reset Settings"),
right_text=lambda: tr("Restore Defaults"),
right_callback=self._reset_settings
)
@@ -1535,7 +1535,7 @@ class IQDeviceLayout(DeviceLayout):
@staticmethod
def _handle_always_offroad():
if ui_state.engaged:
gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode")))
gui_app.set_modal_overlay(alert_dialog(tr("Disengage before forcing offroad")))
return
_offroad_mode_state = ui_state.params.get_bool("IQAlwaysOffroad")
@@ -1578,7 +1578,7 @@ class IQDeviceLayout(DeviceLayout):
@staticmethod
def _update_max_time_offroad_label(value: int) -> str:
label = tr("Always On") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h")
label = tr("Never Sleep") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h")
label += tr(" (Default)") if value == 1800 else ""
return label
@@ -1592,7 +1592,7 @@ class IQDeviceLayout(DeviceLayout):
force_onroad_active = force_onroad_until > now
# Text & Color
offroad_mode_btn_text = tr("Exit Always Offroad") if always_offroad else tr("Enable Always Offroad")
offroad_mode_btn_text = tr("Exit Offroad Mode") if always_offroad else tr("Force Offroad Mode")
offroad_mode_btn_style = ButtonStyle.PRIMARY if always_offroad else ButtonStyle.DANGER
self._always_offroad_btn.action_item.left_button.set_text(offroad_mode_btn_text)
self._always_offroad_btn.action_item.left_button.set_button_style(offroad_mode_btn_style)
@@ -1667,7 +1667,7 @@ class IQDeviceLayout(DeviceLayout):
# ===== models =====
if gui_app.iqpilot_ui():
from openpilot.system.ui.iqpilot.widgets.list_view import button_item as button_item
from openpilot.system.ui.iqwidgets.widgets.list_view import button_item as button_item
_ACTIVE_BUNDLE_KEY = "ModelManager_ActiveBundle"
_DOWNLOAD_INDEX_KEY = "ModelManager_DownloadIndex"
@@ -1692,27 +1692,27 @@ class ModelsLayout(Widget):
def _initialize_items(self):
self.current_model_item = IQListItem(
title=tr("Current Model"),
title=tr("Active Model"),
description="",
action_item=NoElideButtonAction(tr("SELECT")),
action_item=WideButtonAction(tr("SELECT")),
callback=self._handle_current_model_clicked
)
self.supercombo_label = progress_item(tr("Driving Model"))
self.vision_label = progress_item(tr("Vision Model"))
self.policy_label = progress_item(tr("Policy Model"))
self.supercombo_label = progress_item(tr("Combined Model"))
self.vision_label = progress_item(tr("Vision Weights"))
self.policy_label = progress_item(tr("Policy Weights"))
self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "", self._on_refresh_models)
self.refresh_item = button_item(tr("Reload Model List"), tr("REFRESH"), "", self._on_refresh_models)
self.clear_cache_item = IQListItem(
title=tr("Clear Model Cache"),
title=tr("Purge Model Cache"),
description="",
action_item=NoElideButtonAction(tr("CLEAR")),
action_item=WideButtonAction(tr("CLEAR")),
callback=self._clear_cache
)
self.redownload_item = button_item(tr("Redownload Current Model"), tr("REDOWNLOAD"), "", self._redownload_model)
self.cancel_download_item = button_item(tr("Cancel Download"), tr("Cancel"), "", self._cancel_model_request)
self.cancel_download_item = button_item(tr("Stop Download"), tr("Cancel"), "", self._cancel_model_request)
self.items = [self.current_model_item, self.cancel_download_item, self.supercombo_label, self.vision_label,
self.policy_label, self.redownload_item, self.refresh_item, self.clear_cache_item]
@@ -1938,7 +1938,7 @@ class ModelsLayout(Widget):
@staticmethod
def _bundle_to_node(bundle):
return TreeNode(bundle.ref, {'display_name': bundle.displayName, 'short_name': bundle.internalName})
return PickerItem(bundle.ref, {'display_name': bundle.displayName, 'short_name': bundle.internalName})
def _get_folders(self, favorites):
bundles = self.model_manager.availableBundles
@@ -1946,14 +1946,14 @@ class ModelsLayout(Widget):
for bundle in bundles:
folders.setdefault(next((ov_ride.value for ov_ride in bundle.overrides if ov_ride.key == "folder"), ""), []).append(bundle)
folders_list = [TreeFolder("", [TreeNode("Default", {'display_name': tr("Default (CD210)"), 'short_name': "Default"})])]
folders_list = [PickerGroup("", [PickerItem("Default", {'display_name': tr("Default (CD210)"), 'short_name': "Default"})])]
for folder, folder_bundles in sorted(folders.items(), key=lambda x: max((bundle.index for bundle in x[1]), default=-1), reverse=True):
folder_bundles.sort(key=lambda bundle: bundle.index, reverse=True)
name = folder + (f" - (Updated: {m.group(1)})" if folder_bundles and (m := re.search(r'\(([^)]*)\)[^(]*$', folder_bundles[0].displayName)) else "")
folders_list.append(TreeFolder(name, [self._bundle_to_node(bundle) for bundle in folder_bundles]))
folders_list.append(PickerGroup(name, [self._bundle_to_node(bundle) for bundle in folder_bundles]))
if favorites and (fav_bundles := [bundle for bundle in bundles if bundle.ref in favorites]):
folders_list.insert(1, TreeFolder("Favorites", [self._bundle_to_node(bundle) for bundle in fav_bundles]))
folders_list.insert(1, PickerGroup("Favorites", [self._bundle_to_node(bundle) for bundle in fav_bundles]))
return folders_list
def _handle_current_model_clicked(self):
@@ -1962,7 +1962,7 @@ class ModelsLayout(Widget):
folders_list = self._get_folders(favorites)
active_ref = self.model_manager.activeBundle.ref if self._has_active_bundle_param() and self.model_manager.activeBundle else "Default"
self.model_dialog = TreeOptionDialog(tr("Select a Model"), folders_list, active_ref, "ModelManager_Favs",
self.model_dialog = PickerDialog(tr("Choose a Model"), folders_list, active_ref, "ModelManager_Favs",
get_folders_fn=self._get_folders, on_exit=self._on_model_selected)
gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected)
@@ -2165,7 +2165,7 @@ class IQSettingsLayout(OP.SettingsLayout):
# ===== vehicle_brands_base =====
class BrandSettings:
class BrandPanel:
"""Per-brand extra settings rows; brands without any simply don't register."""
def __init__(self):
@@ -2184,12 +2184,12 @@ _TUNING_BLURBS = (
)
class HyundaiSettings(BrandSettings):
class HyundaiSettings(BrandPanel):
def __init__(self):
super().__init__()
self.alpha_long_available = False
self.longitudinal_tuning_item = multiple_button_item(
tr("Custom Longitudinal Tuning"), "",
tr("Longitudinal Tune Profile"), "",
[tr("Off"), tr("Dynamic"), tr("Predictive")],
button_width=300, param="HyundaiLongitudinalTuning", inline=False,
callback=lambda index: ui_state.params.put("HyundaiLongitudinalTuning", index))
@@ -2227,14 +2227,14 @@ class HyundaiSettings(BrandSettings):
_UNSUPPORTED_FLAGS = SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID
class SubaruSettings(BrandSettings):
class SubaruSettings(BrandPanel):
def __init__(self):
super().__init__()
self._supported = False
self.stop_and_go_toggle = toggle_item(tr("Stop and Go (Beta)"), "", param="SubaruStopAndGo",
self.stop_and_go_toggle = toggle_item(tr("Creep from Standstill (Beta)"), "", param="SubaruStopAndGo",
callback=lambda _: self.update_settings())
self.stop_and_go_manual_parking_brake_toggle = toggle_item(
tr("Stop and Go for Manual Parking Brake (Beta)"), "",
tr("Creep from Standstill — Manual Handbrake (Beta)"), "",
param="SubaruStopAndGoManualParkingBrake", callback=lambda _: self.update_settings())
self.items = [self.stop_and_go_toggle, self.stop_and_go_manual_parking_brake_toggle]
@@ -2283,7 +2283,7 @@ def _speed_text(kmh: int) -> str:
return f"{round(kmh * KM_TO_MILE)} mph"
class TeslaSettings(BrandSettings):
class TeslaSettings(BrandPanel):
def __init__(self):
super().__init__()
self.coop_steering_toggle = toggle_item(tr("VTB (Virtual Torque Blending)"), "", param="TeslaCoopSteering")
@@ -2306,11 +2306,11 @@ class TeslaSettings(BrandSettings):
# ===== vehicle_brands_toyota =====
class ToyotaSettings(BrandSettings):
class ToyotaSettings(BrandPanel):
def __init__(self):
super().__init__()
self.enforce_stock_longitudinal = toggle_item(
lambda: tr("Enforce Factory Longitudinal Control"),
lambda: tr("Keep Factory Gas and Brake"),
description=lambda: tr("Keeps gas and brakes with the factory Toyota system; IQ.Pilot steers only."),
initial_state=ui_state.params.get_bool("ToyotaEnforceStockLongitudinal"),
callback=self._on_toggled,
@@ -2361,7 +2361,7 @@ DESCRIPTIONS = {
}
class VolkswagenSettings(BrandSettings):
class VolkswagenSettings(BrandPanel):
def __init__(self):
super().__init__()
@@ -2445,7 +2445,7 @@ class VolkswagenSettings(BrandSettings):
# ===== vehicle_brands_factory =====
# Only brands that actually ship extra rows appear here; every other brand gets None.
_REGISTRY: dict[str, type[BrandSettings]] = {
_REGISTRY: dict[str, type[BrandPanel]] = {
"hyundai": HyundaiSettings,
"subaru": SubaruSettings,
"tesla": TeslaSettings,
@@ -2454,12 +2454,12 @@ _REGISTRY: dict[str, type[BrandSettings]] = {
}
def brand_settings_for(brand: str) -> BrandSettings | None:
def brand_settings_for(brand: str) -> BrandPanel | None:
cls = _REGISTRY.get(brand)
return cls() if cls else None
class BrandSettingsFactory:
class BrandPanelFactory:
"""Legacy shim for callers using the old factory name."""
create_brand_settings = staticmethod(brand_settings_for)
@@ -2503,7 +2503,7 @@ class VehicleSelection:
return bundle.get("name", "")
if ui_state.CP and ui_state.CP.carFingerprint != "MOCK":
return ui_state.CP.carFingerprint
return tr("No vehicle selected")
return tr("No vehicle set")
def force(self, platform_name: str) -> bool:
data = self.platforms.get(platform_name)
@@ -2516,22 +2516,22 @@ class VehicleSelection:
def clear():
ui_state.params.remove("CarPlatformBundle")
def picker_folders(self) -> list[TreeFolder]:
def node_for(name: str) -> TreeNode:
def picker_folders(self) -> list[PickerGroup]:
def node_for(name: str) -> PickerItem:
info = self.platforms[name]
years = ' '.join(map(str, info.get('year', [])))
return TreeNode(name, {
return PickerItem(name, {
'display_name': name,
'search_tags': f"{name} {info.get('make')} {years} {info.get('model', name)}",
})
names = sorted(self.platforms)
makes = sorted({self.platforms[n].get('make') for n in names})
return [TreeFolder(make, [node_for(n) for n in names if self.platforms[n].get('make') == make])
return [PickerGroup(make, [node_for(n) for n in names if self.platforms[n].get('make') == make])
for make in makes]
class PlatformSelector(Button):
class VehiclePicker(Button):
"""Row button that either clears a forced platform or opens the vehicle picker."""
def __init__(self, on_platform_change: Callable[[], None] | None = None):
@@ -2574,7 +2574,7 @@ class PlatformSelector(Button):
self._open_picker()
def _open_picker(self):
dialog = TreeOptionDialog(
dialog = PickerDialog(
tr("Pick your vehicle"),
self.selection.picker_folders(),
search_prompt=tr("Search make or model"),
@@ -2600,16 +2600,16 @@ class PlatformSelector(Button):
_LEGEND = (
(FingerprintStatus.AUTO, lambda: tr("Fingerprinted automatically")),
(FingerprintStatus.FORCED, lambda: tr("Manually selected fingerprint")),
(FingerprintStatus.NONE, lambda: tr("Not fingerprinted or manually selected")),
(FingerprintStatus.AUTO, lambda: tr("Detected automatically")),
(FingerprintStatus.FORCED, lambda: tr("Chosen manually")),
(FingerprintStatus.NONE, lambda: tr("Undetected and unset")),
)
class LegendWidget(Widget):
"""Explains the fingerprint status colours; the active row is highlighted."""
def __init__(self, platform_selector: PlatformSelector):
def __init__(self, platform_selector: VehiclePicker):
super().__init__()
self.set_rect(rl.Rectangle(0, 0, 0, 350))
self._selector = platform_selector
@@ -2619,9 +2619,9 @@ class LegendWidget(Widget):
def _render(self, rect):
x = rect.x + 20
y = rect.y + 20
rl.draw_text_ex(self._font, tr("Select vehicle to force fingerprint manually."), rl.Vector2(x, y), 40, 0, ink.CAPTION)
rl.draw_text_ex(self._font, tr("Pin a vehicle to skip auto-detection."), rl.Vector2(x, y), 40, 0, ink.CAPTION)
y += 80
rl.draw_text_ex(self._font, tr("Colors represent vehicle fingerprint status:"), rl.Vector2(x, y), 40, 0, ink.CAPTION)
rl.draw_text_ex(self._font, tr("Colour key for fingerprint state:"), rl.Vector2(x, y), 40, 0, ink.CAPTION)
y += 80
active = self._selector.status
@@ -2650,7 +2650,7 @@ class VehicleLayout(Widget):
super().__init__()
self._brand_settings = None
self._current_brand = None
self._platform_selector = PlatformSelector(self._on_vehicle_changed)
self._platform_selector = VehiclePicker(self._on_vehicle_changed)
self._vehicle_item = IQListItem(title=self._platform_selector.text, action_item=ButtonAction(text=tr("SELECT")),
callback=self._platform_selector._on_clicked)
self._legend_widget = LegendWidget(self._platform_selector)

View File

@@ -4,7 +4,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
import pyray as rl
from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer
from openpilot.iqpilot.ui.onroad.hud_overlays import BlindSpotIndicators
from openpilot.iqpilot.ui.onroad.hud_overlays import IQBlindSpotOverlay
class IQMiciHudRenderer(HudRenderer):
@@ -16,7 +16,7 @@ class IQMiciHudRenderer(HudRenderer):
def __init__(self):
super().__init__()
self._overlays = [BlindSpotIndicators()]
self._overlays = [IQBlindSpotOverlay()]
def _update_state(self) -> None:
super()._update_state()

View File

@@ -8,7 +8,7 @@ import pyray as rl
from openpilot.common.params import Params
from openpilot.selfdrive.ui import UI_BORDER_SIZE
from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer, BTN_SIZE, ARC_LENGTH
from openpilot.iqpilot.ui.onroad.hud_overlays import DeveloperUiRenderer
from openpilot.iqpilot.ui.onroad.hud_overlays import IQDevMetricsOverlay
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
@@ -50,7 +50,7 @@ class DriverStateRendererIQ(DriverStateRenderer):
self._toast_color: rl.Color = rl.WHITE
self._font = gui_app.font(FontWeight.SEMI_BOLD)
self.dev_ui_offset = DeveloperUiRenderer.get_bottom_dev_ui_offset()
self.dev_ui_offset = IQDevMetricsOverlay.get_bottom_dev_ui_offset()
self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", BTN_SIZE, BTN_SIZE)
self._dm_person = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_person.png", 118, 118)
self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", 118, 118)

View File

@@ -22,7 +22,7 @@ from openpilot.selfdrive.ui.mici.onroad.alert_renderer import IconSide, TURN_SIG
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.iqpilot.lib import canvas
from openpilot.system.ui.iqwidgets.lib import canvas
from iqdbc.car.volkswagen.values import VolkswagenFlags
@@ -40,13 +40,29 @@ def _speed_scale() -> float:
# Accel strip
# ============================================================================
_STRIP_WIDTH = 28
_STRIP_INSET = 14
_STRIP_CEILING = 0.85
_STRIP_EMA = 5.0
_STRIP_GO = canvas.shade(0, 245, 0, 200)
_STRIP_STOP = canvas.shade(245, 0, 0, 200)
_STRIP_ARC_SEGMENTS = 24
_STRIP_ACCEL = (33, 112, 115)
_STRIP_ACCEL_NEON = (94, 232, 236)
_STRIP_DECEL = (255, 0, 247)
_STRIP_DECEL_NEON = (255, 145, 251)
_STRIP_TIP_ALPHA = 235
_STRIP_ROOT_ALPHA = 55
_STRIP_CORE_DEPTH = 7.0
_STRIP_CORE_LAYERS = 8
_STRIP_CORE_ALPHA = 0.15
_STRIP_CORE_FLOOR = 0.3
_STRIP_CORE_TAIL = 22.0
_STRIP_HALO_SPREAD = 11.0
_STRIP_HALO_LAYERS = 11
_STRIP_HALO_ALPHA = 0.095
_STRIP_HALO_TAIL = 34.0
_STRIP_NEON_FULL = 2.0 # m/s^2 at which the edges reach full brightness
class RocketFuel:
class IQAccelBar:
def __init__(self):
self._eased = 0.0
@@ -54,6 +70,52 @@ class RocketFuel:
mag = abs(self._eased)
return 0.0 if mag == 0.0 else max(0.0, _STRIP_CEILING - 0.1 / mag)
@staticmethod
def _tint(fill, frac: float):
return canvas.shade(*fill, int(_STRIP_TIP_ALPHA + (_STRIP_ROOT_ALPHA - _STRIP_TIP_ALPHA) * frac))
@staticmethod
def _halo(center, cap: float, up: bool, neon, heat: float, tail: float) -> None:
step = int(255 * _STRIP_HALO_ALPHA * heat)
if step < 1:
return
start, end = (180.0, 360.0) if up else (0.0, 180.0)
tint = canvas.shade(*neon, step)
faded = canvas.shade(*neon, 0)
top, bottom = (tint, faded) if up else (faded, tint)
for i in range(_STRIP_HALO_LAYERS):
spread = _STRIP_HALO_SPREAD * (1.0 - i / _STRIP_HALO_LAYERS)
if spread <= 0.0:
continue
canvas.annulus(center, cap, cap + spread, start, end, _STRIP_ARC_SEGMENTS, tint)
if tail <= 0.0:
continue
run = min(tail, _STRIP_HALO_TAIL)
y = center.y if up else center.y - run
canvas.v_sweep(center.x - cap - spread, y, spread, run, top, bottom)
canvas.v_sweep(center.x + cap, y, spread, run, top, bottom)
@staticmethod
def _cap(center, cap: float, up: bool, fill, neon, heat: float, tail: float) -> None:
start, end = (180.0, 360.0) if up else (0.0, 180.0)
canvas.annulus(center, 0.0, cap, start, end, _STRIP_ARC_SEGMENTS, fill)
IQAccelBar._halo(center, cap, up, neon, heat, tail)
lit = _STRIP_CORE_FLOOR + (1.0 - _STRIP_CORE_FLOOR) * heat
core = canvas.shade(*neon, max(1, int(255 * _STRIP_CORE_ALPHA * lit)))
faded = canvas.shade(*neon, 0)
top, bottom = (core, faded) if up else (faded, core)
run = min(tail, _STRIP_CORE_TAIL)
for i in range(_STRIP_CORE_LAYERS):
depth = _STRIP_CORE_DEPTH * (1.0 - i / _STRIP_CORE_LAYERS)
if depth <= 0.0:
continue
canvas.annulus(center, max(0.0, cap - depth), cap, start, end, _STRIP_ARC_SEGMENTS, core)
if run <= 0.0:
continue
y = center.y if up else center.y - run
canvas.v_sweep(center.x - cap, y, depth, run, top, bottom)
canvas.v_sweep(center.x + cap - depth, y, depth, run, top, bottom)
def render(self, rect, sm) -> None:
if not ui_state.rocket_fuel:
return
@@ -61,9 +123,21 @@ class RocketFuel:
reach = self._reach() * rect.height / 2.0
if reach <= 0.0:
return
accelerating = self._eased > 0.0
mid = rect.y + rect.height / 2.0
top, tint = (mid - reach, _STRIP_GO) if self._eased > 0.0 else (mid, _STRIP_STOP)
canvas.slab(rect.x, top, _STRIP_WIDTH, reach, tint)
top = mid - reach if accelerating else mid
x = rect.x + _STRIP_INSET
cap = min(_STRIP_WIDTH / 2.0, reach / 2.0)
fill, neon = (_STRIP_ACCEL, _STRIP_ACCEL_NEON) if accelerating else (_STRIP_DECEL, _STRIP_DECEL_NEON)
near = cap / reach
frac_top, frac_bottom = (near, 1.0 - near) if accelerating else (1.0 - near, near)
heat = min(abs(self._eased) / _STRIP_NEON_FULL, 1.0)
canvas.v_sweep(x, top + cap, cap * 2.0, reach - cap * 2.0,
self._tint(fill, frac_top), self._tint(fill, frac_bottom))
tail = max(0.0, reach / 2.0 - cap)
self._cap(canvas.Pt(x + cap, top + cap), cap, True, self._tint(fill, frac_top), neon, heat, tail)
self._cap(canvas.Pt(x + cap, top + reach - cap), cap, False, self._tint(fill, frac_bottom), neon, heat, tail)
# ============================================================================
@@ -92,7 +166,7 @@ class _BlindSide:
canvas.stamp(tex, x, rect.y + _BS_DROP, canvas.shade(255, 255, 255, int(255 * self.glow.x)))
class BlindSpotIndicators:
class IQBlindSpotOverlay:
def __init__(self):
self._left = _BlindSide("left")
self._right = _BlindSide("right")
@@ -121,7 +195,7 @@ _MQB_CLUSTER_EXEMPT = (VolkswagenFlags.PQ | VolkswagenFlags.MLB | VolkswagenFlag
VolkswagenFlags.MEB_GEN2 | VolkswagenFlags.MQB_EVO)
class SpeedRenderer:
class IQSpeedOverlay:
def __init__(self):
self.speed: float = 0.0
self._cluster_ever_live: bool = False
@@ -285,7 +359,7 @@ def _lamp_modes(event_name: str, cs, remembered):
return left, right, None
class TurnSignalController:
class IQTurnSignalOverlay:
def __init__(self, config: TurnSignalConfig | None = None):
self._config = config or TurnSignalConfig()
self._lamps = {IconSide.left: _IndicatorLamp(IconSide.left),
@@ -702,7 +776,7 @@ def _desired_probe(sm):
return desired_lat_accel
class DeveloperUiRenderer(Widget):
class IQDevMetricsOverlay(Widget):
DEV_UI_OFF = 0
DEV_UI_RIGHT = 1
DEV_UI_BOTTOM = 2
@@ -716,7 +790,7 @@ class DeveloperUiRenderer(Widget):
@staticmethod
def get_bottom_dev_ui_offset():
return DeveloperUiRenderer.BOTTOM_BAR_HEIGHT if ui_state.developer_ui != DeveloperUiRenderer.DEV_UI_OFF else 0
return IQDevMetricsOverlay.BOTTOM_BAR_HEIGHT if ui_state.developer_ui != IQDevMetricsOverlay.DEV_UI_OFF else 0
def _update_state(self) -> None:
self.dev_ui_mode = ui_state.developer_ui
@@ -775,7 +849,7 @@ def _dim(color, alpha: float):
return canvas.with_opacity(color, 255 * alpha)
class SpeedLimitRenderer(Widget):
class IQSpeedLimitOverlay(Widget):
"""Regulatory sign, upcoming-limit preview and pre-active nudge arrows."""
def __init__(self):

View File

@@ -11,12 +11,12 @@ from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer
from openpilot.iqpilot.ui.onroad.hud_overlays import (
DeveloperUiRenderer,
IQDevMetricsOverlay,
RoadNameRenderer,
RocketFuel,
SpeedLimitRenderer,
TurnSignalController,
SpeedRenderer,
IQAccelBar,
IQSpeedLimitOverlay,
IQTurnSignalOverlay,
IQSpeedOverlay,
)
from openpilot.iqpilot.ui.onroad.nav_map_panel import NavMapPanel
from openpilot.iqpilot.ui.onroad.soft_warning import SoftWarningRenderer
@@ -28,13 +28,13 @@ ENABLE_SPLIT_NAV_MAP_PANEL = True
class IQHudRenderer(HudRenderer):
def __init__(self):
super().__init__()
self.developer_ui = DeveloperUiRenderer()
self.developer_ui = IQDevMetricsOverlay()
self.nav_map_panel = NavMapPanel()
self.road_name_renderer = RoadNameRenderer()
self.rocket_fuel = RocketFuel()
self.speed_limit_renderer = SpeedLimitRenderer()
self.turn_signal_controller = TurnSignalController()
self.speed_renderer = SpeedRenderer()
self.rocket_fuel = IQAccelBar()
self.speed_limit_renderer = IQSpeedLimitOverlay()
self.turn_signal_controller = IQTurnSignalOverlay()
self.speed_renderer = IQSpeedOverlay()
self.soft_warning_renderer = SoftWarningRenderer()
self._torque_bar = TorqueBar(scale=3.0, always=True)
@@ -59,8 +59,8 @@ class IQHudRenderer(HudRenderer):
if ui_state.torque_bar:
torque_rect = rect
if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH):
torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - DeveloperUiRenderer.BOTTOM_BAR_HEIGHT)
if ui_state.developer_ui in (IQDevMetricsOverlay.DEV_UI_BOTTOM, IQDevMetricsOverlay.DEV_UI_BOTH):
torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - IQDevMetricsOverlay.BOTTOM_BAR_HEIGHT)
self._torque_bar.render(torque_rect)
if not self.split_nav_enabled():