diff --git a/panda/board/obj/body_h7.bin.signed b/panda/board/obj/body_h7.bin.signed index f5ee65ce6..e52fd2c9e 100644 Binary files a/panda/board/obj/body_h7.bin.signed and b/panda/board/obj/body_h7.bin.signed differ diff --git a/panda/board/obj/body_h7/bootstub.elf b/panda/board/obj/body_h7/bootstub.elf index efcabbb2e..73c328644 100755 Binary files a/panda/board/obj/body_h7/bootstub.elf and b/panda/board/obj/body_h7/bootstub.elf differ diff --git a/panda/board/obj/body_h7/main.bin b/panda/board/obj/body_h7/main.bin index 565a907d6..e89c5228e 100755 Binary files a/panda/board/obj/body_h7/main.bin and b/panda/board/obj/body_h7/main.bin differ diff --git a/panda/board/obj/body_h7/main.elf b/panda/board/obj/body_h7/main.elf index c381a42b8..940ee36a5 100755 Binary files a/panda/board/obj/body_h7/main.elf and b/panda/board/obj/body_h7/main.elf differ diff --git a/panda/board/obj/bootstub.body_h7.bin b/panda/board/obj/bootstub.body_h7.bin index b12510e34..b9ae1e6eb 100755 Binary files a/panda/board/obj/bootstub.body_h7.bin and b/panda/board/obj/bootstub.body_h7.bin differ diff --git a/panda/board/obj/bootstub.panda.bin b/panda/board/obj/bootstub.panda.bin index 81f65efd3..dfe4a7cad 100755 Binary files a/panda/board/obj/bootstub.panda.bin and b/panda/board/obj/bootstub.panda.bin differ diff --git a/panda/board/obj/bootstub.panda_h7.bin b/panda/board/obj/bootstub.panda_h7.bin index 584eaf301..a35aee732 100755 Binary files a/panda/board/obj/bootstub.panda_h7.bin and b/panda/board/obj/bootstub.panda_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_h7_remote.bin b/panda/board/obj/bootstub.panda_h7_remote.bin index 584eaf301..a35aee732 100755 Binary files a/panda/board/obj/bootstub.panda_h7_remote.bin and b/panda/board/obj/bootstub.panda_h7_remote.bin differ diff --git a/panda/board/obj/bootstub.panda_jungle_h7.bin b/panda/board/obj/bootstub.panda_jungle_h7.bin index 838481de5..d1a40e846 100755 Binary files a/panda/board/obj/bootstub.panda_jungle_h7.bin and b/panda/board/obj/bootstub.panda_jungle_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_remote.bin b/panda/board/obj/bootstub.panda_remote.bin index 81f65efd3..dfe4a7cad 100755 Binary files a/panda/board/obj/bootstub.panda_remote.bin and b/panda/board/obj/bootstub.panda_remote.bin differ diff --git a/panda/board/obj/gitversion.h b/panda/board/obj/gitversion.h index f10ee4cc5..79320e7ba 100644 --- a/panda/board/obj/gitversion.h +++ b/panda/board/obj/gitversion.h @@ -1,2 +1,2 @@ extern const uint8_t gitversion[19]; -const uint8_t gitversion[19] = "DEV-82619bd7-DEBUG"; +const uint8_t gitversion[19] = "DEV-277b2255-DEBUG"; diff --git a/panda/board/obj/panda.bin.signed b/panda/board/obj/panda.bin.signed index a833d4dee..095883775 100644 Binary files a/panda/board/obj/panda.bin.signed and b/panda/board/obj/panda.bin.signed differ diff --git a/panda/board/obj/panda/bootstub.elf b/panda/board/obj/panda/bootstub.elf index 7253eecc3..83d4547f0 100755 Binary files a/panda/board/obj/panda/bootstub.elf and b/panda/board/obj/panda/bootstub.elf differ diff --git a/panda/board/obj/panda/main.bin b/panda/board/obj/panda/main.bin index e141ed98d..0f1a54458 100755 Binary files a/panda/board/obj/panda/main.bin and b/panda/board/obj/panda/main.bin differ diff --git a/panda/board/obj/panda/main.elf b/panda/board/obj/panda/main.elf index 2ec1b4558..0b724e2c5 100755 Binary files a/panda/board/obj/panda/main.elf and b/panda/board/obj/panda/main.elf differ diff --git a/panda/board/obj/panda_h7.bin.signed b/panda/board/obj/panda_h7.bin.signed index 03b251481..3007ab6fb 100644 Binary files a/panda/board/obj/panda_h7.bin.signed and b/panda/board/obj/panda_h7.bin.signed differ diff --git a/panda/board/obj/panda_h7/bootstub.elf b/panda/board/obj/panda_h7/bootstub.elf index a3265a685..280fcf29c 100755 Binary files a/panda/board/obj/panda_h7/bootstub.elf and b/panda/board/obj/panda_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_h7/main.bin b/panda/board/obj/panda_h7/main.bin index 0529b1413..730439eb7 100755 Binary files a/panda/board/obj/panda_h7/main.bin and b/panda/board/obj/panda_h7/main.bin differ diff --git a/panda/board/obj/panda_h7/main.elf b/panda/board/obj/panda_h7/main.elf index cdc135157..53294827e 100755 Binary files a/panda/board/obj/panda_h7/main.elf and b/panda/board/obj/panda_h7/main.elf differ diff --git a/panda/board/obj/panda_h7_remote.bin.signed b/panda/board/obj/panda_h7_remote.bin.signed index a1817cb3e..1fba3aa66 100644 Binary files a/panda/board/obj/panda_h7_remote.bin.signed and b/panda/board/obj/panda_h7_remote.bin.signed differ diff --git a/panda/board/obj/panda_h7_remote/bootstub.elf b/panda/board/obj/panda_h7_remote/bootstub.elf index fb354cf92..d0245dbd9 100755 Binary files a/panda/board/obj/panda_h7_remote/bootstub.elf and b/panda/board/obj/panda_h7_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_h7_remote/main.bin b/panda/board/obj/panda_h7_remote/main.bin index a1a841736..4525df840 100755 Binary files a/panda/board/obj/panda_h7_remote/main.bin and b/panda/board/obj/panda_h7_remote/main.bin differ diff --git a/panda/board/obj/panda_h7_remote/main.elf b/panda/board/obj/panda_h7_remote/main.elf index 6eea34670..d25cc91c7 100755 Binary files a/panda/board/obj/panda_h7_remote/main.elf and b/panda/board/obj/panda_h7_remote/main.elf differ diff --git a/panda/board/obj/panda_jungle_h7.bin.signed b/panda/board/obj/panda_jungle_h7.bin.signed index 89ac3f97b..68b3c5560 100644 Binary files a/panda/board/obj/panda_jungle_h7.bin.signed and b/panda/board/obj/panda_jungle_h7.bin.signed differ diff --git a/panda/board/obj/panda_jungle_h7/bootstub.elf b/panda/board/obj/panda_jungle_h7/bootstub.elf index b1fd91369..52d626beb 100755 Binary files a/panda/board/obj/panda_jungle_h7/bootstub.elf and b/panda/board/obj/panda_jungle_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_jungle_h7/main.bin b/panda/board/obj/panda_jungle_h7/main.bin index f239891b6..7e9a04074 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.bin and b/panda/board/obj/panda_jungle_h7/main.bin differ diff --git a/panda/board/obj/panda_jungle_h7/main.elf b/panda/board/obj/panda_jungle_h7/main.elf index 32f43489e..cc1fdbf42 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.elf and b/panda/board/obj/panda_jungle_h7/main.elf differ diff --git a/panda/board/obj/panda_remote.bin.signed b/panda/board/obj/panda_remote.bin.signed index aa6d1b6c0..5ad16f1d7 100644 Binary files a/panda/board/obj/panda_remote.bin.signed and b/panda/board/obj/panda_remote.bin.signed differ diff --git a/panda/board/obj/panda_remote/bootstub.elf b/panda/board/obj/panda_remote/bootstub.elf index 0a6cba7df..a48c14ae6 100755 Binary files a/panda/board/obj/panda_remote/bootstub.elf and b/panda/board/obj/panda_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_remote/main.bin b/panda/board/obj/panda_remote/main.bin index bdd2a5915..7fb59ef0b 100755 Binary files a/panda/board/obj/panda_remote/main.bin and b/panda/board/obj/panda_remote/main.bin differ diff --git a/panda/board/obj/panda_remote/main.elf b/panda/board/obj/panda_remote/main.elf index ef0590bdc..5fa18e4f4 100755 Binary files a/panda/board/obj/panda_remote/main.elf and b/panda/board/obj/panda_remote/main.elf differ diff --git a/panda/board/obj/version b/panda/board/obj/version index 382bc80eb..7d2e710b5 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-82619bd7-DEBUG \ No newline at end of file +DEV-277b2255-DEBUG \ No newline at end of file diff --git a/selfdrive/ui/layouts/settings/starpilot/metro.py b/selfdrive/ui/layouts/settings/starpilot/metro.py index f38c87ce7..3302805c8 100644 --- a/selfdrive/ui/layouts/settings/starpilot/metro.py +++ b/selfdrive/ui/layouts/settings/starpilot/metro.py @@ -128,17 +128,38 @@ class ToggleTile(MetroTile): class ValueTile(MetroTile): - def __init__(self, title: str, get_value: Callable[[], str], on_click: Callable, icon_path: str | None = None, bg_color: rl.Color | str | None = None): + def __init__(self, title: str, get_value: Callable[[], str], on_click: Callable, icon_path: str | None = None, + bg_color: rl.Color | str | None = None, is_enabled: Callable[[], bool] | None = None): super().__init__(bg_color=bg_color, on_click=on_click) self.title = title self.get_value = get_value + self.is_enabled = is_enabled or (lambda: True) self._icon = gui_app.starpilot_texture(icon_path, 80, 80) if icon_path else None self._font = gui_app.font(FontWeight.BOLD) + self._active_color = self.bg_color + self._disabled_color = rl.Color(120, 120, 120, 255) + + def _enabled(self) -> bool: + return self.is_enabled() if callable(self.is_enabled) else bool(self.is_enabled) + + def _handle_mouse_press(self, mouse_pos: MousePos): + if not self._enabled(): + self._is_pressed = False + return + super()._handle_mouse_press(mouse_pos) + + def _handle_mouse_release(self, mouse_pos: MousePos): + if not self._enabled(): + self._is_pressed = False + return + super()._handle_mouse_release(mouse_pos) def _render(self, rect: rl.Rectangle): self.set_rect(rect) - r, g, b = max(0, self.bg_color.r - 20), max(0, self.bg_color.g - 20), max(0, self.bg_color.b - 20) - color = rl.Color(r, g, b, 255) if self._is_pressed else self.bg_color + enabled = self._enabled() + base_color = self._active_color if enabled else self._disabled_color + r, g, b = max(0, base_color.r - 20), max(0, base_color.g - 20), max(0, base_color.b - 20) + color = rl.Color(r, g, b, 255) if self._is_pressed and enabled else base_color rl.draw_rectangle_rounded(rect, 0.15, 10, color) self._draw_watermark(rect, self._icon) padding = 25 diff --git a/selfdrive/ui/layouts/settings/starpilot/wheel.py b/selfdrive/ui/layouts/settings/starpilot/wheel.py index 1055a8135..25f730845 100644 --- a/selfdrive/ui/layouts/settings/starpilot/wheel.py +++ b/selfdrive/ui/layouts/settings/starpilot/wheel.py @@ -29,7 +29,7 @@ class StarPilotWheelLayout(StarPilotPanel): "title": tr_noop("Remap Cancel Button"), "type": "toggle", "get_state": lambda: self._params.get_bool("RemapCancelToDistance"), - "set_state": lambda s: self._params.put_bool("RemapCancelToDistance", s), + "set_state": self._set_cancel_remap_state, "color": "#FFC40D", }, { @@ -58,17 +58,39 @@ class StarPilotWheelLayout(StarPilotPanel): "type": "value", "get_value": lambda: self._get_action_name("LKASButtonControl"), "on_click": lambda: self._show_action_picker("LKASButtonControl"), + "is_enabled": lambda: not self._lkas_locked(), "key": "LKASButtonControl", "color": "#FFC40D", }, ] self._rebuild_grid() + def _lkas_locked(self): + return self._params.get_bool("RemapCancelToDistance") + + def _force_lkas_no_action(self): + if self._params.get_int("LKASButtonControl") != 0: + self._params.put_int("LKASButtonControl", 0) + self._params_memory.put_bool("StarPilotTogglesUpdated", True) + + def _set_cancel_remap_state(self, state): + self._params.put_bool("RemapCancelToDistance", state) + if state: + self._force_lkas_no_action() + self._rebuild_grid() + def _get_action_name(self, key): + if key == "LKASButtonControl" and self._lkas_locked(): + self._force_lkas_no_action() + return ACTION_NAME_BY_ID[0] + idx = self._params.get_int(key) return ACTION_NAME_BY_ID.get(idx, ACTION_NAMES[0]) - def _get_available_actions(self): + def _get_available_actions(self, key=None): + if key == "LKASButtonControl" and self._lkas_locked(): + return [ACTION_NAME_BY_ID[0]] + cs = starpilot_state.car_state return [ option["name"] for option in ACTION_OPTIONS @@ -76,7 +98,12 @@ class StarPilotWheelLayout(StarPilotPanel): ] def _show_action_picker(self, key): - actions = self._get_available_actions() + if key == "LKASButtonControl" and self._lkas_locked(): + self._force_lkas_no_action() + self._rebuild_grid() + return + + actions = self._get_available_actions(key) current = self._get_action_name(key) if current not in actions: current = actions[0] @@ -84,6 +111,7 @@ class StarPilotWheelLayout(StarPilotPanel): def on_select(res, val): if res == DialogResult.CONFIRM: self._params.put_int(key, ACTION_IDS.get(val, 0)) + self._params_memory.put_bool("StarPilotTogglesUpdated", True) self._rebuild_grid() gui_app.set_modal_overlay(SelectionDialog(tr(key), actions, current, on_close=on_select)) @@ -91,6 +119,8 @@ class StarPilotWheelLayout(StarPilotPanel): def _rebuild_grid(self): if not self.CATEGORIES: return + if self._lkas_locked(): + self._force_lkas_no_action() if self._tile_grid is None: self._tile_grid = __import__('openpilot.selfdrive.ui.layouts.settings.starpilot.metro', fromlist=['TileGrid']).TileGrid(columns=None, padding=20) self._tile_grid.clear() @@ -110,7 +140,7 @@ class StarPilotWheelLayout(StarPilotPanel): elif tile_type == "value": from openpilot.selfdrive.ui.layouts.settings.starpilot.metro import ValueTile - tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], bg_color=cat.get("color")) + tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], bg_color=cat.get("color"), is_enabled=cat.get("is_enabled")) else: continue self._tile_grid.add_tile(tile) diff --git a/selfdrive/ui/ui b/selfdrive/ui/ui index ba6e54549..87f7f44ff 100755 Binary files a/selfdrive/ui/ui and b/selfdrive/ui/ui differ diff --git a/starpilot/system/the_pond/assets/components/tools/device_settings.js b/starpilot/system/the_pond/assets/components/tools/device_settings.js index c70ecc89a..8148f05bb 100644 --- a/starpilot/system/the_pond/assets/components/tools/device_settings.js +++ b/starpilot/system/the_pond/assets/components/tools/device_settings.js @@ -565,6 +565,13 @@ function clearSearchFilter() { scheduleSyncInputs() } +function getSettingLockReason(param) { + if (param?.key === "LKASButtonControl" && !!state.values.RemapCancelToDistance) { + return "Cancel remap requires the LKAS button to stay on No Action." + } + return "" +} + function handleSectionTabClick(sectionSlug, event) { if (!sectionSlug || sectionSlug === state.activeSectionSlug) return @@ -590,6 +597,8 @@ function renderSettingRow(p) { const isNumeric = p.ui_type === "numeric" const isChild = p.parent_key ? "ds-child-modifier" : "" + const lockReason = getSettingLockReason(p) + const isLocked = lockReason !== "" return html`
@@ -597,6 +606,7 @@ function renderSettingRow(p) {
${p.label} ${p.description ? html`
${p.description}
` : ""} + ${lockReason ? html`
Locked: ${lockReason}
` : ""} ${() => p.is_parent_toggle && state.values[p.key] ? html`
@@ -675,6 +685,7 @@ function renderSettingRow(p) { class="ds-select" id="ds-${p.key}" data-endpoint="${p.options_endpoint || ""}" + ?disabled="${isLocked}" @change="${() => updateParam(p.key, "dropdown")}"> diff --git a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json index a11a5f96f..1eaa9d2e7 100644 --- a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json +++ b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json @@ -1724,13 +1724,6 @@ "data_type": "bool", "ui_type": "toggle" }, - { - "key": "RemapCancelToDistance", - "label": "Remap Cancel To Distance", - "description": "On pedal-interceptor Bolts, remap the steering-wheel CANCEL button to distance/personality input.", - "data_type": "bool", - "ui_type": "toggle" - }, { "key": "VoltSNG", "label": "Stop-and-Go Hack", @@ -1771,6 +1764,183 @@ } ] }, + { + "name": "Wheel Controls", + "icon": "bi-controller", + "params": [ + { + "key": "RemapCancelToDistance", + "label": "Remap Cancel To Distance", + "description": "On pedal-interceptor Bolts, remap the steering-wheel CANCEL button to distance/personality input.", + "data_type": "bool", + "ui_type": "toggle" + }, + { + "key": "DistanceButtonControl", + "label": "Distance Button (Short Press)", + "description": "Action performed when the \"Distance\" button is pressed.", + "data_type": "int", + "ui_type": "dropdown", + "options": [ + { + "value": 0, + "label": "No Action" + }, + { + "value": 1, + "label": "Change Personality" + }, + { + "value": 2, + "label": "Force Coast" + }, + { + "value": 3, + "label": "Pause Steering" + }, + { + "value": 4, + "label": "Pause Accel/Brake" + }, + { + "value": 5, + "label": "Toggle Experimental" + }, + { + "value": 6, + "label": "Toggle Traffic" + }, + { + "value": 7, + "label": "Toggle Switchback" + } + ] + }, + { + "key": "LongDistanceButtonControl", + "label": "Distance Button (Long Press)", + "description": "Action performed when the \"Distance\" button is pressed for more than 0.5 seconds.", + "data_type": "int", + "ui_type": "dropdown", + "options": [ + { + "value": 0, + "label": "No Action" + }, + { + "value": 1, + "label": "Change Personality" + }, + { + "value": 2, + "label": "Force Coast" + }, + { + "value": 3, + "label": "Pause Steering" + }, + { + "value": 4, + "label": "Pause Accel/Brake" + }, + { + "value": 5, + "label": "Toggle Experimental" + }, + { + "value": 6, + "label": "Toggle Traffic" + }, + { + "value": 7, + "label": "Toggle Switchback" + } + ] + }, + { + "key": "VeryLongDistanceButtonControl", + "label": "Distance Button (Extra Long Press)", + "description": "Action performed when the \"Distance\" button is pressed for more than 2.5 seconds.", + "data_type": "int", + "ui_type": "dropdown", + "options": [ + { + "value": 0, + "label": "No Action" + }, + { + "value": 1, + "label": "Change Personality" + }, + { + "value": 2, + "label": "Force Coast" + }, + { + "value": 3, + "label": "Pause Steering" + }, + { + "value": 4, + "label": "Pause Accel/Brake" + }, + { + "value": 5, + "label": "Toggle Experimental" + }, + { + "value": 6, + "label": "Toggle Traffic" + }, + { + "value": 7, + "label": "Toggle Switchback" + } + ] + }, + { + "key": "LKASButtonControl", + "label": "LKAS Button", + "description": "Action performed when the \"LKAS\" button is pressed.", + "data_type": "int", + "ui_type": "dropdown", + "options": [ + { + "value": 0, + "label": "No Action" + }, + { + "value": 1, + "label": "Change Personality" + }, + { + "value": 2, + "label": "Force Coast" + }, + { + "value": 3, + "label": "Pause Steering" + }, + { + "value": 4, + "label": "Pause Accel/Brake" + }, + { + "value": 5, + "label": "Toggle Experimental" + }, + { + "value": 6, + "label": "Toggle Traffic" + }, + { + "value": 7, + "label": "Toggle Switchback" + } + ] + } + ] + }, { "name": "Device & Data", "icon": "bi-hdd", diff --git a/starpilot/system/the_pond/assets/components/tools/plots.js b/starpilot/system/the_pond/assets/components/tools/plots.js index c4a4aef0b..bc677e3fb 100644 --- a/starpilot/system/the_pond/assets/components/tools/plots.js +++ b/starpilot/system/the_pond/assets/components/tools/plots.js @@ -20,16 +20,16 @@ const SVG_WIDTH = 1000 const SVG_HEIGHT = 260 const ADVANCED_TERMS_KEY = "plotsShowAdvancedTerms" const QUALITY_WINDOW_SECONDS = 30 -const QUALITY_MIN_SAMPLES = 12 +const QUALITY_MIN_SAMPLES = 8 const LATERAL_QUALITY_CONFIG = { desiredKey: "desiredLateralAccel", actualKey: "actualLateralAccel", minSpeedMps: 0.5, - minDemand: 0.015, + minDemand: 0.008, allowLowDemandFallback: true, - fallbackMinSpeedMps: 1.0, - fallbackMinPeakDemand: 0.03, + fallbackMinSpeedMps: 0.5, + fallbackMinPeakDemand: 0.01, great: 0.15, good: 0.30, fair: 0.50, @@ -39,8 +39,10 @@ const LONGITUDINAL_QUALITY_CONFIG = { desiredKey: "desiredLongitudinalAccel", actualKey: "actualLongitudinalAccel", minSpeedMps: 0.0, - minDemand: 0.08, - allowLowDemandFallback: false, + minDemand: 0.05, + allowLowDemandFallback: true, + fallbackMinSpeedMps: 1.5, + fallbackMinPeakDemand: 0.04, applyPersistenceRules: true, warnError: 0.50, severeError: 0.90, @@ -114,6 +116,12 @@ function formatQualityLabel(label) { return text || "N/A" } +function signalMagnitude(sample, config) { + const desired = Math.abs(toNumber(sample?.[config.desiredKey], 0)) + const actual = Math.abs(toNumber(sample?.[config.actualKey], 0)) + return Math.max(desired, actual) +} + function computeMatchQuality(samples, config) { const safeSamples = Array.isArray(samples) ? samples : [] if (safeSamples.length < 2) { @@ -124,25 +132,22 @@ function computeMatchQuality(samples, config) { const cutoffTs = latestTs > 0 ? latestTs - QUALITY_WINDOW_SECONDS : 0 const recentSamples = safeSamples.filter((sample) => toNumber(sample?.timestamp, 0) >= cutoffTs) - const demandEligibleSamples = recentSamples.filter((sample) => { + const eligibleSignalSamples = recentSamples.filter((sample) => { const speed = Math.abs(toNumber(sample?.speed, 0)) - const desired = Math.abs(toNumber(sample?.[config.desiredKey], 0)) + const signal = signalMagnitude(sample, config) const speedOk = config.minSpeedMps <= 0 ? true : speed >= config.minSpeedMps - const demandOk = config.minDemand <= 0 ? true : desired >= config.minDemand + const demandOk = config.minDemand <= 0 ? true : signal >= config.minDemand return speedOk && demandOk }) - let eligibleSamples = demandEligibleSamples + let eligibleSamples = eligibleSignalSamples let usedLowDemandFallback = false const allowLowDemandFallback = config.allowLowDemandFallback !== false if (allowLowDemandFallback && eligibleSamples.length < QUALITY_MIN_SAMPLES && recentSamples.length >= QUALITY_MIN_SAMPLES) { const totalSpeed = recentSamples.reduce((sum, sample) => sum + Math.abs(toNumber(sample?.speed, 0)), 0) const avgSpeed = recentSamples.length > 0 ? (totalSpeed / recentSamples.length) : 0 - const peakDemand = recentSamples.reduce((peak, sample) => { - const desired = Math.abs(toNumber(sample?.[config.desiredKey], 0)) - return Math.max(peak, desired) - }, 0) + const peakDemand = recentSamples.reduce((peak, sample) => Math.max(peak, signalMagnitude(sample, config)), 0) const fallbackMinSpeed = Math.max(0, toNumber(config.fallbackMinSpeedMps, 0)) const fallbackMinPeakDemand = Math.max(0, toNumber(config.fallbackMinPeakDemand, 0)) @@ -164,7 +169,7 @@ function computeMatchQuality(samples, config) { return { label: "N/A", value: null, - detail: `Need ${QUALITY_MIN_SAMPLES} demand samples (${eligibleSamples.length} demand / ${recentSamples.length} total)`, + detail: `Need ${QUALITY_MIN_SAMPLES} signal samples (${eligibleSamples.length} signal / ${recentSamples.length} total)`, } } @@ -622,7 +627,7 @@ export function LivePlots() { ${state.error ? html`

Error: ${state.error}

` : ""} ${state.live?.lastError ? html`

Source Error: ${state.live.lastError}

` : ""}

- Match rating uses a 30-second rolling window. Lateral prefers true steering-demand moments, and longitudinal also checks how much of the window stays above error limits so brief spikes are less likely to mark "Poor." + Match rating uses a 30-second rolling window. Strong steering or accel moments are preferred, but gentler windows can still earn a rating so normal driving does not sit at N/A. Longitudinal also checks how much of the window stays above error limits so brief spikes are less likely to mark "Poor."

diff --git a/starpilot/system/the_pond/the_pond.py b/starpilot/system/the_pond/the_pond.py index 54d3096f1..20e510e63 100644 --- a/starpilot/system/the_pond/the_pond.py +++ b/starpilot/system/the_pond/the_pond.py @@ -674,6 +674,26 @@ def _safe_float(value, default=0.0): except Exception: return float(default) +def _get_param_int_value(key, default=0): + try: + raw_value = params.get(key) + if isinstance(raw_value, bytes): + raw_value = raw_value.decode("utf-8", errors="replace") + return int(float(str(raw_value or default))) + except Exception: + return int(default) + +def _enforce_cancel_remap_lkas_lock(): + if not params.get_bool("RemapCancelToDistance"): + return False + + if _get_param_int_value("LKASButtonControl", 0) == 0: + return False + + params.put("LKASButtonControl", "0") + update_starpilot_toggles() + return True + def _get_system_uptime_seconds(): try: with open("/proc/uptime", "r", encoding="utf-8") as uptime_file: @@ -2803,15 +2823,30 @@ def setup(app): metered_enabled = str_val.strip() in ("1", "true", "True") gsm_metered_apply_result = _apply_cellular_metered_setting(metered_enabled) + locked_lkas = _enforce_cancel_remap_lkas_lock() update_starpilot_toggles() response = {"message": f"Parameter '{key}' updated successfully."} + updated = {} + if key == "RemapCancelToDistance" and params.get_bool("RemapCancelToDistance"): + updated["RemapCancelToDistance"] = True + updated["LKASButtonControl"] = 0 + response["message"] = "Remap Cancel To Distance enabled. LKAS Button has been locked to No Action." + elif key == "LKASButtonControl" and params.get_bool("RemapCancelToDistance"): + updated["LKASButtonControl"] = 0 + updated["RemapCancelToDistance"] = True + response["message"] = "LKAS Button is locked to No Action while Remap Cancel To Distance is enabled." + elif locked_lkas: + updated["LKASButtonControl"] = 0 + if gsm_metered_apply_result is not None: - response["updated"] = {"GsmMetered": str_val.strip() in ("1", "true", "True")} + updated["GsmMetered"] = str_val.strip() in ("1", "true", "True") response["networkProfilesUpdated"] = gsm_metered_apply_result.get("profiles", []) warnings = gsm_metered_apply_result.get("warnings", []) if warnings: response["warning"] = " ".join(warnings) + if updated: + response["updated"] = updated return jsonify(response), 200 @@ -2819,6 +2854,7 @@ def setup(app): @app.route("/api/params/all", methods=["GET"]) def get_all_params(): + _enforce_cancel_remap_lkas_lock() allowed_keys, types = _get_param_type_info() result = {} diff --git a/starpilot/ui/qt/offroad/vehicle_settings.cc b/starpilot/ui/qt/offroad/vehicle_settings.cc index 12a43c5dd..75cd2ffed 100644 --- a/starpilot/ui/qt/offroad/vehicle_settings.cc +++ b/starpilot/ui/qt/offroad/vehicle_settings.cc @@ -305,6 +305,11 @@ StarPilotVehiclesPanel::StarPilotVehiclesPanel(StarPilotSettingsWindow *parent, QSet rebootKeys = {"RemapCancelToDistance", "TacoTuneHacks"}; for (const QString &key : rebootKeys) { QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) { + if (key == "RemapCancelToDistance" && state && params.getInt("LKASButtonControl") != 0) { + params.putInt("LKASButtonControl", 0); + updateStarPilotToggles(); + } + if (started) { if (key == "TacoTuneHacks" && state) { if (StarPilotConfirmationDialog::toggleReboot(this)) { diff --git a/starpilot/ui/qt/offroad/wheel_settings.cc b/starpilot/ui/qt/offroad/wheel_settings.cc index 7d485dbf1..f0348c31e 100644 --- a/starpilot/ui/qt/offroad/wheel_settings.cc +++ b/starpilot/ui/qt/offroad/wheel_settings.cc @@ -1,5 +1,54 @@ #include "starpilot/ui/qt/offroad/wheel_settings.h" +namespace { + +QMap getWheelFunctionsMap() { + return { + {0, QObject::tr("No Action")}, + {3, QObject::tr("Pause Steering")}, + {7, QObject::tr("Toggle \"Switchback Mode\" On/Off")}, + }; +} + +QMap getLongitudinalWheelFunctionsMap() { + return { + {1, QObject::tr("Change \"Personality Profile\"")}, + {2, QObject::tr("Force openpilot to Coast")}, + {4, QObject::tr("Pause Acceleration/Braking")}, + {5, QObject::tr("Toggle \"Experimental Mode\" On/Off")}, + {6, QObject::tr("Toggle \"Traffic Mode\" On/Off")}, + }; +} + +QMap getMergedWheelFunctionsMap() { + QMap functionsMap = getWheelFunctionsMap(); + const QMap longitudinalFunctionsMap = getLongitudinalWheelFunctionsMap(); + for (auto it = longitudinalFunctionsMap.constBegin(); it != longitudinalFunctionsMap.constEnd(); ++it) { + functionsMap[it.key()] = it.value(); + } + return functionsMap; +} + +QString getWheelFunctionLabel(Params ¶ms, const QString &key) { + const QMap functionsMap = getMergedWheelFunctionsMap(); + return functionsMap.value(params.getInt(key.toStdString()), QObject::tr("No Action")); +} + +bool lockLkasButtonIfNeeded(Params ¶ms) { + if (!params.getBool("RemapCancelToDistance")) { + return false; + } + + if (params.getInt("LKASButtonControl") != 0) { + params.putInt("LKASButtonControl", 0); + updateStarPilotToggles(); + } + + return true; +} + +} // namespace + StarPilotWheelPanel::StarPilotWheelPanel(StarPilotSettingsWindow *parent, bool forceOpen) : StarPilotListWidget(parent), parent(parent) { forceOpenDescriptions = forceOpen; @@ -11,25 +60,18 @@ StarPilotWheelPanel::StarPilotWheelPanel(StarPilotSettingsWindow *parent, bool f }; for (const auto &[param, title, desc, icon] : wheelToggles) { - QMap functionsMap { - {0, tr("No Action")}, - {3, tr("Pause Steering")}, - {7, tr("Toggle \"Switchback Mode\" On/Off")} - }; - - QMap longitudinalFunctionsMap { - {1, tr("Change \"Personality Profile\"")}, - {2, tr("Force openpilot to Coast")}, - {4, tr("Pause Acceleration/Braking")}, - {5, tr("Toggle \"Experimental Mode\" On/Off")}, - {6, tr("Toggle \"Traffic Mode\" On/Off")} - }; - ButtonControl *wheelToggle = new ButtonControl(title, tr("SELECT"), desc); - QObject::connect(wheelToggle, &ButtonControl::clicked, [functionsMap, longitudinalFunctionsMap, key = param, parent, wheelToggle, this]() mutable { + QObject::connect(wheelToggle, &ButtonControl::clicked, [key = param, parent, wheelToggle, this]() { + if (key == "LKASButtonControl" && lockLkasButtonIfNeeded(params)) { + wheelToggle->setValue(tr("No Action")); + wheelToggle->setEnabled(false); + return; + } + + QMap functionsMap = getWheelFunctionsMap(); if (parent->hasOpenpilotLongitudinal) { - QMap::const_iterator it; - for (it = longitudinalFunctionsMap.constBegin(); it != longitudinalFunctionsMap.constEnd(); ++it) { + const QMap longitudinalFunctionsMap = getLongitudinalWheelFunctionsMap(); + for (auto it = longitudinalFunctionsMap.constBegin(); it != longitudinalFunctionsMap.constEnd(); ++it) { functionsMap[it.key()] = it.value(); } } @@ -37,16 +79,17 @@ StarPilotWheelPanel::StarPilotWheelPanel(StarPilotSettingsWindow *parent, bool f QString selection = MultiOptionDialog::getSelection(tr("Select a function to assign to this button"), functionsMap.values(), functionsMap[params.getInt(key.toStdString())], this); if (!selection.isEmpty()) { params.putInt(key.toStdString(), functionsMap.key(selection)); - wheelToggle->setValue(selection); + updateStarPilotToggles(); } }); - QMap mergedFunctionsMap = functionsMap; - QMap::const_iterator it; - for (it = longitudinalFunctionsMap.constBegin(); it != longitudinalFunctionsMap.constEnd(); ++it) { - mergedFunctionsMap[it.key()] = it.value(); + + if (param == "LKASButtonControl" && lockLkasButtonIfNeeded(params)) { + wheelToggle->setValue(tr("No Action")); + wheelToggle->setEnabled(false); + } else { + wheelToggle->setValue(getWheelFunctionLabel(params, param)); } - wheelToggle->setValue(mergedFunctionsMap[params.getInt(param.toStdString())]); toggles[param] = wheelToggle; @@ -78,6 +121,16 @@ void StarPilotWheelPanel::updateToggles() { setVisible &= !parent->lkasAllowedForAOL || !(params.getBool("AlwaysOnLateral") && params.getBool("AlwaysOnLateralLKAS")); } + if (ButtonControl *wheelToggle = qobject_cast(toggle)) { + if (key == "LKASButtonControl") { + const bool lkasLocked = lockLkasButtonIfNeeded(params); + wheelToggle->setEnabled(!lkasLocked); + wheelToggle->setValue(lkasLocked ? tr("No Action") : getWheelFunctionLabel(params, key)); + } else { + wheelToggle->setValue(getWheelFunctionLabel(params, key)); + } + } + toggle->setVisible(setVisible); }