From 90fbb74f933aa44a05c3d4204493a027baeb9fe1 Mon Sep 17 00:00:00 2001 From: Fufs <42976807+Fufs@users.noreply.github.com> Date: Thu, 7 May 2026 22:31:43 -0400 Subject: [PATCH] C4 UI Vehicle Panel --- .../assets/icons_mici/settings/vehicle.png | Bin 0 -> 1841 bytes .../settings/vehicle/fingerprint.png | Bin 0 -> 5995 bytes .../ui/mici/layouts/settings/developer.py | 147 +------------ .../ui/mici/layouts/settings/settings.py | 6 + selfdrive/ui/mici/layouts/settings/vehicle.py | 207 ++++++++++++++++++ 5 files changed, 214 insertions(+), 146 deletions(-) create mode 100644 selfdrive/assets/icons_mici/settings/vehicle.png create mode 100644 selfdrive/assets/icons_mici/settings/vehicle/fingerprint.png create mode 100644 selfdrive/ui/mici/layouts/settings/vehicle.py diff --git a/selfdrive/assets/icons_mici/settings/vehicle.png b/selfdrive/assets/icons_mici/settings/vehicle.png new file mode 100644 index 0000000000000000000000000000000000000000..d92384ebb055f78f18275d9f26bbc29fc2fb19de GIT binary patch literal 1841 zcmah~X*8RQ7X6Y)LJ;#cR3k~R8k(x98l{NP3LXy?Z4FItwW{WnCVCACuV|xYx5jdB zQCb&O6-wRjNr|eqs9En58l?kbXbmykytUr1_kO$|`|P#$Icu+V_W5yc{oU=9JW2}% z0DwHj$-z^?ED7k+2uUpt93PjEOr+D{waj zJ>%AWD7BiaeZJm0*B4NB9uc&7Zmzbiqb?rvbG<($N9t`lT9Svc;_Dbs2He zG=j*|_|M_&>|5aign*&0=grTp20|Ky+~ilRW7*o(QEH&4z~Yon%DqW;ml4`8l~vDf zHA2e;HRBn+hJJ>iA7RsxhDOImDuJFzU)~Xyrj!X>IqECKXaF-N>a`Jg3S6n=Fn9~x zB_X>&>V2)r5X#P=@}5b^jH}zq)hEQr>}W&Xck%_*uC|6gc~N?=W(5EON8XbXNbBqOn%%)@a}2CcGeJ zhU8drn)N4m`sM;f7>*t8`<0+Z!)?|0KYA<-!jV#3<;iUEy96Q!sNJ4(G2+=x;AnWy zZYx#2)dR-l3qc?Q%ty7EfvUpr-*kfyfj>pRM2e@DI?q-5tIa!$|7qTmw>OM#e!QBJ z9QLrJQVE;|V|sHpl*ekCj?&ac`GeOn1|P3hNkUvxQTTnk?Ze7`BdvK@uo092_!yw? zZP~xL^76;P-NYdme`M_>rNrXthag1K7bE*3$%ka{Ji&*;@3jEN4WB&TARWu z;=F-yoRrx)LcBWwLWBnAso(LIp+)mlHNi=V!G?xQhx@LSjmLx=>6fgdmHCq0)Hzt5 zT_PD&6!-+vNz{ztuNj7+cXp!+(^rx7V^PnRbhaJ=jB*zn5JA|)5fO3aP5V=84_K$} zO3`NjC}8R!isT`@Y)>=is4O#3C9tF@wBoY9$n@Fx`$U}g4)ep2VwrN;9bf(XW6_W= zT8RU$l;+PtBD!0+n9B0#fgwXy%!!t0S0H03aexaYyvVUF&~z)?Op1roW)nr%s2TfG zS<5|>Rn)A`aiXW<#T}=;;;jrHm8<~bI=0@B zFN_O_^=R2xy@8HIF_oD2QZ}#bL@G5xX6Y4Xo3!$V@gC zd%n&&;JFia0zLhNeRy81!Y%IP>#X37Nde}qO{r4|YvEip{ekLosw#Lx5U-G60W8Ie za*tJ}*$NenRaytX&4#q;osadny+QV7?cb2?SX@G8?%I15ZIESXUO?Hz4+ab6=F6$d z;Jus;Imh4`1zi>y5{Ho!n5kCYvdshR-*c~OQJXk}FLay%73Z1%$RClS@re31sHeo} z1d_ifSL-_t}y<-Wl$0jhUPFS4p3|o+PY6MI$5ItypizWR^>> z`(*Eg#5JmHj6ZQFcZ`-Yo>fc5P%PWmutsF0BU zu{~o=o30DNK+q#g&=Dx7-zQ0msm9c3gGp1M1X#c|(Ez9Y(OKk*Y4vhusZ%nqjs2-< zF+2wPiOtod?5d;nE(HeJh%_P(4W4T8KJYR?xHWx%2A418@4-_n|G@}*vUgbW?f{CT Kn?s#FE$J^JUr^xy literal 0 HcmV?d00001 diff --git a/selfdrive/assets/icons_mici/settings/vehicle/fingerprint.png b/selfdrive/assets/icons_mici/settings/vehicle/fingerprint.png new file mode 100644 index 0000000000000000000000000000000000000000..a292855036800734e90d2a87b0d71f085b56f54a GIT binary patch literal 5995 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 str: - make = ui_state.params.get("CarMake", encoding="utf-8") or "" - if make: - return make - - model = ui_state.params.get("CarModel", encoding="utf-8") or "" - if model: - return self._make_by_model.get(model, format_fingerprint_value(model.split("_", 1)[0])) - return "Select" - - def _get_selected_model_option(self) -> FingerprintModelOption | None: - model = ui_state.params.get("CarModel", encoding="utf-8") or "" - if not model: - return None - - model_name = ui_state.params.get("CarModelName", encoding="utf-8") or "" - make = ui_state.params.get("CarMake", encoding="utf-8") or self._make_by_model.get(model, "") - if make and model_name: - for option in self._models_by_make.get(make, ()): - if option.value == model and option.label == model_name: - return option - - return self._models_by_value.get(model) - - def _get_display_model(self) -> str: - selected_option = self._get_selected_model_option() - if selected_option is not None: - return selected_option.button_label - - model = ui_state.params.get("CarModel", encoding="utf-8") or "" - model_name = ui_state.params.get("CarModelName", encoding="utf-8") or "" - if model: - model_option = self._models_by_value.get(model) - if model_option is not None: - return model_option.button_label - - if model_name: - return shorten_model_label(self._get_display_make(), model_name) - if model: - return format_fingerprint_value(model) - return "Select" - - def _set_car_make(self, make: str): - ui_state.params.put("CarMake", make) - self._car_make_btn.set_value(make) - - def _set_car_model(self, model: str | FingerprintModelOption): - if isinstance(model, FingerprintModelOption): - model_option = model - model_value = model.value - model_name = model.label - else: - model_value = model - model_option = self._models_by_value.get(model_value) - model_name = model_option.label if model_option is not None else format_fingerprint_value(model_value) - - make = self._make_by_model.get(model_value) - if make is not None: - ui_state.params.put("CarMake", make) - self._car_make_btn.set_value(make) - - ui_state.params.put("CarModel", model_value) - ui_state.params.put("CarModelName", model_name) - self._car_model_btn.set_value(self._get_display_model()) - restart_needed_callback(True) - - def _open_make_selector(self): - options = list(self._make_options) - if not options: - gui_app.push_widget(BigDialog("", "No fingerprint list available")) - return - - current_make = self._get_display_make() - default_make = current_make if current_make in options else options[0] - - def on_selected(selected_make: str): - self._set_car_make(selected_make) - current_model = ui_state.params.get("CarModel", encoding="utf-8") or "" - available_models = {option.value for option in self._models_by_make.get(selected_make, ())} - if current_model not in available_models and self._models_by_make.get(selected_make): - self._set_car_model(self._models_by_make[selected_make][0]) - - self._show_option_dialog("select make", options, default_make, on_selected) - - def _open_model_selector(self): - make = self._get_display_make() - model_options = self._models_by_make.get(make, ()) - if not model_options: - gui_app.push_widget(BigDialog("", "Select a car make first")) - return - - current_model = ui_state.params.get("CarModel", encoding="utf-8") or "" - current_model_name = ui_state.params.get("CarModelName", encoding="utf-8") or "" - option_labels = [option.option_label for option in model_options] - selected_by_label = {option.option_label: option for option in model_options} - default_model = next((option.option_label for option in model_options if option.value == current_model and option.label == current_model_name), None) - if default_model is None: - default_model = next((option.option_label for option in model_options if option.value == current_model), option_labels[0]) - - def on_selected(selected_label: str): - self._set_car_model(selected_by_label[selected_label]) - - self._show_option_dialog("select model", option_labels, default_model, on_selected) - def _update_toggles(self): ui_state.update_params() @@ -286,9 +144,6 @@ class DeveloperLayoutMici(NavScroller): for key, item in self._refresh_toggles: item.set_checked(ui_state.params.get_bool(key)) - self._car_make_btn.set_value(self._get_display_make()) - self._car_model_btn.set_value(self._get_display_model()) - def _on_joystick_debug_mode(self, state: bool): ui_state.params.put_bool("JoystickDebugMode", state) ui_state.params.put_bool("LongitudinalManeuverMode", False) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 29709344a..6901e3740 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -3,6 +3,7 @@ from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.network.network_layout import NetworkLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.vehicle import VehicleLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.driving_model import DrivingModelBigButton @@ -65,6 +66,10 @@ class SettingsLayout(NavScroller): network_btn = SettingsBigButton("network", "", gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 76, 56)) network_btn.set_click_callback(lambda: gui_app.push_widget(network_panel)) + vehicle_panel = VehicleLayoutMici() + vehicle_btn = SettingsBigButton("vehicle", "", gui_app.texture("icons_mici/settings/vehicle.png", 64, 57)) + vehicle_btn.set_click_callback(lambda: gui_app.push_widget(vehicle_panel)) + visuals_panel = VisualsLayoutMici() visuals_btn = SettingsBigButton("visuals", "", gui_app.texture("icons_mici/settings/device/cameras.png", 64, 64)) visuals_btn.set_click_callback(lambda: gui_app.push_widget(visuals_panel)) @@ -85,6 +90,7 @@ class SettingsLayout(NavScroller): toggles_btn, network_btn, self._force_drive_state_btn, + vehicle_btn, device_btn, self._driving_model_btn, visuals_btn, diff --git a/selfdrive/ui/mici/layouts/settings/vehicle.py b/selfdrive/ui/mici/layouts/settings/vehicle.py new file mode 100644 index 000000000..463f19040 --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/vehicle.py @@ -0,0 +1,207 @@ +from openpilot.common.params import Params +from openpilot.system.ui.widgets.scroller import NavScroller +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigParamControl +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigMultiOptionDialog +from openpilot.selfdrive.ui.mici.layouts.settings.fingerprint_catalog import ( + FingerprintModelOption, + format_fingerprint_value, + get_fingerprint_catalog, + shorten_model_label, +) +from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr + +class FingerprintLayoutMici(NavScroller): + def __init__(self): + super().__init__() + + self._make_options, self._models_by_make, self._models_by_value, self._make_by_model = get_fingerprint_catalog() + + def force_fp_toggle_callback(checked): + + self._update_enabled(checked) + restart_needed_callback(None) + + # TODO: tr(...)? + self._force_fingerprint_toggle = BigParamControl( + "disable auto fingerprint", "ForceFingerprint", toggle_callback=force_fp_toggle_callback, + ) + self._car_make_btn = BigButton("car make", self._get_display_make()) + self._car_make_btn.set_click_callback(self._open_make_selector) + + self._car_model_btn = BigButton("car model", self._get_display_model()) + self._car_model_btn.set_click_callback(self._open_model_selector) + + # TODO: WIP + # self._car_variant_btn = BigButton("variant") + # self.car_variant_btn.set_click_callback(self._open_variant_selector()) + + self._scroller.add_widgets([ + self._force_fingerprint_toggle, + self._car_make_btn, + self._car_model_btn, + # self._car_variant_btn # TODO: WIP + ]) + + ui_state.add_offroad_transition_callback(self._update_toggles) + + def _show_option_dialog(self, title: str, options: list[str], current: str, on_selected): + dialog_holder: dict[str, BigMultiOptionDialog] = {} + + def on_confirm(): + on_selected(dialog_holder["dialog"].get_selected_option()) + + dialog = BigMultiOptionDialog(options=options, default=current, right_btn_callback=on_confirm) + dialog_holder["dialog"] = dialog + gui_app.push_widget(dialog) + + def _update_enabled(self, force_fp): + self._force_fingerprint_toggle.set_enabled(ui_state.is_offroad()) + self._car_make_btn.set_enabled((ui_state.is_offroad() and force_fp)) + self._car_model_btn.set_enabled((ui_state.is_offroad() and force_fp)) + + def _update_toggles(self): + ui_state.update_params() + + force_fp = ui_state.params.get_bool("ForceFingerprint") + + self._force_fingerprint_toggle.set_checked(force_fp) + self._car_make_btn.set_value(self._get_display_make()) + self._car_model_btn.set_value(self._get_display_model()) + + self._update_enabled(force_fp) + + + def show_event(self): + super().show_event() + + self._update_toggles() + + def _get_display_make(self) -> str: + make = ui_state.params.get("CarMake", encoding="utf-8") or "" + if make: + return make + + model = ui_state.params.get("CarModel", encoding="utf-8") or "" + if model: + return self._make_by_model.get(model, format_fingerprint_value(model.split("_", 1)[0])) + return "Select" + + def _get_selected_model_option(self) -> FingerprintModelOption | None: + model = ui_state.params.get("CarModel", encoding="utf-8") or "" + if not model: + return None + + model_name = ui_state.params.get("CarModelName", encoding="utf-8") or "" + make = ui_state.params.get("CarMake", encoding="utf-8") or self._make_by_model.get(model, "") # FIXME: Skoda is no redering fancy czech S correctly + if make and model_name: + for option in self._models_by_make.get(make, ()): + if option.value == model and option.label == model_name: + return option + + return self._models_by_value.get(model) + + def _get_display_model(self) -> str: + selected_option = self._get_selected_model_option() + if selected_option is not None: + return selected_option.button_label + + model = ui_state.params.get("CarModel", encoding="utf-8") or "" + model_name = ui_state.params.get("CarModelName", encoding="utf-8") or "" + if model: + model_option = self._models_by_value.get(model) + if model_option is not None: + return model_option.button_label + + if model_name: + return shorten_model_label(self._get_display_make(), model_name) + if model: + return format_fingerprint_value(model) + return "Select" + + def _set_car_make(self, make: str): + ui_state.params.put("CarMake", make) + self._car_make_btn.set_value(make) + + def _set_car_model(self, model: str | FingerprintModelOption): + if isinstance(model, FingerprintModelOption): + model_option = model + model_value = model.value + model_name = model.label + else: + model_value = model + model_option = self._models_by_value.get(model_value) + model_name = model_option.label if model_option is not None else format_fingerprint_value(model_value) + + make = self._make_by_model.get(model_value) + if make is not None: + ui_state.params.put("CarMake", make) + self._car_make_btn.set_value(make) + + ui_state.params.put("CarModel", model_value) + ui_state.params.put("CarModelName", model_name) + self._car_model_btn.set_value(self._get_display_model()) + restart_needed_callback(True) + + def _open_make_selector(self): + options = list(self._make_options) + if not options: + gui_app.push_widget(BigDialog("", "No fingerprint list available")) + return + + current_make = self._get_display_make() + default_make = current_make if current_make in options else options[0] + + def on_selected(selected_make: str): + self._set_car_make(selected_make) + current_model = ui_state.params.get("CarModel", encoding="utf-8") or "" + available_models = {option.value for option in self._models_by_make.get(selected_make, ())} + if current_model not in available_models and self._models_by_make.get(selected_make): + self._set_car_model(self._models_by_make[selected_make][0]) + + self._show_option_dialog("select make", options, default_make, on_selected) + + def _open_model_selector(self): + make = self._get_display_make() + model_options = self._models_by_make.get(make, ()) + if not model_options: + gui_app.push_widget(BigDialog("", "Select a car make first")) + return + + current_model = ui_state.params.get("CarModel", encoding="utf-8") or "" + current_model_name = ui_state.params.get("CarModelName", encoding="utf-8") or "" + option_labels = [option.option_label for option in model_options] + selected_by_label = {option.option_label: option for option in model_options} + default_model = next((option.option_label for option in model_options if option.value == current_model and option.label == current_model_name), None) + if default_model is None: + default_model = next((option.option_label for option in model_options if option.value == current_model), option_labels[0]) + + def on_selected(selected_label: str): + self._set_car_model(selected_by_label[selected_label]) + + self._show_option_dialog("select model", option_labels, default_model, on_selected) + +class VehicleLayoutMici(NavScroller): + def __init__(self): + super().__init__() + + self._params = Params() + + fingerprint_panel = FingerprintLayoutMici() + fingerprint_btn = BigButton("fingerprint", "",gui_app.texture("icons_mici/settings/vehicle/fingerprint.png", 58, 64)) + fingerprint_btn.set_click_callback(lambda: gui_app.push_widget(fingerprint_panel)) + + # TODO: make it reloadable without restarting ui + match ui_state.params.get("CarModel"): + case _: + vehicle_specific_widgets = tuple() + + self._scroller.add_widgets([ + fingerprint_btn, + *vehicle_specific_widgets + ]) + + self._font_medium = gui_app.font(FontWeight.MEDIUM) +