diff --git a/common/params_keys.h b/common/params_keys.h index 1ec3ddaaf..a55ffacd4 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -225,6 +225,7 @@ inline static std::unordered_map keys = { {"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}}, {"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}}, {"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}}, {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/opendbc_repo b/opendbc_repo index 028c2d8a6..da51ac8b7 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 028c2d8a6783cf7b4d9d5d058f67ff61d0f6bd17 +Subproject commit da51ac8b7b1f29eb35e795389b738e73a0e53311 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 3377ce6c6..002e01ae3 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -449,7 +449,8 @@ class DriverMonitoring: rpyCalib = [0., 0., 0.] else: highway_speed = sm['carState'].vEgo - enabled = sm['selfdriveState'].enabled + # TODO-SP: unit test to assert both control checks are always present + enabled = sm['selfdriveState'].enabled or sm['carControl'].latActive wrong_gear = sm['carState'].gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low) standstill = sm['carState'].standstill driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py index 86d9e6169..0dd12d76f 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/__init__.py @@ -23,7 +23,7 @@ class VehicleLayout(Widget): self._current_brand = None self._platform_selector = PlatformSelector(self._update_brand_settings) - self._vehicle_item = ListItemSP(title=self._platform_selector.text, action_item=ButtonAction(text=tr("Select")), + self._vehicle_item = ListItemSP(title=self._platform_selector.text, action_item=ButtonAction(text=tr("SELECT")), callback=self._platform_selector._on_clicked) self._vehicle_item.title_color = self._platform_selector.color self._legend_widget = LegendWidget(self._platform_selector) @@ -42,7 +42,7 @@ class VehicleLayout(Widget): def _update_brand_settings(self): self._vehicle_item._title = self._platform_selector.text self._vehicle_item.title_color = self._platform_selector.color - vehicle_text = tr("Remove") if ui_state.params.get("CarPlatformBundle") else tr("Select") + vehicle_text = tr("REMOVE") if ui_state.params.get("CarPlatformBundle") else tr("SELECT") self._vehicle_item.action_item.set_text(vehicle_text) brand = self.get_brand() diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py index e061a8a22..ac3d04f36 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/toyota.py @@ -5,11 +5,55 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr, tr_noop +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp + + +DESCRIPTIONS = { + 'enforce_stock_longitudinal': tr_noop( + 'sunnypilot will not take over control of gas and brakes. Factory Toyota longitudinal control will be used.' + ), +} class ToyotaSettings(BrandSettings): def __init__(self): super().__init__() + self.enforce_stock_longitudinal = toggle_item_sp( + lambda: tr("Enforce Factory Longitudinal Control"), + description=lambda: tr(DESCRIPTIONS["enforce_stock_longitudinal"]), + initial_state=ui_state.params.get_bool("ToyotaEnforceStockLongitudinal"), + callback=self._on_enable_enforce_stock_longitudinal, + enabled=lambda: not ui_state.engaged, + ) + + self.items = [self.enforce_stock_longitudinal, ] + + def _on_enable_enforce_stock_longitudinal(self, state: bool): + if state: + def confirm_callback(result: int): + if result == DialogResult.CONFIRM: + ui_state.params.put_bool("ToyotaEnforceStockLongitudinal", True) + if ui_state.params.get_bool("AlphaLongitudinalEnabled"): + ui_state.params.put_bool("AlphaLongitudinalEnabled", False) + ui_state.params.put_bool("OnroadCycleRequested", True) + else: + self.enforce_stock_longitudinal.action_item.set_state(False) + + content = (f"

{self.enforce_stock_longitudinal.title}


" + + f"

{self.enforce_stock_longitudinal.description}

") + + dlg = ConfirmDialog(content, tr("Enable"), rich=True) + gui_app.set_modal_overlay(dlg, callback=confirm_callback) + + else: + ui_state.params.put_bool("ToyotaEnforceStockLongitudinal", False) + ui_state.params.put_bool("OnroadCycleRequested", True) + def update_settings(self): pass diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index c941fa148..31fcd025a 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -106,7 +106,7 @@ class UIState(UIStateSP): @property def engaged(self) -> bool: - return self.started and self.sm["selfdriveState"].enabled + return self.started and (self.sm["selfdriveState"].enabled or self.sm["selfdriveStateSP"].mads.enabled) def is_onroad(self) -> bool: return self.started diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 83114ac55..a93f5724b 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -114,7 +114,7 @@ def initialize_params(params) -> list[dict[str, Any]]: # hyundai keys.extend([ - "HyundaiLongitudinalTuning" + "HyundaiLongitudinalTuning", ]) # subaru @@ -128,4 +128,9 @@ def initialize_params(params) -> list[dict[str, Any]]: "TeslaCoopSteering", ]) + # toyota + keys.extend([ + "ToyotaEnforceStockLongitudinal", + ]) + return [{k: params.get(k, return_default=True)} for k in keys] diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 707e0620f..bbefc3d0c 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -1037,6 +1037,10 @@ "max": 5.0, "step": 0.1 }, + "ToyotaEnforceStockLongitudinal": { + "title": "Toyota: Enforce Factory Longitudinal Control", + "description": "When enabled, sunnypilot will not take over control of gas and brakes. Factory Toyota longitudinal control will be used." + }, "TrainingVersion": { "title": "Training Version", "description": "" diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 2d7239ae6..6ba41f143 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -15,6 +15,7 @@ from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING +from openpilot.system.ui.widgets.scroller_tici import LineSeparator, LINE_COLOR, LINE_PADDING from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH @@ -179,13 +180,8 @@ class ListItemSP(ListItem): return rl.Rectangle(0, 0, 0, 0) if not self.inline: - has_description = bool(self.description) and self.description_visible - - if has_description: - action_y = item_rect.y + self._text_size.y + style.ITEM_PADDING * 3 - else: - action_y = item_rect.y + item_rect.height - style.BUTTON_HEIGHT - style.ITEM_PADDING * 1.5 - + text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) + action_y = item_rect.y + text_size.y + style.ITEM_PADDING * 3 return rl.Rectangle(item_rect.x + style.ITEM_PADDING, action_y, item_rect.width - (style.ITEM_PADDING * 2), style.BUTTON_HEIGHT) right_width = self.action_item.get_width_hint() @@ -312,3 +308,15 @@ def button_item_sp(title: str | Callable[[], str], button_text: str | Callable[[ callback: Callable | None = None, enabled: bool | Callable[[], bool] = True) -> ListItemSP: action = ButtonActionSP(text=button_text, enabled=enabled) return ListItemSP(title=title, description=description, action_item=action, callback=callback) + + +class LineSeparatorSP(LineSeparator): + def __init__(self, height: int = 1): + super().__init__() + self._rect = rl.Rectangle(0, 0, 0, height) + + def _render(self, _): + line_y = int(self._rect.y + self._rect.height // 2) + rl.draw_line(int(self._rect.x) + LINE_PADDING, line_y, + int(self._rect.x + self._rect.width) - LINE_PADDING, line_y, + LINE_COLOR)