diff --git a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py index 5f979b597..55b28788a 100644 --- a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py +++ b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py @@ -17,14 +17,24 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import _SettingsPag from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import ( AETHER_LIST_METRICS, + COMPACT_PANEL_METRICS, + AetherAdjustorRow, + AetherSegmentedControl, AetherSliderDialog, DEFAULT_PANEL_STYLE, + PanelManagerView, ParentToggle, SettingRow, SettingSection, AetherSettingsView, TileGrid, HubTile, + ToggleTile, + draw_section_header, + draw_list_group_shell, + SECTION_GAP, + SECTION_HEADER_HEIGHT, + SECTION_HEADER_GAP, hex_to_color, ) @@ -201,6 +211,334 @@ class LongitudinalManagerView(AetherSettingsView): self._main_grid.render(rl.Rectangle(rect.x, y, width, rect.height)) +class ConditionalDriveModeView(PanelManagerView): + METRICS = replace( + COMPACT_PANEL_METRICS, + outer_margin_y=14, + panel_padding_top=16, + panel_padding_bottom=14, + header_height=0, + ) + TAB_HEIGHT = 68 + TAB_BOTTOM_GAP = 18 + + @property + def vertical_scrolling_disabled(self) -> bool: + return True + + def __init__(self, controller: StarPilotLongitudinalLayout): + super().__init__() + self._header_title = tr("Conditional Drive Mode") + self._controller = controller + self._pressed_target: str | None = None + self._adjustor_rows: dict[str, AetherAdjustorRow] = {} + self._can_click = True + self._active_adjustor_key = None + + self._init_segmented_control() + self._init_adjustors() + self._init_toggles() + self._forward_touch_valid() + + def _forward_touch_valid(self): + pass + + def _init_segmented_control(self): + self._drive_mode_control = self._child( + AetherSegmentedControl( + [tr("OFF"), tr("Experimental"), tr("Chill")], + self._get_drive_mode_index, + self._on_drive_mode_change, + style=PANEL_STYLE, + suppress_background=True, + ) + ) + + def _get_drive_mode_index(self): + if self._controller._params.get_bool("ConditionalExperimental"): + return 1 + elif self._controller._params.get_bool("ConditionalChill"): + return 2 + return 0 + + def _on_drive_mode_change(self, idx): + if idx == 0: + self._controller._params.put_bool("ConditionalExperimental", False) + self._controller._params.put_bool("ConditionalChill", False) + elif idx == 1: + self._controller._params.put_bool("ConditionalExperimental", True) + self._controller._params.put_bool("ConditionalChill", False) + elif idx == 2: + self._controller._params.put_bool("ConditionalExperimental", False) + self._controller._params.put_bool("ConditionalChill", True) + self._update_pagination() + + def _init_toggles(self): + self._toggle_grid = TileGrid(columns=2, padding=12, force_square=True, min_tile_height=130.0) + self._child(self._toggle_grid) + self.register_page_grid(self._toggle_grid) + + cem_defs = [ + {"title": tr("Curves"), "subtitle": tr("Switch to Experimental Mode on open-road curves."), "get_state": lambda: self._controller._params.get_bool("CECurves"), "set_state": lambda v: self._controller._params.put_bool("CECurves", v)}, + {"title": tr("Curves w/ Lead"), "subtitle": tr("Switch on curves even when following a lead."), "get_state": lambda: self._controller._params.get_bool("CECurvesLead"), "set_state": lambda v: self._controller._params.put_bool("CECurvesLead", v), "is_enabled": lambda: self._controller._params.get_bool("CECurves"), "disabled_label": tr("Turn on Curves first")}, + {"title": tr("Stop Lights/Signs"), "subtitle": tr("Switch when openpilot detects a stop."), "get_state": lambda: self._controller._params.get_bool("CEStopLights"), "set_state": lambda v: self._controller._params.put_bool("CEStopLights", v)}, + {"title": tr("Lead Ahead"), "subtitle": tr("Switch when a slower/stopped vehicle is ahead."), "get_state": lambda: self._controller._params.get_bool("CELead"), "set_state": lambda v: self._controller._params.put_bool("CELead", v)}, + {"title": tr("Slower Lead"), "subtitle": tr("Switch specifically for slower leads."), "get_state": lambda: self._controller._params.get_bool("CESlowerLead"), "set_state": lambda v: self._controller._params.put_bool("CESlowerLead", v), "is_enabled": lambda: self._controller._params.get_bool("CELead"), "disabled_label": tr("Turn on Lead first")}, + {"title": tr("Stopped Lead"), "subtitle": tr("Switch specifically for stopped leads."), "get_state": lambda: self._controller._params.get_bool("CEStoppedLead"), "set_state": lambda v: self._controller._params.put_bool("CEStoppedLead", v), "is_enabled": lambda: self._controller._params.get_bool("CELead"), "disabled_label": tr("Turn on Lead first")}, + {"title": tr("Signal Lane Detect"), "subtitle": tr("Don't trigger on turn signal if lines are clear."), "get_state": lambda: self._controller._params.get_bool("CESignalLaneDetection"), "set_state": lambda v: self._controller._params.put_bool("CESignalLaneDetection", v), "is_enabled": lambda: self._controller._params.get_int("CESignalSpeed") > 0, "disabled_label": tr("Needs Turn Signal speed > 0")}, + {"title": tr("Status Widget"), "subtitle": tr("Show condition trigger on the drive screen."), "get_state": lambda: self._controller._params.get_bool("ShowCEMStatus"), "set_state": lambda v: self._controller._params.put_bool("ShowCEMStatus", v)}, + {"title": tr("Persist Exp State"), "subtitle": tr("Keep manual Experimental override through reboots."), "get_state": lambda: self._controller._params.get_bool("PersistExperimentalState"), "set_state": self._controller._set_persist_experimental_state}, + ] + + ccm_defs = [ + {"title": tr("Stable Lead Ahead"), "subtitle": tr("Switch to Chill Mode when following a steady lead."), "get_state": lambda: self._controller._params.get_bool("CCMLead"), "set_state": lambda v: self._controller._params.put_bool("CCMLead", v)}, + {"title": tr("Launch Assist"), "subtitle": tr("Temporarily switch to Chill from a stop."), "get_state": lambda: self._controller._params.get_bool("CCMLaunchAssist"), "set_state": lambda v: self._controller._params.put_bool("CCMLaunchAssist", v)}, + {"title": tr("Status Widget"), "subtitle": tr("Show condition trigger on the drive screen."), "get_state": lambda: self._controller._params.get_bool("ShowCCMStatus"), "set_state": lambda v: self._controller._params.put_bool("ShowCCMStatus", v)}, + {"title": tr("Persist Chill State"), "subtitle": tr("Keep manual Chill override through reboots."), "get_state": lambda: self._controller._params.get_bool("PersistChillState"), "set_state": self._controller._set_persist_chill_state}, + ] + + self._cem_toggle_defs = cem_defs + self._ccm_toggle_defs = ccm_defs + + self._update_pagination() + + def _update_pagination(self): + mode = self._get_drive_mode_index() + if mode == 1: + pages = [self._cem_toggle_defs[i:i+4] for i in range(0, len(self._cem_toggle_defs), 4)] + self._set_toggle_pages(pages) + elif mode == 2: + pages = [self._ccm_toggle_defs[i:i+4] for i in range(0, len(self._ccm_toggle_defs), 4)] + self._set_toggle_pages(pages) + else: + self._set_toggle_pages([]) + + def _make_toggle_tile(self, info: dict) -> ToggleTile: + kwargs = { + "title": info["title"], + "desc": info.get("subtitle", ""), + "get_state": info["get_state"], + "set_state": info["set_state"], + "bg_color": PANEL_STYLE.accent + } + if "is_enabled" in info: + kwargs["is_enabled"] = info["is_enabled"] + if "disabled_label" in info: + kwargs["disabled_label"] = info["disabled_label"] + + return ToggleTile(**kwargs) + + def _set_active_adjustor(self, key: str, active: bool): + if active: + if self._active_adjustor_key and self._active_adjustor_key != key: + old = self._adjustor_rows.get(self._active_adjustor_key) + if old: + old.reset_interaction() + self._active_adjustor_key = key + elif self._active_adjustor_key == key: + self._active_adjustor_key = None + + def _init_adjustors(self): + speed_unit = self._controller._speed_unit() + is_metric = self._controller._is_metric() + max_speed = 150.0 if is_metric else 100.0 + + specs = { + "CESpeed": {"title": tr("Below Speed"), "subtitle": tr("Switch to Experimental Mode below this speed."), "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 20, 35, 55, 75], "labels": {}, "get": lambda: float(self._controller._params.get_int("CESpeed"))}, + "CESpeedLead": {"title": tr("Speed w/ Lead"), "subtitle": tr("Switch below this speed when following a lead."), "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 20, 35, 55, 75], "labels": {}, "get": lambda: float(self._controller._params.get_int("CESpeedLead"))}, + "CESignalSpeed": {"title": tr("Turn Signal Below"), "subtitle": tr("Switch when turn signal is on below this speed."), "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 20, 35, 55, 75], "labels": {0.0: tr("Off")}, "get": lambda: float(self._controller._params.get_int("CESignalSpeed"))}, + "CEModelStopTime": {"title": tr("Predicted Stop In"), "subtitle": tr("Switch when openpilot predicts a stop within time."), "min": 0, "max": 10.0, "step": 1.0, "unit": "s", "presets": [0, 3, 5, 7, 10], "labels": {0.0: tr("Off")}, "get": lambda: float(self._controller._params.get_int("CEModelStopTime"))}, + "CCMSpeed": {"title": tr("Above Speed"), "subtitle": tr("Switch to Chill Mode on open roads above this speed."), "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 35, 55, 65, 80], "labels": {}, "get": lambda: float(self._controller._params.get_int("CCMSpeed"))}, + "CCMSpeedLead": {"title": tr("Speed w/ Lead"), "subtitle": tr("Switch when following a stable lead above this speed."), "min": 0, "max": max_speed, "step": 1.0, "unit": speed_unit, "presets": [0, 35, 55, 65, 80], "labels": {}, "get": lambda: float(self._controller._params.get_int("CCMSpeedLead"))}, + "CCMSetSpeedMargin": {"title": tr("Set Speed Margin"), "subtitle": tr("How far below set speed before Chill engages."), "min": 0, "max": 30.0 if is_metric else 15.0, "step": 1.0, "unit": speed_unit, "presets": [0, 5, 10, 15], "labels": {}, "get": lambda: float(self._controller._params.get_int("CCMSetSpeedMargin"))}, + } + + self._cem_keys = ["CESpeed", "CESpeedLead", "CESignalSpeed", "CEModelStopTime"] + self._ccm_keys = ["CCMSpeed", "CCMSpeedLead", "CCMSetSpeedMargin"] + + for key, spec in specs.items(): + adjustor = self._child(AetherAdjustorRow( + spec["title"], spec["subtitle"], spec["min"], spec["max"], spec["step"], + get_value=spec["get"], + on_change=lambda _v: None, + on_commit=None, + unit=spec["unit"], + labels=spec["labels"], + presets=spec["presets"], + is_active=lambda: False, + set_active=lambda active, k=key: self._show_slider(k) if active else None, + style=PANEL_STYLE, color=PANEL_STYLE.accent + )) + adjustor.set_touch_valid_callback(lambda: self._scroll_panel.is_touch_valid()) + self._adjustor_rows[key] = adjustor + + def _show_slider(self, key: str): + is_metric = self._controller._is_metric() + speed_unit = self._controller._speed_unit() + max_speed = 150.0 if is_metric else 100.0 + + specs = { + "CESpeed": {"title": tr("Below Speed"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {}, "presets": [0, 20, 35, 55, 75]}, + "CESpeedLead": {"title": tr("Speed w/ Lead"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {}, "presets": [0, 20, 35, 55, 75]}, + "CESignalSpeed": {"title": tr("Turn Signal Below"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {0.0: tr("Off")}, "presets": [0, 20, 35, 55, 75]}, + "CEModelStopTime": {"title": tr("Predicted Stop In"), "min": 0, "max": 10.0, "unit": "s", "labels": {0.0: tr("Off")}, "presets": [0, 3, 5, 7, 10]}, + "CCMSpeed": {"title": tr("Above Speed"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {}, "presets": [0, 35, 55, 65, 80]}, + "CCMSpeedLead": {"title": tr("Speed w/ Lead"), "min": 0, "max": max_speed, "unit": speed_unit, "labels": {}, "presets": [0, 35, 55, 65, 80]}, + "CCMSetSpeedMargin": {"title": tr("Set Speed Margin"), "min": 0, "max": 30.0 if is_metric else 15.0, "unit": speed_unit, "labels": {}, "presets": [0, 5, 10, 15]}, + } + + spec = specs[key] + original_val = float(self._controller._params.get_int(key)) + + def on_close(res, val): + if res == DialogResult.CONFIRM: + self._controller._params.put_int(key, int(val)) + + gui_app.push_widget(AetherSliderDialog( + title=spec["title"], + min_val=float(spec["min"]), max_val=float(spec["max"]), step=1.0, + current_val=original_val, + on_close=on_close, presets=[float(p) for p in spec["presets"]], + unit=spec["unit"], labels=spec["labels"], color=PANEL_STYLE.accent + )) + + def show_event(self): + super().show_event() + self._pressed_target = None + self._can_click = True + + def hide_event(self): + super().hide_event() + self._pressed_target = None + self._can_click = True + + def _draw_header(self, rect: rl.Rectangle): + pass + + def _measure_content_height(self, content_width: float) -> float: + mode = self._get_drive_mode_index() + if mode == 0: + return self.TAB_HEIGHT + self.TAB_BOTTOM_GAP + + keys = self._cem_keys if mode == 1 else self._ccm_keys + grid = self._toggle_grid + + col_width = (content_width - SECTION_GAP) / 2 if self._uses_two_columns(content_width) else content_width + + for key in keys: + self._adjustor_rows[key].custom_row_height = None + grid._tile_height = None + + default_adjustor_h = float(AETHER_LIST_METRICS.adjustor_row_height) + left_h = len(keys) * default_adjustor_h + 16.0 + + num_tiles = 4 if self._has_pagination else len(grid.tiles) + rows = (num_tiles + 1) // 2 if self._uses_two_columns(content_width) else num_tiles + + pagination_space = 32.0 if self._has_pagination else 0.0 + tiles_h = rows * grid.min_tile_height + (rows - 1) * grid.gap + grid.gap * 2 + pagination_space + + right_h = tiles_h + + if self._uses_two_columns(content_width): + max_natural_h = max(left_h, right_h) + section_overhead = SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + + if self._scroll_rect: + available_h = self._scroll_rect.height - section_overhead - self.TAB_HEIGHT - self.TAB_BOTTOM_GAP - 6.0 + else: + available_h = max_natural_h + + max_container_h = available_h + + left_row_h = max(60.0, (max_container_h - 16.0) / max(1, len(keys))) + for key in keys: + self._adjustor_rows[key].custom_row_height = left_row_h + + self._left_container_h = max_container_h + self._tiles_container_h = max_container_h + + return self._compute_two_column_height(section_overhead + max_container_h) + self.TAB_HEIGHT + self.TAB_BOTTOM_GAP + else: + self._left_container_h = left_h + self._tiles_container_h = right_h + total = left_h + SECTION_GAP + right_h + SECTION_HEADER_HEIGHT * 2 + SECTION_HEADER_GAP * 2 + return total + self.TAB_HEIGHT + self.TAB_BOTTOM_GAP + + def _draw_scroll_content(self, rect: rl.Rectangle, content_width: float): + y = rect.y + self._scroll_offset + + header_w = content_width - AETHER_LIST_METRICS.content_right_gutter + bar_rect = rl.Rectangle(rect.x, y, header_w, self.TAB_HEIGHT) + draw_list_group_shell(bar_rect, style=PANEL_STYLE) + self._drive_mode_control.render(bar_rect) + + y += self.TAB_HEIGHT + self.TAB_BOTTOM_GAP + mode = self._get_drive_mode_index() + if mode == 0: + return + + keys = self._cem_keys if mode == 1 else self._ccm_keys + grid = self._toggle_grid + + col_width = (content_width - SECTION_GAP) / 2 if self._uses_two_columns(content_width) else content_width + + draw_section_header(rl.Rectangle(rect.x, y, col_width, SECTION_HEADER_HEIGHT), tr("Values"), style=PANEL_STYLE) + if self._uses_two_columns(content_width): + draw_section_header(rl.Rectangle(rect.x + col_width + SECTION_GAP, y, col_width, SECTION_HEADER_HEIGHT), tr("Triggers"), style=PANEL_STYLE) + + y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + + self._draw_adjustors(y, rect.x, col_width, keys) + + if self._uses_two_columns(content_width): + self._draw_two_column_tile_grid(grid, rect.x + col_width + SECTION_GAP, y, col_width, self._tiles_container_h, title=None, style=PANEL_STYLE) + else: + y += self._left_container_h + SECTION_GAP + draw_section_header(rl.Rectangle(rect.x, y, col_width, SECTION_HEADER_HEIGHT), tr("Triggers"), style=PANEL_STYLE) + y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP + self._draw_two_column_tile_grid(grid, rect.x, y, col_width, self._tiles_container_h, title=None, style=PANEL_STYLE) + + def _draw_adjustors(self, y: float, x: float, width: float, keys: list[str]): + draw_list_group_shell(rl.Rectangle(x, y, width, self._left_container_h), style=PANEL_STYLE) + current_y = y + 8 + for index, key in enumerate(keys): + adjustor = self._adjustor_rows[key] + row_h = adjustor.measure_height(width) + row_rect = rl.Rectangle(x, current_y, width, row_h) + adjustor.set_is_last(index == len(keys) - 1) + adjustor.set_parent_rect(self._scroll_rect) + adjustor.render(row_rect) + current_y += row_h + + def _get_active_elements(self): + mode = self._get_drive_mode_index() + if mode == 1: + return [self._adjustor_rows[k] for k in self._cem_keys] + [self._toggle_grid] + elif mode == 2: + return [self._adjustor_rows[k] for k in self._ccm_keys] + [self._toggle_grid] + return [] + + def _handle_mouse_press(self, mouse_pos: MousePos): + super()._handle_mouse_press(mouse_pos) + for el in self._get_active_elements(): + el._handle_mouse_press(mouse_pos) + self._drive_mode_control._handle_mouse_press(mouse_pos) + + def _handle_mouse_release(self, mouse_pos: MousePos): + for el in self._get_active_elements(): + el._handle_mouse_release(mouse_pos) + self._drive_mode_control._handle_mouse_release(mouse_pos) + super()._handle_mouse_release(mouse_pos) + + def _handle_mouse_event(self, mouse_event: MouseEvent): + super()._handle_mouse_event(mouse_event) + for el in self._get_active_elements(): + el._handle_mouse_event(mouse_event) + self._drive_mode_control._handle_mouse_event(mouse_event) + + + # ═══════════════════════════════════════════════════════════════ # StarPilotLongitudinalLayout — controller # ═══════════════════════════════════════════════════════════════ @@ -406,112 +744,6 @@ class StarPilotLongitudinalLayout(_SettingsPage): )) # ── 4. Adaptive Speed Controls Rows (CES + CSC + CCM) ── - self._conditional_experimental_rows = [ - SettingRow("PersistExp", "toggle", tr_noop("Persist Experimental State"), - subtitle=tr_noop("Keep your manual Conditional Experimental override through reboots until manually cleared."), - get_state=lambda: self._params.get_bool("PersistExperimentalState"), - set_state=self._set_persist_experimental_state, - visible=ce_on), - SettingRow("CESpeed", "value", tr_noop("Below Speed"), - subtitle=tr_noop("Switch to Experimental Mode when driving below this speed without a lead to handle low-speed situations smoothly."), - get_value=lambda: f"{self._params.get_int('CESpeed')}{self._speed_unit()}", - on_click=lambda: self._show_slider("CESpeed", 0, 150 if self._is_metric() else 100, unit=self._speed_unit()), - visible=ce_on), - SettingRow("CESpeedLead", "value", tr_noop("Speed With Lead"), - subtitle=tr_noop("Switch to Experimental Mode when driving below this speed with a lead to handle low-speed situations smoothly."), - get_value=lambda: f"{self._params.get_int('CESpeedLead')}{self._speed_unit()}", - on_click=lambda: self._show_slider("CESpeedLead", 0, 150 if self._is_metric() else 100, unit=self._speed_unit()), - visible=ce_on), - SettingRow("CECurves", "toggle", tr_noop("Curve Detected Ahead"), - subtitle=tr_noop("Switch to Experimental Mode when a curve is detected to allow the model to select an appropriate speed."), - get_state=lambda: self._params.get_bool("CECurves"), - set_state=lambda s: self._params.put_bool("CECurves", s), - visible=ce_on), - SettingRow("CECurvesLead", "toggle", tr_noop("Curves With Lead"), - subtitle=tr_noop("Switch to Experimental Mode on curves even when following a lead vehicle."), - get_state=lambda: self._params.get_bool("CECurvesLead"), - set_state=lambda s: self._params.put_bool("CECurvesLead", s), - visible=lambda: ce_on() and self._params.get_bool("CECurves")), - SettingRow("CEStopLights", "toggle", tr_noop("Detected Stop Lights/Signs"), - subtitle=tr_noop("Switch to Experimental Mode whenever the driving model predicts a stop or detects a stop sign."), - get_state=lambda: self._params.get_bool("CEStopLights"), - set_state=lambda s: self._params.put_bool("CEStopLights", s), - visible=ce_on), - SettingRow("CEModelStopTime", "value", tr_noop("Predicted Stop In"), - subtitle=tr_noop("Switch to Experimental Mode when openpilot predicts a stop within the set time."), - get_value=lambda: tr("Off") if self._params.get_int('CEModelStopTime') == 0 else f"{self._params.get_int('CEModelStopTime')}s", - on_click=lambda: self._show_slider("CEModelStopTime", 0, 10, unit="s"), - visible=lambda: ce_on() and self._params.get_bool("CEStopLights")), - SettingRow("CELead", "toggle", tr_noop("Lead Detected Ahead"), - subtitle=tr_noop("Switch to Experimental Mode when a slower or stopped vehicle is detected ahead."), - get_state=lambda: self._params.get_bool("CELead"), - set_state=lambda s: self._params.put_bool("CELead", s), - visible=ce_on), - SettingRow("CESlowerLead", "toggle", tr_noop("Slower Lead"), - subtitle=tr_noop("Switch to Experimental Mode specifically when a slower lead vehicle is detected."), - get_state=lambda: self._params.get_bool("CESlowerLead"), - set_state=lambda s: self._params.put_bool("CESlowerLead", s), - visible=ce_lead), - SettingRow("CEStoppedLead", "toggle", tr_noop("Stopped Lead"), - subtitle=tr_noop("Switch to Experimental Mode specifically when a stopped lead vehicle is detected."), - get_state=lambda: self._params.get_bool("CEStoppedLead"), - set_state=lambda s: self._params.put_bool("CEStoppedLead", s), - visible=ce_lead), - SettingRow("CESignalSpeed", "value", tr_noop("Turn Signal Below"), - subtitle=tr_noop("Switch to Experimental Mode when turn signal is on below this speed for smoother turns."), - get_value=lambda: tr("Off") if self._params.get_int('CESignalSpeed') == 0 else f"{self._params.get_int('CESignalSpeed')}{self._speed_unit()}", - on_click=lambda: self._show_slider("CESignalSpeed", 0, 150 if self._is_metric() else 100, unit=self._speed_unit()), - visible=ce_on), - SettingRow("CESignalLaneDetection", "toggle", tr_noop("Signal Lane Detection"), - subtitle=tr_noop("Do not trigger turn signal Experimental Mode if clear lane lines are detected."), - get_state=lambda: self._params.get_bool("CESignalLaneDetection"), - set_state=lambda s: self._params.put_bool("CESignalLaneDetection", s), - visible=lambda: ce_on() and self._params.get_int("CESignalSpeed") > 0), - SettingRow("ShowCEMStatus", "toggle", tr_noop("Status Widget"), - subtitle=tr_noop("Show which condition triggered Experimental Mode on the driving screen."), - get_state=lambda: self._params.get_bool("ShowCEMStatus"), - set_state=lambda s: self._params.put_bool("ShowCEMStatus", s), - visible=ce_on), - ] - - self._conditional_chill_rows = [ - SettingRow("PersistChill", "toggle", tr_noop("Persist Chill State"), - subtitle=tr_noop("Keep your manual Conditional Chill override through reboots until manually cleared."), - get_state=lambda: self._params.get_bool("PersistChillState"), - set_state=self._set_persist_chill_state, - visible=cc_on), - SettingRow("CCMSpeed", "value", tr_noop("Above Speed"), - subtitle=tr_noop("Switch to Chill Mode on open roads above this speed when no lead is detected and the car is below the set speed."), - get_value=lambda: f"{self._params.get_int('CCMSpeed')}{self._speed_unit()}", - on_click=lambda: self._show_slider("CCMSpeed", 0, 150 if self._is_metric() else 100, unit=self._speed_unit()), - visible=cc_on), - SettingRow("CCMSpeedLead", "value", tr_noop("Speed With Lead"), - subtitle=tr_noop("Switch to Chill Mode when a stable lead is being followed above this speed."), - get_value=lambda: f"{self._params.get_int('CCMSpeedLead')}{self._speed_unit()}", - on_click=lambda: self._show_slider("CCMSpeedLead", 0, 150 if self._is_metric() else 100, unit=self._speed_unit()), - visible=cc_on), - SettingRow("CCMSetSpeedMargin", "value", tr_noop("Set Speed Margin"), - subtitle=tr_noop("How far below the set speed the car must be before open-road Conditional Chill can engage."), - get_value=lambda: f"{self._params.get_int('CCMSetSpeedMargin')}{self._speed_unit()}", - on_click=lambda: self._show_slider("CCMSetSpeedMargin", 0, 30 if self._is_metric() else 15, unit=self._speed_unit()), - visible=cc_on), - SettingRow("CCMLead", "toggle", tr_noop("Stable Lead Ahead"), - subtitle=tr_noop("Switch to Chill Mode when following a steady, well-tracked lead vehicle at cruising speeds."), - get_state=lambda: self._params.get_bool("CCMLead"), - set_state=lambda s: self._params.put_bool("CCMLead", s), - visible=cc_on), - SettingRow("CCMLaunchAssist", "toggle", tr_noop("Launch Assist"), - subtitle=tr_noop("Temporarily switch to Chill Mode when starting from a stop if the planner is allowing throttle. Useful for sluggish takeoffs."), - get_state=lambda: self._params.get_bool("CCMLaunchAssist"), - set_state=lambda s: self._params.put_bool("CCMLaunchAssist", s), - visible=cc_on), - SettingRow("ShowCCMStatus", "toggle", tr_noop("Status Widget"), - subtitle=tr_noop("Show which condition triggered Chill Mode on the driving screen."), - get_state=lambda: self._params.get_bool("ShowCCMStatus"), - set_state=lambda s: self._params.put_bool("ShowCCMStatus", s), - visible=cc_on), - ] - self._curve_speed_controller_rows = [ SettingRow("ShowCSCStatus", "toggle", tr_noop("Status Widget"), subtitle=tr_noop("Show the Curve Speed Controller ambient effect on the driving screen."), @@ -665,52 +897,8 @@ class StarPilotLongitudinalLayout(_SettingsPage): pt_csc = self._make_parent("CurveSpeedController", "Curve Speed Controller", "Configure speed control on curves and reset collected calibration data.") - ce_rows = self._conditional_experimental_rows - cc_rows = self._conditional_chill_rows - cond_mode_row = SettingRow( - "ConditionalDriveMode", "value", tr_noop("Conditional Drive Mode"), - subtitle=tr_noop("Select your preferred conditional driving mode: OFF, Conditional Experimental, or Conditional Chill."), - get_value=self._get_conditional_mode_label, - on_click=self._show_conditional_mode_selector - ) - self._sub_panels["ce"] = AetherSettingsView( - self, - [ - SettingSection( - tr("Conditional Drive Mode"), - [cond_mode_row], - visible=lambda: not ce_on() and not cc_on() - ), - SettingSection( - tr("Conditional Experimental Settings"), - [cond_mode_row] + [x for x in ce_rows if x.type != "toggle"], - visible=ce_on, - column_pair="cem" - ), - SettingSection( - tr("Conditional Experimental Triggers"), - [x for x in ce_rows if x.type == "toggle"], - visible=ce_on, - column_pair="cem" - ), - SettingSection( - tr("Conditional Chill Settings"), - [cond_mode_row] + [x for x in cc_rows if x.type != "toggle"], - visible=cc_on, - column_pair="ccm" - ), - SettingSection( - tr("Conditional Chill Triggers"), - [x for x in cc_rows if x.type == "toggle"], - visible=cc_on, - column_pair="ccm" - ), - ], - header_title=tr("Conditional Drive Mode"), - header_subtitle=tr("Configure automated switching between Experimental and Chill Modes based on set conditions."), - parent_toggle=None, - panel_style=PANEL_STYLE, - ) + self._sub_panels["ce"] = ConditionalDriveModeView(self) + csc_rows = self._curve_speed_controller_rows self._sub_panels["csc"] = AetherSettingsView(