Files
IQ.Pilot/iqpilot/selfdrive/ui/mici/layouts/settings/vehicle.py
2026-09-03 18:23:24 -05:00

228 lines
9.2 KiB
Python

"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import unicodedata
import pyray as rl
from iqpilot.selfdrive.car.vehicle_catalog import load_catalog
from iqpilot.selfdrive.ui.mici.widgets.stock_button import BigButton, BigParamControl
from iqpilot.selfdrive.ui.mici.layouts.settings.iq_widgets import MappedParamToggle
from iqpilot.selfdrive.ui.ui_state import ui_state
from iqpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from iqpilot.system.ui.lib.text_measure import measure_text_cached
from iqpilot.system.ui.widgets import Widget
from iqpilot.system.ui.widgets.scroller import NavScroller
from iqpilot.system.ui.lib.multilang import tr
def _ascii_safe(text: str) -> str:
return unicodedata.normalize("NFD", text).encode("ascii", "ignore").decode("ascii")
def _load_platforms() -> dict:
try:
return load_catalog()
except Exception:
return {}
class _PickerRow(Widget):
HEIGHT = 92
def __init__(self, label: str, on_tap):
super().__init__()
self._label = label
self._on_tap = on_tap
self.set_rect(rl.Rectangle(0, 0, gui_app.width, self.HEIGHT))
self._font = gui_app.font(FontWeight.MEDIUM)
def _handle_mouse_release(self, mouse_pos: MousePos):
super()._handle_mouse_release(mouse_pos)
self._on_tap(self._label)
def _render(self, _):
color = rl.Color(255, 255, 255, 255) if self.is_pressed else rl.Color(255, 255, 255, 200)
ts = measure_text_cached(self._font, self._label, 46)
rl.draw_text_ex(self._font, self._label, rl.Vector2(self._rect.x + 44, self._rect.y + (self.HEIGHT - ts.y) / 2), 46, 0, color)
class _VerticalPicker(NavScroller):
def __init__(self, options: list[str], on_pick):
super().__init__(horizontal=False, snap_items=False, pad_start=20, pad_end=20)
self._on_pick = on_pick
rows = [_PickerRow(o, self._pick) for o in options]
for row in rows:
row.set_touch_valid_callback(lambda: self._scroller.scroll_panel.is_touch_valid())
self._scroller.add_widgets(rows)
def _pick(self, option: str):
gui_app.pop_widget()
self._on_pick(option)
class VehicleLayoutMici(NavScroller):
def __init__(self):
super().__init__()
self._platforms = _load_platforms()
self._vehicle_btn = BigButton(tr("vehicle"))
self._vehicle_btn.set_click_callback(self._on_vehicle_clicked)
self._toyota_long = BigParamControl(tr("enforce factory long."), "IQToyotaFactoryLong",
toggle_callback=self._on_toyota_long)
self._hyundai_tuning = MappedParamToggle(tr("hyundai long. tuning"), "IQHyundaiLongTune",
[tr("off"), tr("dynamic"), tr("predictive")], [0, 1, 2])
self._subaru_snag = BigParamControl(tr("creep from standstill (beta)"), "IQSubaruCreepAssist")
self._subaru_manual = BigParamControl(tr("stop and go manual brake"), "IQSubaruCreepAssistManualBrake")
self._vw_pq_hca = BigParamControl(tr("PQ HCA status 7 mode"), "pqhca5or7Toggle")
self._vw_lateral = BigParamControl(tr("lateral when cruise faulted"), "AllowLateralWhenLongUnavailable")
self._vw_mqb_acc_resume = BigParamControl(tr("MQB ACC resume"), "iqMqbAccResume")
self._vw_mqb_steering_lockout = BigParamControl(tr("MQB steering lockout"), "iqMqbSteeringLockout")
self._vw_curvature_controller = BigParamControl(tr("curvature controller"), "EnableCurvatureController")
self._vw_smooth_steer = BigParamControl(tr("smooth steering"), "EnableSmoothSteer")
self._tesla_vtb = BigParamControl(tr("virtual torque blending"), "IQTeslaTorqueBlend")
self._tesla_fsd_visualization = BigParamControl(tr("FSD visuals"), "IQTeslaFsdVisualization")
self._brand_widgets = {
"toyota": [self._toyota_long],
"hyundai": [self._hyundai_tuning],
"subaru": [self._subaru_snag, self._subaru_manual],
"volkswagen": [self._vw_pq_hca, self._vw_lateral, self._vw_mqb_acc_resume, self._vw_mqb_steering_lockout,
self._vw_curvature_controller, self._vw_smooth_steer],
"tesla": [self._tesla_vtb, self._tesla_fsd_visualization],
}
self._all_brand_widgets = [w for ws in self._brand_widgets.values() for w in ws]
self._scroller.add_widgets([self._vehicle_btn] + self._all_brand_widgets)
def _get_current_brand(self) -> str:
bundle = ui_state.params.get("CarPlatformBundle")
if bundle:
return bundle.get("brand", "")
if ui_state.CP:
return getattr(ui_state.CP, "brand", "")
return ""
def _vw_flags(self):
try:
from iqdbc.car.volkswagen.values import CAR
bundle = ui_state.params.get("CarPlatformBundle")
if bundle and (platform := bundle.get("platform")):
return CAR[platform].config.flags
if ui_state.CP:
return ui_state.CP.flags
except Exception:
pass
return 0
def _uses_vw_hca_status_toggle(self) -> bool:
from iqdbc.car.volkswagen.values import VolkswagenFlags
return bool(self._vw_flags() & (VolkswagenFlags.PQ | VolkswagenFlags.MLB))
def _is_vw_mqb(self) -> bool:
from iqdbc.car.volkswagen.values import VolkswagenFlags
flags = self._vw_flags()
return not bool(flags & (VolkswagenFlags.PQ | VolkswagenFlags.MLB | VolkswagenFlags.MEB | VolkswagenFlags.MEB_GEN2 | VolkswagenFlags.MQB_EVO))
def _is_vw_curvature_car(self) -> bool:
from iqdbc.car.volkswagen.values import VolkswagenFlags
return bool(self._vw_flags() & (VolkswagenFlags.MEB | VolkswagenFlags.MQB_EVO))
def _supports_vw_lateral_when_faulted(self) -> bool:
from iqdbc.car.volkswagen.values import VolkswagenFlags
# PQ, MEB, MQB_EVO and base MQB all implement cruiseFaultLateralMode in carstate.py.
# MLB does not.
return not bool(self._vw_flags() & VolkswagenFlags.MLB)
def _pretty_name(self, platform: str) -> str:
for name, v in self._platforms.items():
if v.get("platform") == platform:
make = v.get("make", "")
if make and name.lower().startswith(make.lower() + " "):
return name[len(make) + 1:]
return name
return _ascii_safe(platform).replace("_", " ").title()
def _vehicle_status(self) -> str:
bundle = ui_state.params.get("CarPlatformBundle")
if bundle:
name = _ascii_safe(bundle.get("name", "?"))
make = bundle.get("make", "")
if make and name.lower().startswith(make.lower() + " "):
name = name[len(make) + 1:]
return name[:1].upper() + name[1:]
if ui_state.CP and ui_state.CP.carFingerprint not in ("", "MOCK"):
return self._pretty_name(ui_state.CP.carFingerprint)
return "tap to select"
def _on_vehicle_clicked(self):
if ui_state.params.get("CarPlatformBundle"):
ui_state.params.remove("CarPlatformBundle")
self._refresh()
else:
self._open_make_picker()
def _open_make_picker(self):
makes = sorted({v.get("make", "") for v in self._platforms.values() if v.get("make")})
label_to_make = {_ascii_safe(m): m for m in makes}
gui_app.push_widget(_VerticalPicker(list(label_to_make.keys()),
lambda lbl: self._open_model_picker(label_to_make.get(lbl, ""))))
def _open_model_picker(self, make: str):
if not make:
return
prefix = make + " "
label_to_key: dict = {}
for key in sorted(p for p, v in self._platforms.items() if v.get("make") == make):
label = key[len(prefix):] if key.lower().startswith(prefix.lower()) else key
label_to_key[_ascii_safe(label)] = key
gui_app.push_widget(_VerticalPicker(list(label_to_key.keys()),
lambda lbl: self._select_platform(label_to_key.get(lbl, ""))))
def _select_platform(self, key: str):
if key and (data := self._platforms.get(key)):
ui_state.params.put("CarPlatformBundle", {**data, "name": key})
gui_app.pop_widgets_to(self)
self._refresh()
def _on_toyota_long(self, checked: bool):
if checked and ui_state.params.get_bool("AlphaLongitudinalEnabled"):
ui_state.params.put_bool("AlphaLongitudinalEnabled", False)
ui_state.params.put_bool("OnroadCycleRequested", True)
def _refresh(self):
self._vehicle_btn.set_value(self._vehicle_status())
brand = self._get_current_brand()
offroad = ui_state.is_offroad()
uses_hca_status_toggle = self._uses_vw_hca_status_toggle()
is_mqb = self._is_vw_mqb()
supports_lateral_when_faulted = self._supports_vw_lateral_when_faulted()
is_curvature_car = self._is_vw_curvature_car()
visible = set(self._brand_widgets.get(brand, []))
for w in self._all_brand_widgets:
show = w in visible
if w is self._vw_pq_hca:
show = show and uses_hca_status_toggle
elif w in (self._vw_mqb_acc_resume, self._vw_mqb_steering_lockout):
show = show and is_mqb
elif w is self._vw_lateral:
show = show and supports_lateral_when_faulted
elif w in (self._vw_curvature_controller, self._vw_smooth_steer):
show = show and is_curvature_car
w.set_visible(show)
if show:
w.refresh()
for w in (self._toyota_long, self._subaru_snag, self._subaru_manual, self._tesla_vtb, self._tesla_fsd_visualization):
w.set_enabled(offroad)
def _update_state(self):
super()._update_state()
self._vehicle_btn.set_value(self._vehicle_status())
def show_event(self):
super().show_event()
self._refresh()