218 lines
8.6 KiB
Python
218 lines
8.6 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._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],
|
|
"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 _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()
|
|
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
|
|
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()
|