mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-11 10:43:46 +08:00
Add an optional GPU-model-ready chime
(cherry picked from commit 0a212734c1)
This commit is contained in:
committed by
firestar5683
parent
202ea33690
commit
46596218ba
@@ -352,6 +352,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DrivingModelVersion", {PERSISTENT, STRING, "v15", "v15", 1}},
|
||||
{"DynamicPathWidth", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"DynamicPedalsOnUI", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
|
||||
{"GpuModelReadySound", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
|
||||
{"EngageVolume", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
|
||||
{"EVTuning", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"Fahrenheit", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
|
||||
Binary file not shown.
@@ -42,6 +42,8 @@ StarPilotAudibleAlert = custom.StarPilotCarControl.HUDControl.AudibleAlert
|
||||
STARPILOT_CUSTOM_ALERT_OFFSET = 1000
|
||||
STARPILOT_CUSTOM_ALERT_START = int(StarPilotAudibleAlert.angry)
|
||||
TURN_STEERING_LIMIT_ALERT_SUFFIX = "steersaturated"
|
||||
GPU_MODEL_READY_ALERT = 2000
|
||||
|
||||
# Keep carState out of this list; C4's onroad stack is near msgq's 15-reader limit.
|
||||
SOUNDD_SERVICES = ('selfdriveState', 'soundPressure', 'starpilotSelfdriveState', 'starpilotPlan')
|
||||
|
||||
@@ -67,6 +69,7 @@ def should_mute_turn_steering_limit_alert(alert_type: str, v_ego: float, mute_be
|
||||
|
||||
|
||||
sound_list: dict[int, tuple[str, int | None, float]] = {
|
||||
GPU_MODEL_READY_ALERT: ("model_ready.wav", 1, MAX_VOLUME),
|
||||
# AudibleAlert, file name, play count (none for infinite)
|
||||
AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME),
|
||||
AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME),
|
||||
@@ -124,6 +127,11 @@ class Soundd:
|
||||
self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False)
|
||||
|
||||
self.params_memory = Params(memory=True)
|
||||
from openpilot.starpilot.common.gpu_model_ready_sound import GpuModelReadyChime
|
||||
self.model_ready_chime = GpuModelReadyChime()
|
||||
self.model_ready_params = Params(return_defaults=True)
|
||||
self.model_ready_last_check = 0.0
|
||||
self.model_ready_pending = False
|
||||
|
||||
self.starpilot_toggles = get_starpilot_toggles()
|
||||
|
||||
@@ -325,6 +333,33 @@ class Soundd:
|
||||
self.update_alert(AudibleAlert.none)
|
||||
self.selfdrive_timeout_alert = False
|
||||
|
||||
def update_model_ready_sound(self, sm):
|
||||
# A one-shot ending exactly on a callback boundary must release its slot.
|
||||
if self.current_alert == GPU_MODEL_READY_ALERT:
|
||||
loaded = self.loaded_sounds.get(GPU_MODEL_READY_ALERT)
|
||||
if loaded is None or self.current_sound_frame >= len(loaded):
|
||||
self.current_alert = AudibleAlert.none
|
||||
self.current_sound_frame = 0
|
||||
now = time.monotonic()
|
||||
if now - self.model_ready_last_check >= 0.25:
|
||||
self.model_ready_last_check = now
|
||||
try:
|
||||
self.model_ready_pending = self.model_ready_chime.update(
|
||||
active=self.model_ready_params.get_bool("UsbGpuActive"),
|
||||
loading=self.model_ready_params.get_bool("UsbGpuLoading"),
|
||||
onroad=self.model_ready_params.get_bool("IsOnroad"),
|
||||
enabled=self.model_ready_params.get_bool("GpuModelReadySound"), now=now)
|
||||
except Exception:
|
||||
self.model_ready_chime.consume()
|
||||
self.model_ready_pending = False
|
||||
# Run after stock/custom alert selection: warnings and timeout alerts always win.
|
||||
if (self.model_ready_pending and self.current_alert == AudibleAlert.none
|
||||
and not self.selfdrive_timeout_alert and sm.valid["selfdriveState"] and sm.alive["selfdriveState"]):
|
||||
self.model_ready_chime.consume()
|
||||
self.model_ready_pending = False
|
||||
self.current_alert_type = ""
|
||||
self.update_alert(GPU_MODEL_READY_ALERT)
|
||||
|
||||
def get_volume_override(self):
|
||||
if self.current_alert_type.startswith("belowSteerSpeed/"):
|
||||
return self.starpilot_toggles.below_steer_speed_volume / 100.0
|
||||
@@ -391,6 +426,7 @@ class Soundd:
|
||||
self.current_volume = 0.0
|
||||
|
||||
self.get_audible_alert(sm)
|
||||
self.update_model_ready_sound(sm)
|
||||
|
||||
if self.current_alert != AudibleAlert.none:
|
||||
v_ego = max(float(getattr(sm["starpilotSelfdriveState"], "vEgo", 0.0)), 0.0)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import ast
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
import wave
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.common.gpu_model_ready_sound import GpuModelReadyChime
|
||||
|
||||
|
||||
def update(chime, now=0, **kwargs):
|
||||
return chime.update(**dict(active=False, loading=False, onroad=True, enabled=True, now=now) | kwargs)
|
||||
|
||||
|
||||
def test_ready_once_after_success_not_during_loading_or_failure():
|
||||
chime = GpuModelReadyChime()
|
||||
assert not update(chime, loading=True)
|
||||
assert not update(chime, 1, active=True, loading=True)
|
||||
assert update(chime, 2, active=True)
|
||||
chime.consume()
|
||||
assert not update(chime, 3, active=True)
|
||||
assert not update(chime, 4, loading=True)
|
||||
assert not update(chime, 5)
|
||||
|
||||
|
||||
def test_restart_with_already_ready_model_does_not_chime():
|
||||
assert not update(GpuModelReadyChime(), active=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cancel", [dict(enabled=False), dict(onroad=False), dict(active=False), dict(loading=True)])
|
||||
def test_cancel_pending_on_disabled_offroad_fallback_or_reload(cancel):
|
||||
chime = GpuModelReadyChime()
|
||||
update(chime)
|
||||
assert update(chime, 1, active=True)
|
||||
assert not update(chime, 2, **(dict(active=True) | cancel))
|
||||
|
||||
|
||||
def test_pending_notification_expires_without_late_chime():
|
||||
chime = GpuModelReadyChime()
|
||||
update(chime)
|
||||
assert update(chime, 1, active=True)
|
||||
assert not update(chime, 6, active=True)
|
||||
assert not update(chime, 7, active=True)
|
||||
|
||||
|
||||
def test_enabling_option_after_load_does_not_replay():
|
||||
chime = GpuModelReadyChime()
|
||||
update(chime, enabled=False)
|
||||
assert not update(chime, 1, active=True, enabled=False)
|
||||
assert not update(chime, 2, active=True, enabled=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("alert,timeout,valid,alive,expected", [(1,False,True,True,False),(0,True,True,True,False),(0,False,False,True,False),(0,False,True,False,False),(0,False,True,True,True)])
|
||||
def test_soundd_never_replaces_existing_or_timeout_alerts(alert,timeout,valid,alive,expected):
|
||||
source=(Path(__file__).parents[1]/'soundd.py').read_text()
|
||||
cls=next(n for n in ast.parse(source).body if isinstance(n,ast.ClassDef) and n.name=='Soundd')
|
||||
method=next(n for n in cls.body if isinstance(n,ast.FunctionDef) and n.name=='update_model_ready_sound')
|
||||
scope={'time':SimpleNamespace(monotonic=lambda:1.1),'AudibleAlert':SimpleNamespace(none=0),'GPU_MODEL_READY_ALERT':2000}
|
||||
exec(compile(ast.Module(body=[method],type_ignores=[]),'<soundd method>','exec'),scope)
|
||||
calls=[]
|
||||
sound=SimpleNamespace(model_ready_last_check=1.0,model_ready_pending=True,model_ready_chime=GpuModelReadyChime(),current_alert=alert,selfdrive_timeout_alert=timeout,update_alert=calls.append)
|
||||
sm=SimpleNamespace(valid={'selfdriveState':valid},alive={'selfdriveState':alive})
|
||||
scope['update_model_ready_sound'](sound,sm)
|
||||
assert calls == ([2000] if expected else [])
|
||||
|
||||
|
||||
def test_notification_asset_matches_soundd_audio_format():
|
||||
with wave.open(str(Path(__file__).parents[2]/'assets/sounds/model_ready.wav')) as audio:
|
||||
assert (audio.getnchannels(),audio.getsampwidth(),audio.getframerate()) == (1,2,48000)
|
||||
assert 0 < audio.getnframes() < 48000
|
||||
@@ -3355,6 +3355,14 @@
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"settings_tier": "simple"
|
||||
},
|
||||
{
|
||||
"key": "GpuModelReadySound",
|
||||
"label": "GPU Model Ready Sound",
|
||||
"description": "Play a short chime when the external GPU model finishes loading. Driving alerts take priority.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle",
|
||||
"settings_tier": "simple"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Optional notification state; never supersedes driving alerts or starts inference."""
|
||||
|
||||
|
||||
class GpuModelReadyChime:
|
||||
def __init__(self):
|
||||
self.ready = None
|
||||
self.pending_until = None
|
||||
|
||||
def update(self, *, active, loading, onroad, enabled, now):
|
||||
ready = bool(onroad and active and not loading)
|
||||
# A soundd restart with an already-loaded model is not a loading event.
|
||||
if self.ready is False and ready and enabled:
|
||||
self.pending_until = now + 5.0
|
||||
self.ready = ready
|
||||
if not enabled or not ready or (self.pending_until is not None and now >= self.pending_until):
|
||||
self.pending_until = None
|
||||
return self.pending_until is not None
|
||||
|
||||
def consume(self):
|
||||
self.pending_until = None
|
||||
Reference in New Issue
Block a user