mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-23 02:02:08 +08:00
The Fairlife Milk Out of Stock Ya'll
This commit is contained in:
@@ -253,6 +253,11 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.vEgoStarting = 0.5
|
||||
ret.vEgoStopping = 0.35
|
||||
|
||||
if candidate == CAR.HYUNDAI_ELANTRA_2021:
|
||||
ret.longitudinalActuatorDelay = 0.22
|
||||
ret.stopAccel = -1.5
|
||||
ret.stoppingDecelRate = 0.5
|
||||
|
||||
if candidate == CAR.HYUNDAI_IONIQ_6:
|
||||
ret.longitudinalActuatorDelay = 0.6
|
||||
|
||||
|
||||
@@ -704,6 +704,14 @@ class TestHyundaiFingerprint:
|
||||
assert CP.vEgoStopping == pytest.approx(0.35)
|
||||
assert CP.stoppingDecelRate == pytest.approx(0.35)
|
||||
|
||||
def test_elantra_2021_longitudinal_params_match_observed_response(self):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_ELANTRA_2021, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.22)
|
||||
assert CP.stopAccel == pytest.approx(-1.5)
|
||||
assert CP.stoppingDecelRate == pytest.approx(0.5)
|
||||
|
||||
def test_kia_niro_phev_2022_longitudinal_params_soften_final_stop_hold(self):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.KIA_NIRO_PHEV_2022, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
|
||||
@@ -18,6 +18,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
# Toyota LTA also has torque
|
||||
"TOYOTA_RAV4_TSS2_2023" = [nan, 3.0, nan]
|
||||
"TOYOTA_MATRIX_RETROFIT" = [4.05, 1.84, 0.10]
|
||||
"TOYOTA_RAV4_PRIME" = [1.7, 2.0, 0.14]
|
||||
|
||||
# Tesla angle based controllers
|
||||
"TESLA_MODEL_3" = [nan, 2.5, nan]
|
||||
|
||||
@@ -9,7 +9,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
||||
|
||||
"TOYOTA_ALPHARD_TSS2" = "TOYOTA_SIENNA"
|
||||
"TOYOTA_PRIUS_V" = "TOYOTA_PRIUS"
|
||||
"TOYOTA_RAV4_PRIME" = "TOYOTA_RAV4_TSS2"
|
||||
"TOYOTA_SIENNA_4TH_GEN" = "TOYOTA_RAV4_TSS2"
|
||||
"LEXUS_IS" = "LEXUS_NX"
|
||||
"LEXUS_CTH" = "LEXUS_NX"
|
||||
|
||||
@@ -53,6 +53,8 @@ class TestToyotaInterfaces:
|
||||
|
||||
assert default_params.lateralTuning.which() == "pid"
|
||||
assert forced_params.lateralTuning.which() == "torque"
|
||||
assert forced_params.lateralTuning.torque.latAccelFactor == pytest.approx(1.7)
|
||||
assert forced_params.lateralTuning.torque.friction == pytest.approx(0.14)
|
||||
|
||||
def test_tss2_dbc(self):
|
||||
# We make some assumptions about TSS2 platforms,
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
from enum import StrEnum
|
||||
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.starpilot.common.experimental_state import requested_experimental_mode
|
||||
|
||||
|
||||
class ModeBannerVariant(StrEnum):
|
||||
CHILL = "chill"
|
||||
EXPERIMENTAL = "experimental"
|
||||
CONDITIONAL_EXPERIMENTAL = "conditional_experimental"
|
||||
CONDITIONAL_CHILL = "conditional_chill"
|
||||
|
||||
|
||||
def get_mode_banner_variant(params, params_memory=None) -> ModeBannerVariant:
|
||||
if params.get_bool("SafeMode"):
|
||||
return ModeBannerVariant.CHILL
|
||||
if params.get_bool("ConditionalExperimental"):
|
||||
return ModeBannerVariant.CONDITIONAL_EXPERIMENTAL
|
||||
if params.get_bool("ConditionalChill"):
|
||||
return ModeBannerVariant.CONDITIONAL_CHILL
|
||||
if requested_experimental_mode(params, params_memory):
|
||||
return ModeBannerVariant.EXPERIMENTAL
|
||||
return ModeBannerVariant.CHILL
|
||||
|
||||
|
||||
def _color(red: int, green: int, blue: int, alpha: int) -> rl.Color:
|
||||
return rl.Color(red, green, blue, alpha)
|
||||
|
||||
|
||||
def _lerp_color(start: rl.Color, end: rl.Color, progress: float, alpha: int) -> rl.Color:
|
||||
progress = max(0.0, min(1.0, progress))
|
||||
return rl.Color(
|
||||
round(start.r + (end.r - start.r) * progress),
|
||||
round(start.g + (end.g - start.g) * progress),
|
||||
round(start.b + (end.b - start.b) * progress),
|
||||
alpha,
|
||||
)
|
||||
|
||||
|
||||
def _conditional_colors(variant: ModeBannerVariant, alpha: int) -> tuple[rl.Color, rl.Color, rl.Color, rl.Color]:
|
||||
blue = _color(35, 149, 255, alpha)
|
||||
mint = _color(20, 255, 171, alpha)
|
||||
orange = _color(255, 138, 22, alpha)
|
||||
red = _color(219, 56, 34, alpha)
|
||||
if variant == ModeBannerVariant.CONDITIONAL_EXPERIMENTAL:
|
||||
return blue, mint, orange, red
|
||||
return orange, red, blue, mint
|
||||
|
||||
|
||||
def mode_banner_color(variant: ModeBannerVariant, progress: float, alpha: int = 255) -> rl.Color:
|
||||
progress = max(0.0, min(1.0, progress))
|
||||
if variant == ModeBannerVariant.CHILL:
|
||||
return _lerp_color(_color(20, 255, 171, alpha), _color(35, 149, 255, alpha), progress, alpha)
|
||||
if variant == ModeBannerVariant.EXPERIMENTAL:
|
||||
return _lerp_color(_color(255, 155, 63, alpha), _color(219, 56, 34, alpha), progress, alpha)
|
||||
|
||||
dominant_start, dominant_end, target_start, target_end = _conditional_colors(variant, alpha)
|
||||
if progress <= 0.58:
|
||||
return _lerp_color(dominant_start, dominant_end, progress / 0.58, alpha)
|
||||
if progress <= 0.80:
|
||||
return _lerp_color(dominant_end, target_start, (progress - 0.58) / 0.22, alpha)
|
||||
return _lerp_color(target_start, target_end, ((progress - 0.80) / 0.20) * 0.67, alpha)
|
||||
|
||||
|
||||
def mode_atom_color(variant: ModeBannerVariant, progress: float, alpha: int = 255) -> rl.Color:
|
||||
# The compact atom reads left-to-right as blue to mint in fixed Chill mode.
|
||||
if variant == ModeBannerVariant.CHILL:
|
||||
progress = 1.0 - progress
|
||||
return mode_banner_color(variant, progress, alpha)
|
||||
|
||||
|
||||
def draw_mode_banner_gradient(rect: rl.Rectangle, variant: ModeBannerVariant, alpha: int = 255) -> None:
|
||||
if variant in (ModeBannerVariant.CHILL, ModeBannerVariant.EXPERIMENTAL):
|
||||
rl.draw_rectangle_gradient_h(
|
||||
int(rect.x), int(rect.y), int(rect.width), int(rect.height),
|
||||
mode_banner_color(variant, 0.0, alpha), mode_banner_color(variant, 1.0, alpha),
|
||||
)
|
||||
return
|
||||
|
||||
transition_start = int(rect.x + rect.width * 0.58)
|
||||
transition_end = int(rect.x + rect.width * 0.80)
|
||||
right = int(rect.x + rect.width)
|
||||
rl.draw_rectangle_gradient_h(
|
||||
int(rect.x), int(rect.y), transition_start - int(rect.x), int(rect.height),
|
||||
mode_banner_color(variant, 0.0, alpha), mode_banner_color(variant, 0.58, alpha),
|
||||
)
|
||||
rl.draw_rectangle_gradient_h(
|
||||
transition_start, int(rect.y), transition_end - transition_start, int(rect.height),
|
||||
mode_banner_color(variant, 0.58, alpha), mode_banner_color(variant, 0.80, alpha),
|
||||
)
|
||||
rl.draw_rectangle_gradient_h(
|
||||
transition_end, int(rect.y), right - transition_end, int(rect.height),
|
||||
mode_banner_color(variant, 0.80, alpha), mode_banner_color(variant, 1.0, alpha),
|
||||
)
|
||||
@@ -5,11 +5,13 @@ import time
|
||||
from cereal import log
|
||||
import pyray as rl
|
||||
from collections.abc import Callable
|
||||
from importlib.resources import as_file
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.layouts import HBoxLayout
|
||||
from openpilot.system.ui.widgets.icon_widget import IconWidget
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.application import ASSETS_DIR, gui_app, FontWeight, MousePos
|
||||
from openpilot.selfdrive.ui.lib.mode_banner import ModeBannerVariant, get_mode_banner_variant, mode_atom_color
|
||||
from openpilot.selfdrive.ui.lib.starpilot_version import STARPILOT_DISPLAY_VERSION
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
|
||||
@@ -81,6 +83,68 @@ class NetworkIcon(Widget):
|
||||
rl.draw_texture_ex(draw_net_txt, rl.Vector2(draw_x, draw_y), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.9)))
|
||||
|
||||
|
||||
class ModeStatusAtom(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._variant = ModeBannerVariant.CHILL
|
||||
self._textures = self._make_textures()
|
||||
self.set_rect(rl.Rectangle(0, 0, 48, 48))
|
||||
self.set_enabled(False)
|
||||
self.refresh()
|
||||
|
||||
@staticmethod
|
||||
def _make_textures() -> dict[ModeBannerVariant, rl.Texture]:
|
||||
textures = {}
|
||||
with as_file(ASSETS_DIR.joinpath("icons_mici/experimental_mode.png")) as asset_path:
|
||||
source = rl.load_image(asset_path.as_posix())
|
||||
|
||||
pixel_count = source.width * source.height
|
||||
source_pixels = bytearray(rl.ffi.buffer(source.data, pixel_count * 4))
|
||||
try:
|
||||
for variant in ModeBannerVariant:
|
||||
tinted = rl.image_copy(source)
|
||||
tinted_pixels = bytearray(source_pixels)
|
||||
gradient = [mode_atom_color(variant, x / max(source.width - 1, 1)) for x in range(source.width)]
|
||||
for y in range(source.height):
|
||||
for x in range(source.width):
|
||||
offset = (y * source.width + x) * 4
|
||||
opacity = source_pixels[offset + 3]
|
||||
if opacity == 0:
|
||||
continue
|
||||
|
||||
color = gradient[x]
|
||||
source_shade = max(source_pixels[offset:offset + 3]) / 255.0
|
||||
shade = 0.65 + 0.35 * source_shade
|
||||
tinted_pixels[offset] = round(color.r * shade)
|
||||
tinted_pixels[offset + 1] = round(color.g * shade)
|
||||
tinted_pixels[offset + 2] = round(color.b * shade)
|
||||
tinted_pixels[offset + 3] = opacity
|
||||
|
||||
rl.ffi.buffer(tinted.data, len(tinted_pixels))[:] = bytes(tinted_pixels)
|
||||
texture = rl.load_texture_from_image(tinted)
|
||||
rl.set_texture_filter(texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
|
||||
rl.set_texture_wrap(texture, rl.TextureWrap.TEXTURE_WRAP_CLAMP)
|
||||
rl.unload_image(tinted)
|
||||
textures[variant] = texture
|
||||
finally:
|
||||
rl.unload_image(source)
|
||||
return textures
|
||||
|
||||
def refresh(self) -> None:
|
||||
self._variant = get_mode_banner_variant(ui_state.params, ui_state.params_memory)
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> None:
|
||||
texture = self._textures[self._variant]
|
||||
source = rl.Rectangle(0, 0, texture.width, texture.height)
|
||||
rl.draw_texture_pro(texture, source, rect, rl.Vector2(0, 0), 0, rl.WHITE)
|
||||
|
||||
def __del__(self):
|
||||
if rl.is_window_ready():
|
||||
for texture in getattr(self, "_textures", {}).values():
|
||||
if texture.id != 0:
|
||||
rl.unload_texture(texture)
|
||||
|
||||
|
||||
class MiciHomeLayout(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -95,7 +159,7 @@ class MiciHomeLayout(Widget):
|
||||
self._experimental_mode = False
|
||||
self._current_model_name = "default"
|
||||
|
||||
self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48))
|
||||
self._mode_status_atom = ModeStatusAtom()
|
||||
self._egpu_icon = IconWidget("icons_mici/egpu.png", (50, 37))
|
||||
self._egpu_icon_gray = IconWidget("icons_mici/egpu_gray.png", (50, 37))
|
||||
self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46))
|
||||
@@ -103,7 +167,7 @@ class MiciHomeLayout(Widget):
|
||||
self._status_bar_layout = HBoxLayout([
|
||||
IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9),
|
||||
NetworkIcon(),
|
||||
self._experimental_icon,
|
||||
self._mode_status_atom,
|
||||
self._egpu_icon,
|
||||
self._egpu_icon_gray,
|
||||
self._mic_icon,
|
||||
@@ -123,6 +187,7 @@ class MiciHomeLayout(Widget):
|
||||
|
||||
def _update_params(self):
|
||||
self._experimental_mode = ui_state.params.get_bool("ExperimentalMode")
|
||||
self._mode_status_atom.refresh()
|
||||
|
||||
def _clean_model_name(value: str) -> str:
|
||||
return re.sub(r"[🗺️👀📡]", "", value).replace("(Default)", "").strip()
|
||||
@@ -152,6 +217,7 @@ class MiciHomeLayout(Widget):
|
||||
if ui_state.has_longitudinal_control:
|
||||
self._experimental_mode = not self._experimental_mode
|
||||
ui_state.params.put("ExperimentalMode", self._experimental_mode)
|
||||
self._mode_status_atom.refresh()
|
||||
self._mouse_down_t = None
|
||||
self._did_long_press = True
|
||||
|
||||
@@ -214,7 +280,6 @@ class MiciHomeLayout(Widget):
|
||||
self._version_commit_label.render()
|
||||
|
||||
# ***** Center-aligned bottom section icons *****
|
||||
self._experimental_icon.set_visible(self._experimental_mode)
|
||||
self._egpu_icon.set_visible(ui_state.usbgpu_active)
|
||||
self._egpu_icon_gray.set_visible(ui_state.usbgpu and not ui_state.usbgpu_active)
|
||||
self._mic_icon.set_visible(ui_state.recording_audio)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
from openpilot.selfdrive.ui.lib.mode_banner import ModeBannerVariant, get_mode_banner_variant, mode_atom_color, mode_banner_color
|
||||
|
||||
|
||||
class FakeParams:
|
||||
def __init__(self, bools=None, ints=None):
|
||||
self.bools = bools or {}
|
||||
self.ints = ints or {}
|
||||
|
||||
def get_bool(self, key):
|
||||
return self.bools.get(key, False)
|
||||
|
||||
def get_int(self, key, default=0):
|
||||
return self.ints.get(key, default)
|
||||
|
||||
|
||||
def _rgb(color):
|
||||
return color.r, color.g, color.b
|
||||
|
||||
|
||||
def test_mode_banner_variant_tracks_fixed_and_conditional_modes():
|
||||
assert get_mode_banner_variant(FakeParams()) == ModeBannerVariant.CHILL
|
||||
assert get_mode_banner_variant(FakeParams({"ExperimentalMode": True})) == ModeBannerVariant.EXPERIMENTAL
|
||||
assert get_mode_banner_variant(FakeParams({"ConditionalExperimental": True})) == ModeBannerVariant.CONDITIONAL_EXPERIMENTAL
|
||||
assert get_mode_banner_variant(FakeParams({"ConditionalChill": True})) == ModeBannerVariant.CONDITIONAL_CHILL
|
||||
assert get_mode_banner_variant(FakeParams({"SafeMode": True, "ConditionalExperimental": True})) == ModeBannerVariant.CHILL
|
||||
|
||||
|
||||
def test_conditional_gradients_keep_full_major_and_partial_minor_colors():
|
||||
conditional_experimental = ModeBannerVariant.CONDITIONAL_EXPERIMENTAL
|
||||
assert _rgb(mode_banner_color(conditional_experimental, 0.0)) == (35, 149, 255)
|
||||
assert _rgb(mode_banner_color(conditional_experimental, 0.58)) == (20, 255, 171)
|
||||
assert _rgb(mode_banner_color(conditional_experimental, 0.80)) == (255, 138, 22)
|
||||
assert _rgb(mode_banner_color(conditional_experimental, 1.0)) == (231, 83, 30)
|
||||
|
||||
conditional_chill = ModeBannerVariant.CONDITIONAL_CHILL
|
||||
assert _rgb(mode_banner_color(conditional_chill, 0.0)) == (255, 138, 22)
|
||||
assert _rgb(mode_banner_color(conditional_chill, 0.58)) == (219, 56, 34)
|
||||
assert _rgb(mode_banner_color(conditional_chill, 0.80)) == (35, 149, 255)
|
||||
assert _rgb(mode_banner_color(conditional_chill, 1.0)) == (25, 220, 199)
|
||||
|
||||
|
||||
def test_atom_gradients_use_compact_icon_directions():
|
||||
assert _rgb(mode_atom_color(ModeBannerVariant.CHILL, 0.0)) == (35, 149, 255)
|
||||
assert _rgb(mode_atom_color(ModeBannerVariant.CHILL, 1.0)) == (20, 255, 171)
|
||||
assert _rgb(mode_atom_color(ModeBannerVariant.EXPERIMENTAL, 0.0)) == (255, 155, 63)
|
||||
assert _rgb(mode_atom_color(ModeBannerVariant.EXPERIMENTAL, 1.0)) == (219, 56, 34)
|
||||
|
||||
for variant in (ModeBannerVariant.CONDITIONAL_EXPERIMENTAL, ModeBannerVariant.CONDITIONAL_CHILL):
|
||||
assert _rgb(mode_atom_color(variant, 0.0)) == _rgb(mode_banner_color(variant, 0.0))
|
||||
assert _rgb(mode_atom_color(variant, 1.0)) == _rgb(mode_banner_color(variant, 1.0))
|
||||
@@ -4,6 +4,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.selfdrive.ui.lib.mode_banner import ModeBannerVariant, draw_mode_banner_gradient, get_mode_banner_variant
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.starpilot.common.experimental_state import requested_experimental_mode
|
||||
|
||||
@@ -18,74 +19,18 @@ class ExperimentalModeButton(Widget):
|
||||
|
||||
self.params = Params()
|
||||
self.experimental_mode = requested_experimental_mode(self.params, ui_state.params_memory)
|
||||
self.conditional_mode = self._get_conditional_mode()
|
||||
self.mode_variant = get_mode_banner_variant(self.params, ui_state.params_memory)
|
||||
|
||||
self.chill_pixmap = gui_app.texture("icons/couch.png", self.img_width, self.img_width)
|
||||
self.experimental_pixmap = gui_app.texture("icons/experimental_grey.png", self.img_width, self.img_width)
|
||||
|
||||
def show_event(self):
|
||||
self.experimental_mode = requested_experimental_mode(self.params, ui_state.params_memory)
|
||||
self.conditional_mode = self._get_conditional_mode()
|
||||
|
||||
def _get_conditional_mode(self):
|
||||
if self.params.get_bool("SafeMode"):
|
||||
return None
|
||||
if self.params.get_bool("ConditionalExperimental"):
|
||||
return "experimental"
|
||||
if self.params.get_bool("ConditionalChill"):
|
||||
return "chill"
|
||||
return None
|
||||
|
||||
def _get_gradient_colors(self):
|
||||
alpha = 0xCC if self.is_pressed else 0xFF
|
||||
|
||||
if self.experimental_mode:
|
||||
return rl.Color(255, 155, 63, alpha), rl.Color(219, 56, 34, alpha)
|
||||
else:
|
||||
return rl.Color(20, 255, 171, alpha), rl.Color(35, 149, 255, alpha)
|
||||
|
||||
def _draw_gradient_background(self, rect):
|
||||
if self.conditional_mode:
|
||||
alpha = 0xCC if self.is_pressed else 0xFF
|
||||
blue = rl.Color(35, 149, 255, alpha)
|
||||
mint = rl.Color(20, 255, 171, alpha)
|
||||
orange = rl.Color(255, 138, 22, alpha)
|
||||
red = rl.Color(219, 56, 34, alpha)
|
||||
if self.conditional_mode == "experimental":
|
||||
dominant_start, dominant_end = blue, mint
|
||||
target_start, target_end = orange, red
|
||||
else:
|
||||
dominant_start, dominant_end = orange, red
|
||||
target_start, target_end = blue, mint
|
||||
|
||||
transition_start = int(rect.x + rect.width * 0.58)
|
||||
transition_end = int(rect.x + rect.width * 0.80)
|
||||
right = int(rect.x + rect.width)
|
||||
dominant_width = transition_start - int(rect.x)
|
||||
target_width = right - transition_end
|
||||
target_progress = 0.67
|
||||
target_visible_end = rl.Color(
|
||||
round(target_start.r + (target_end.r - target_start.r) * target_progress),
|
||||
round(target_start.g + (target_end.g - target_start.g) * target_progress),
|
||||
round(target_start.b + (target_end.b - target_start.b) * target_progress),
|
||||
alpha,
|
||||
)
|
||||
|
||||
rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), dominant_width, int(rect.height),
|
||||
dominant_start, dominant_end)
|
||||
rl.draw_rectangle_gradient_h(transition_start, int(rect.y), transition_end - transition_start, int(rect.height),
|
||||
dominant_end, target_start)
|
||||
rl.draw_rectangle_gradient_h(transition_end, int(rect.y), target_width, int(rect.height),
|
||||
target_start, target_visible_end)
|
||||
return
|
||||
|
||||
start_color, end_color = self._get_gradient_colors()
|
||||
rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), int(rect.width), int(rect.height),
|
||||
start_color, end_color)
|
||||
self.mode_variant = get_mode_banner_variant(self.params, ui_state.params_memory)
|
||||
|
||||
def _render(self, rect):
|
||||
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
|
||||
self._draw_gradient_background(rect)
|
||||
draw_mode_banner_gradient(rect, self.mode_variant, 0xCC if self.is_pressed else 0xFF)
|
||||
rl.draw_rectangle_rounded_lines_ex(self._rect, 0.19, 10, 5, rl.BLACK)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
@@ -95,9 +40,9 @@ class ExperimentalModeButton(Widget):
|
||||
rl.draw_line_ex(rl.Vector2(line_x, rect.y), rl.Vector2(line_x, rect.y + rect.height), 3, separator_color)
|
||||
|
||||
# Draw text label (left aligned)
|
||||
if self.conditional_mode == "experimental":
|
||||
if self.mode_variant == ModeBannerVariant.CONDITIONAL_EXPERIMENTAL:
|
||||
text = tr("CONDITIONAL EXPERIMENTAL")
|
||||
elif self.conditional_mode == "chill":
|
||||
elif self.mode_variant == ModeBannerVariant.CONDITIONAL_CHILL:
|
||||
text = tr("CONDITIONAL CHILL")
|
||||
else:
|
||||
text = tr("EXPERIMENTAL MODE ON") if self.experimental_mode else tr("CHILL MODE ON")
|
||||
|
||||
@@ -284,13 +284,6 @@ EXCLUDED_KEYS = {
|
||||
"UptimeOffroad"
|
||||
}
|
||||
|
||||
TUNING_LEVELS = {
|
||||
"MINIMAL": 0,
|
||||
"STANDARD": 1,
|
||||
"ADVANCED": 2,
|
||||
"DEVELOPER": 3
|
||||
}
|
||||
|
||||
# Shared params handles for modules that import these from starpilot_variables.
|
||||
params = Params(return_defaults=True)
|
||||
params_memory = Params(memory=True)
|
||||
@@ -419,8 +412,6 @@ class StarPilotVariables:
|
||||
toggle = self.starpilot_toggles
|
||||
|
||||
self.default_values = {key.decode(): self.params.get_default_value(key) for key in self.params.all_keys()}
|
||||
self.tuning_levels = {key.decode(): self.params.get_tuning_level(key) for key in self.params.all_keys()}
|
||||
|
||||
branch = get_build_metadata().channel
|
||||
self.release_branch = branch == "StarPilot"
|
||||
self.staging_branch = branch == "StarPilot-Staging"
|
||||
@@ -431,8 +422,6 @@ class StarPilotVariables:
|
||||
# Development/vetting branches are no longer gated into dashcam mode.
|
||||
toggle.block_user = False
|
||||
|
||||
toggle.tuning_level = self.params.get("TuningLevel") if self.params.get_bool("TuningLevelConfirmed") else TUNING_LEVELS["ADVANCED"]
|
||||
|
||||
device_management = self.get_value("DeviceManagement")
|
||||
|
||||
toggle.use_higher_bitrate = device_management
|
||||
@@ -460,8 +449,8 @@ class StarPilotVariables:
|
||||
return f"#{color.get('alpha', 255):02X}{color.get('red', 255):02X}{color.get('green', 255):02X}{color.get('blue', 255):02X}"
|
||||
return "#FFFFFFFF"
|
||||
|
||||
def get_value(self, key, cast=bool, condition=True, conversion=None, default=None, min=None, max=None, respect_tuning_level=True):
|
||||
if not condition or (respect_tuning_level and self.starpilot_toggles.tuning_level < self.tuning_levels.get(key, 0)):
|
||||
def get_value(self, key, cast=bool, condition=True, conversion=None, default=None, min=None, max=None):
|
||||
if not condition:
|
||||
if default is not None:
|
||||
value = default
|
||||
elif cast is bool:
|
||||
@@ -509,9 +498,7 @@ class StarPilotVariables:
|
||||
return value
|
||||
|
||||
def get_button_function(self, key, condition=True):
|
||||
# Tuning level should hide wheel-mapping controls, not silently revert their
|
||||
# runtime behavior to defaults after the driver has configured them.
|
||||
return self.get_value(key, cast=float, condition=condition, respect_tuning_level=False)
|
||||
return self.get_value(key, cast=float, condition=condition)
|
||||
|
||||
def migrate_prius_cluster_offset(self, car_model):
|
||||
if car_model not in PRIUS_CLUSTER_OFFSET_CARS or self.params_raw.get_bool(PRIUS_CLUSTER_OFFSET_MIGRATION_KEY):
|
||||
@@ -595,7 +582,6 @@ class StarPilotVariables:
|
||||
|
||||
def update(self, holiday_theme="stock", started=False, clear_update_flag=True):
|
||||
toggle = self.starpilot_toggles
|
||||
toggle.tuning_level = self.params.get("TuningLevel") if self.params.get_bool("TuningLevelConfirmed") else TUNING_LEVELS["ADVANCED"]
|
||||
# CarParams uses this value to select the matching Panda safety configuration.
|
||||
toggle.tesla_cooperative_steering = self.params.get_bool("TeslaCoopSteering")
|
||||
|
||||
|
||||
@@ -160,15 +160,13 @@ def test_cancel_button_migration_copies_distance_actions_once():
|
||||
assert params.get_int("CancelButtonControl") == 3
|
||||
|
||||
|
||||
def test_button_function_ignores_tuning_level_gate():
|
||||
def test_runtime_values_ignore_legacy_tuning_level_metadata():
|
||||
params = _FakeParams(ints={"LKASButtonControl": spv.BUTTON_FUNCTIONS["AOL_TOGGLE"]})
|
||||
variables = object.__new__(spv.StarPilotVariables)
|
||||
variables.params = params
|
||||
variables.starpilot_toggles = SimpleNamespace(tuning_level=spv.TUNING_LEVELS["STANDARD"])
|
||||
variables.tuning_levels = {"LKASButtonControl": spv.TUNING_LEVELS["ADVANCED"]}
|
||||
variables.default_values = {"LKASButtonControl": str(spv.BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"])}
|
||||
|
||||
assert variables.get_value("LKASButtonControl", cast=int) == spv.BUTTON_FUNCTIONS["EXPERIMENTAL_MODE"]
|
||||
assert variables.get_value("LKASButtonControl", cast=int) == spv.BUTTON_FUNCTIONS["AOL_TOGGLE"]
|
||||
assert variables.get_button_function("LKASButtonControl") == spv.BUTTON_FUNCTIONS["AOL_TOGGLE"]
|
||||
|
||||
|
||||
|
||||
@@ -2629,6 +2629,8 @@ def _apply_param_bundle(params: Params, bundle: dict[str, Any]) -> None:
|
||||
elif kind == "string":
|
||||
params.put(key, str(value or ""))
|
||||
|
||||
Params(memory=True).put_bool("StarPilotTogglesUpdated", True)
|
||||
|
||||
|
||||
def _merge_flm_override_state(base: dict[str, Any], delta: dict[str, Any]) -> dict[str, Any]:
|
||||
base = normalize_flm_overrides(base)
|
||||
@@ -2815,6 +2817,7 @@ def accept_trial_as_baseline() -> dict[str, Any]:
|
||||
params.put_bool("FLMTrialApplied", False)
|
||||
params.put("FLMActiveProfileId", "")
|
||||
_clear_persistent_trial_baseline(params)
|
||||
Params(memory=True).put_bool("StarPilotTogglesUpdated", True)
|
||||
for path in paths["snapshots"].glob("*.json"):
|
||||
path.unlink()
|
||||
|
||||
|
||||
@@ -22,44 +22,51 @@ def _simple_module(name, **attrs):
|
||||
def _install_flm_import_stubs(tmp_path):
|
||||
class FakeParams:
|
||||
_store = {}
|
||||
_memory_store = {}
|
||||
|
||||
def __init__(self, return_defaults=False):
|
||||
def __init__(self, return_defaults=False, memory=False):
|
||||
self.return_defaults = return_defaults
|
||||
self.memory = memory
|
||||
|
||||
@property
|
||||
def _values(self):
|
||||
return type(self)._memory_store if self.memory else type(self)._store
|
||||
|
||||
def get(self, key, block=False, return_default=False, encoding=None, default=None):
|
||||
del block, return_default
|
||||
value = self._store.get(key, default)
|
||||
value = self._values.get(key, default)
|
||||
if encoding and isinstance(value, bytes):
|
||||
return value.decode(encoding, errors="replace")
|
||||
return value
|
||||
|
||||
def get_bool(self, key, default=False):
|
||||
value = self._store.get(key, default)
|
||||
value = self._values.get(key, default)
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return str(value).strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
def get_float(self, key, block=False, return_default=False, default=0.0):
|
||||
del block, return_default
|
||||
value = self._store.get(key, default)
|
||||
value = self._values.get(key, default)
|
||||
try:
|
||||
return float(value)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def put(self, key, value):
|
||||
self._store[key] = value
|
||||
self._values[key] = value
|
||||
|
||||
def put_bool(self, key, value):
|
||||
self._store[key] = bool(value)
|
||||
self._values[key] = bool(value)
|
||||
|
||||
def put_float(self, key, value):
|
||||
self._store[key] = float(value)
|
||||
self._values[key] = float(value)
|
||||
|
||||
def remove(self, key):
|
||||
self._store.pop(key, None)
|
||||
self._values.pop(key, None)
|
||||
|
||||
FakeParams._store = {}
|
||||
FakeParams._memory_store = {}
|
||||
|
||||
class FakeHyundaiFlags:
|
||||
CANFD = 1
|
||||
@@ -850,7 +857,9 @@ def test_apply_and_revert_trial_profile_round_trip(tmp_path):
|
||||
assert fake_params_cls._store["FLMTrialBaseline"]["params"]["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(0.08)
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.55)
|
||||
assert fake_params_cls._memory_store["StarPilotTogglesUpdated"] is True
|
||||
|
||||
fake_params_cls._memory_store["StarPilotTogglesUpdated"] = False
|
||||
revert_result = module.revert_trial_profile()
|
||||
assert revert_result["snapshot"]["profileId"] == profile_id
|
||||
assert fake_params_cls._store["AdvancedLateralTune"] is False
|
||||
@@ -858,6 +867,7 @@ def test_apply_and_revert_trial_profile_round_trip(tmp_path):
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is False
|
||||
assert "FLMTrialBaseline" not in fake_params_cls._store
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.55)
|
||||
assert fake_params_cls._memory_store["StarPilotTogglesUpdated"] is True
|
||||
|
||||
|
||||
def test_repeated_trial_revisions_revert_to_original_baseline(tmp_path):
|
||||
@@ -1040,6 +1050,7 @@ def test_irrecoverable_trial_can_keep_current_values_as_new_baseline(tmp_path):
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["generic.turn_in_boost_left"] == pytest.approx(0.1)
|
||||
assert fake_params_cls._store["FLMActiveProfileId"] == ""
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is False
|
||||
assert fake_params_cls._memory_store["StarPilotTogglesUpdated"] is True
|
||||
|
||||
|
||||
def test_workspace_hydrates_display_metadata_for_existing_active_trial(tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user