mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-30 19:33:45 +08:00
Raindrops on Roses & Whiskers on Kittens
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import Iterable
|
||||
from typing import Any
|
||||
|
||||
from openpilot.common.params import ParamKeyType, Params
|
||||
|
||||
|
||||
FAVORITE_SLOTS_PARAM = "StarPilotFavoriteSlots"
|
||||
FAVORITE_SLOT_COUNT = 3
|
||||
|
||||
|
||||
def default_favorite_slots() -> list[dict[str, Any]]:
|
||||
return [
|
||||
{"enabled": False, "show_onroad": False, "key": None, "label": ""}
|
||||
for _ in range(FAVORITE_SLOT_COUNT)
|
||||
]
|
||||
|
||||
|
||||
def _load_raw_slots(raw_slots: Any) -> list[Any]:
|
||||
if raw_slots in (None, "", b""):
|
||||
return default_favorite_slots()
|
||||
|
||||
if isinstance(raw_slots, bytes):
|
||||
raw_slots = raw_slots.decode("utf-8", errors="replace")
|
||||
|
||||
if isinstance(raw_slots, str):
|
||||
try:
|
||||
raw_slots = json.loads(raw_slots)
|
||||
except json.JSONDecodeError:
|
||||
return default_favorite_slots()
|
||||
|
||||
if isinstance(raw_slots, dict):
|
||||
raw_slots = raw_slots.get("slots", [])
|
||||
|
||||
return raw_slots if isinstance(raw_slots, list) else default_favorite_slots()
|
||||
|
||||
|
||||
def _get_key_type(params: Params, key: str):
|
||||
for getter_name in ("get_type", "get_key_type"):
|
||||
getter = getattr(params, getter_name, None)
|
||||
if getter is None:
|
||||
continue
|
||||
try:
|
||||
return getter(key)
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def is_bool_param(params: Params, key: str | None, eligible_keys: Iterable[str] | None = None) -> bool:
|
||||
if not key:
|
||||
return False
|
||||
|
||||
if eligible_keys is not None and key not in set(eligible_keys):
|
||||
return False
|
||||
|
||||
return _get_key_type(params, key) == ParamKeyType.BOOL
|
||||
|
||||
|
||||
def normalize_favorite_slots(raw_slots: Any, params: Params | None = None,
|
||||
eligible_keys: Iterable[str] | None = None) -> list[dict[str, Any]]:
|
||||
slots = default_favorite_slots()
|
||||
eligible = set(eligible_keys) if eligible_keys is not None else None
|
||||
|
||||
for idx, raw_slot in enumerate(_load_raw_slots(raw_slots)[:FAVORITE_SLOT_COUNT]):
|
||||
if not isinstance(raw_slot, dict):
|
||||
continue
|
||||
|
||||
key = raw_slot.get("key")
|
||||
if key is not None:
|
||||
key = str(key).strip() or None
|
||||
|
||||
if key and ((eligible is not None and key not in eligible) or (params is not None and not is_bool_param(params, key))):
|
||||
key = None
|
||||
|
||||
label = str(raw_slot.get("label") or "").strip()
|
||||
if len(label) > 32:
|
||||
label = label[:32].rstrip()
|
||||
|
||||
slots[idx] = {
|
||||
"enabled": bool(raw_slot.get("enabled", False)),
|
||||
"show_onroad": bool(raw_slot.get("show_onroad", False)),
|
||||
"key": key,
|
||||
"label": label if key else "",
|
||||
}
|
||||
|
||||
return slots
|
||||
|
||||
|
||||
def load_favorite_slots(params: Params | None = None, eligible_keys: Iterable[str] | None = None) -> list[dict[str, Any]]:
|
||||
params = params or Params(return_defaults=True)
|
||||
try:
|
||||
raw_slots = params.get(FAVORITE_SLOTS_PARAM)
|
||||
except Exception:
|
||||
raw_slots = None
|
||||
return normalize_favorite_slots(raw_slots, params=params, eligible_keys=eligible_keys)
|
||||
|
||||
|
||||
def save_favorite_slots(slots: list[dict[str, Any]], params: Params | None = None) -> list[dict[str, Any]]:
|
||||
params = params or Params(return_defaults=True)
|
||||
normalized = normalize_favorite_slots(slots, params=params)
|
||||
params.put(FAVORITE_SLOTS_PARAM, normalized)
|
||||
return normalized
|
||||
|
||||
|
||||
def request_starpilot_toggle_refresh(params_memory: Params | None = None) -> None:
|
||||
params_memory = params_memory or Params(memory=True)
|
||||
params_memory.put_bool("StarPilotTogglesUpdated", True)
|
||||
|
||||
|
||||
def toggle_favorite_slot(slot_index: int, params: Params | None = None, params_memory: Params | None = None) -> bool:
|
||||
if slot_index < 0 or slot_index >= FAVORITE_SLOT_COUNT:
|
||||
return False
|
||||
|
||||
params = params or Params(return_defaults=True)
|
||||
slots = load_favorite_slots(params)
|
||||
slot = slots[slot_index]
|
||||
key = slot.get("key")
|
||||
if not slot.get("enabled") or not is_bool_param(params, key):
|
||||
return False
|
||||
|
||||
next_value = not params.get_bool(key)
|
||||
put_bool = getattr(params, "put_bool_nonblocking", None) or getattr(params, "put_bool", None)
|
||||
if put_bool is None:
|
||||
return False
|
||||
|
||||
put_bool(key, next_value)
|
||||
request_starpilot_toggle_refresh(params_memory)
|
||||
return True
|
||||
@@ -137,6 +137,9 @@ BUTTON_FUNCTIONS = {
|
||||
"BOOKMARK": 8,
|
||||
"AOL_TOGGLE": 9,
|
||||
"SLC_ADOPT": 10,
|
||||
"FAVORITE_1": 11,
|
||||
"FAVORITE_2": 12,
|
||||
"FAVORITE_3": 13,
|
||||
}
|
||||
|
||||
CANCEL_BUTTON_MIGRATION_KEY = "CancelButtonControlsMigrated"
|
||||
@@ -482,6 +485,11 @@ class StarPilotVariables:
|
||||
# runtime behavior to defaults after the driver has configured them.
|
||||
return self.get_value(key, cast=float, condition=condition, respect_tuning_level=False)
|
||||
|
||||
@staticmethod
|
||||
def set_favorite_button_flags(toggle, suffix, button_control):
|
||||
for slot_number in range(1, 4):
|
||||
setattr(toggle, f"favorite_{slot_number}_via_{suffix}", button_control == BUTTON_FUNCTIONS[f"FAVORITE_{slot_number}"])
|
||||
|
||||
def _sync_stock_param(self, key, stock_key, live_value):
|
||||
try:
|
||||
live_value = float(live_value)
|
||||
@@ -876,6 +884,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_distance = distance_button_control == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_distance = toggle.openpilot_longitudinal and distance_button_control == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_distance = distance_button_control == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "distance", distance_button_control)
|
||||
|
||||
distance_button_control_long = self.get_button_function("LongDistanceButtonControl")
|
||||
toggle.experimental_mode_via_distance_long = toggle.openpilot_longitudinal and distance_button_control_long == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -887,6 +896,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_distance_long = distance_button_control_long == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_distance_long = toggle.openpilot_longitudinal and distance_button_control_long == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_distance_long = distance_button_control_long == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "distance_long", distance_button_control_long)
|
||||
|
||||
distance_button_control_very_long = self.get_button_function("VeryLongDistanceButtonControl")
|
||||
toggle.experimental_mode_via_distance_very_long = toggle.openpilot_longitudinal and distance_button_control_very_long == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -898,6 +908,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_distance_very_long = distance_button_control_very_long == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_distance_very_long = toggle.openpilot_longitudinal and distance_button_control_very_long == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_distance_very_long = distance_button_control_very_long == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "distance_very_long", distance_button_control_very_long)
|
||||
|
||||
cancel_button_control = self.get_button_function("CancelButtonControl", condition=toggle.remap_cancel_to_distance)
|
||||
toggle.experimental_mode_via_cancel = toggle.openpilot_longitudinal and cancel_button_control == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -909,6 +920,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_cancel = cancel_button_control == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_cancel = toggle.openpilot_longitudinal and cancel_button_control == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_cancel = cancel_button_control == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "cancel", cancel_button_control)
|
||||
|
||||
cancel_button_control_long = self.get_button_function("LongCancelButtonControl", condition=toggle.remap_cancel_to_distance)
|
||||
toggle.experimental_mode_via_cancel_long = toggle.openpilot_longitudinal and cancel_button_control_long == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -920,6 +932,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_cancel_long = cancel_button_control_long == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_cancel_long = toggle.openpilot_longitudinal and cancel_button_control_long == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_cancel_long = cancel_button_control_long == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "cancel_long", cancel_button_control_long)
|
||||
|
||||
cancel_button_control_very_long = self.get_button_function("VeryLongCancelButtonControl", condition=toggle.remap_cancel_to_distance)
|
||||
toggle.experimental_mode_via_cancel_very_long = toggle.openpilot_longitudinal and cancel_button_control_very_long == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -931,6 +944,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_cancel_very_long = cancel_button_control_very_long == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_cancel_very_long = toggle.openpilot_longitudinal and cancel_button_control_very_long == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_cancel_very_long = cancel_button_control_very_long == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "cancel_very_long", cancel_button_control_very_long)
|
||||
|
||||
toggle.frogsgomoo_tweak = self.get_value("FrogsGoMoosTweak", condition=toggle.openpilot_longitudinal and toggle.car_make == "toyota")
|
||||
toggle.stoppingDecelRate = 0.01 if toggle.frogsgomoo_tweak else toggle.stoppingDecelRate
|
||||
@@ -986,6 +1000,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_lkas = lkas_button_control == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_lkas = toggle.openpilot_longitudinal and lkas_button_control == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_lkas = lkas_button_control == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "lkas", lkas_button_control)
|
||||
|
||||
has_canfd_media_buttons = toggle.car_make == "hyundai" and bool(CP.flags & HyundaiFlags.CANFD)
|
||||
mode_button_control = self.get_button_function("ModeButtonControl", condition=has_canfd_media_buttons)
|
||||
@@ -998,6 +1013,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_mode = mode_button_control == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_mode = toggle.openpilot_longitudinal and mode_button_control == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_mode = mode_button_control == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "mode", mode_button_control)
|
||||
|
||||
mode_button_control_long = self.get_button_function("LongModeButtonControl", condition=has_canfd_media_buttons)
|
||||
toggle.experimental_mode_via_mode_long = toggle.openpilot_longitudinal and mode_button_control_long == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -1009,6 +1025,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_mode_long = mode_button_control_long == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_mode_long = toggle.openpilot_longitudinal and mode_button_control_long == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_mode_long = mode_button_control_long == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "mode_long", mode_button_control_long)
|
||||
|
||||
mode_button_control_very_long = self.get_button_function("VeryLongModeButtonControl", condition=has_canfd_media_buttons)
|
||||
toggle.experimental_mode_via_mode_very_long = toggle.openpilot_longitudinal and mode_button_control_very_long == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -1020,6 +1037,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_mode_very_long = mode_button_control_very_long == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_mode_very_long = toggle.openpilot_longitudinal and mode_button_control_very_long == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_mode_very_long = mode_button_control_very_long == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "mode_very_long", mode_button_control_very_long)
|
||||
|
||||
star_button_control = self.get_button_function("StarButtonControl", condition=has_canfd_media_buttons)
|
||||
toggle.experimental_mode_via_star = toggle.openpilot_longitudinal and star_button_control == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -1031,6 +1049,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_star = star_button_control == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_star = toggle.openpilot_longitudinal and star_button_control == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_star = star_button_control == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "star", star_button_control)
|
||||
|
||||
star_button_control_long = self.get_button_function("LongStarButtonControl", condition=has_canfd_media_buttons)
|
||||
toggle.experimental_mode_via_star_long = toggle.openpilot_longitudinal and star_button_control_long == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -1042,6 +1061,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_star_long = star_button_control_long == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_star_long = toggle.openpilot_longitudinal and star_button_control_long == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_star_long = star_button_control_long == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "star_long", star_button_control_long)
|
||||
|
||||
star_button_control_very_long = self.get_button_function("VeryLongStarButtonControl", condition=has_canfd_media_buttons)
|
||||
toggle.experimental_mode_via_star_very_long = toggle.openpilot_longitudinal and star_button_control_very_long == BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
@@ -1053,6 +1073,7 @@ class StarPilotVariables:
|
||||
toggle.switchback_mode_via_star_very_long = star_button_control_very_long == BUTTON_FUNCTIONS["SWITCHBACK_MODE"]
|
||||
toggle.traffic_mode_via_star_very_long = toggle.openpilot_longitudinal and star_button_control_very_long == BUTTON_FUNCTIONS["TRAFFIC_MODE"]
|
||||
toggle.bookmark_via_star_very_long = star_button_control_very_long == BUTTON_FUNCTIONS["BOOKMARK"]
|
||||
self.set_favorite_button_flags(toggle, "star_very_long", star_button_control_very_long)
|
||||
toggle.has_canfd_media_buttons = has_canfd_media_buttons
|
||||
|
||||
toggle.lock_doors_timer = self.get_value("LockDoorsTimer", cast=float, condition=(toggle.car_make == "toyota"))
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
from openpilot.common.params import ParamKeyType
|
||||
from openpilot.starpilot.common.favorite_slots import (
|
||||
FAVORITE_SLOTS_PARAM,
|
||||
default_favorite_slots,
|
||||
load_favorite_slots,
|
||||
toggle_favorite_slot,
|
||||
)
|
||||
|
||||
|
||||
class FakeParams:
|
||||
def __init__(self):
|
||||
self.store = {}
|
||||
self.types = {
|
||||
FAVORITE_SLOTS_PARAM: ParamKeyType.JSON,
|
||||
"RedneckCruise": ParamKeyType.BOOL,
|
||||
"NotBool": ParamKeyType.INT,
|
||||
}
|
||||
|
||||
def get(self, key):
|
||||
return self.store.get(key)
|
||||
|
||||
def get_bool(self, key):
|
||||
return bool(self.store.get(key, False))
|
||||
|
||||
def put(self, key, value):
|
||||
self.store[key] = value
|
||||
|
||||
def put_bool(self, key, value):
|
||||
self.store[key] = bool(value)
|
||||
|
||||
def put_bool_nonblocking(self, key, value):
|
||||
self.put_bool(key, value)
|
||||
|
||||
def get_type(self, key):
|
||||
return self.types.get(key, ParamKeyType.STRING)
|
||||
|
||||
|
||||
def test_load_favorite_slots_defaults_on_empty_payload():
|
||||
params = FakeParams()
|
||||
|
||||
assert load_favorite_slots(params) == default_favorite_slots()
|
||||
|
||||
|
||||
def test_load_favorite_slots_filters_non_bool_keys():
|
||||
params = FakeParams()
|
||||
params.put(FAVORITE_SLOTS_PARAM, [
|
||||
{"enabled": True, "show_onroad": True, "key": "NotBool", "label": "Bad"},
|
||||
{"enabled": True, "show_onroad": False, "key": "RedneckCruise", "label": "Redneck Cruise"},
|
||||
])
|
||||
|
||||
slots = load_favorite_slots(params)
|
||||
|
||||
assert slots[0]["key"] is None
|
||||
assert slots[0]["enabled"] is True
|
||||
assert slots[1]["key"] == "RedneckCruise"
|
||||
assert slots[1]["show_onroad"] is False
|
||||
|
||||
|
||||
def test_toggle_favorite_slot_ignores_disabled_slot():
|
||||
params = FakeParams()
|
||||
params.put("RedneckCruise", False)
|
||||
params.put(FAVORITE_SLOTS_PARAM, [
|
||||
{"enabled": False, "show_onroad": True, "key": "RedneckCruise", "label": "Redneck Cruise"},
|
||||
])
|
||||
|
||||
assert toggle_favorite_slot(0, params, FakeParams()) is False
|
||||
assert params.get_bool("RedneckCruise") is False
|
||||
|
||||
|
||||
def test_toggle_favorite_slot_flips_bool_and_requests_refresh():
|
||||
params = FakeParams()
|
||||
memory = FakeParams()
|
||||
params.put("RedneckCruise", False)
|
||||
params.put(FAVORITE_SLOTS_PARAM, [
|
||||
{"enabled": True, "show_onroad": False, "key": "RedneckCruise", "label": "Redneck Cruise"},
|
||||
])
|
||||
|
||||
assert toggle_favorite_slot(0, params, memory) is True
|
||||
assert params.get_bool("RedneckCruise") is True
|
||||
assert memory.get_bool("StarPilotTogglesUpdated") is True
|
||||
@@ -105,6 +105,16 @@ def test_button_function_ignores_tuning_level_gate():
|
||||
assert variables.get_button_function("LKASButtonControl") == spv.BUTTON_FUNCTIONS["AOL_TOGGLE"]
|
||||
|
||||
|
||||
def test_favorite_button_flags_map_to_three_slots():
|
||||
toggle = SimpleNamespace()
|
||||
|
||||
spv.StarPilotVariables.set_favorite_button_flags(toggle, "lkas", spv.BUTTON_FUNCTIONS["FAVORITE_2"])
|
||||
|
||||
assert toggle.favorite_1_via_lkas is False
|
||||
assert toggle.favorite_2_via_lkas is True
|
||||
assert toggle.favorite_3_via_lkas is False
|
||||
|
||||
|
||||
def test_set_speed_limit_available_on_openpilot_longitudinal():
|
||||
assert spv.set_speed_limit_available(openpilot_longitudinal=True, has_cc_long=False, pcm_cruise_speed=True) is True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user