mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
533 lines
27 KiB
Python
533 lines
27 KiB
Python
"""Stock alert preferences through actual temporary Params and native requests."""
|
||
|
||
from dataclasses import replace
|
||
from pathlib import Path
|
||
from types import SimpleNamespace
|
||
import tempfile
|
||
import unittest
|
||
from unittest.mock import Mock, patch
|
||
|
||
import numpy as np
|
||
|
||
from openpilot.common.params import Params
|
||
from openpilot.selfdrive.ui.soundd import ALERT_VOLUME_KEYS, CRITICAL_MAX, AudibleAlert, Soundd, sound_list
|
||
from openpilot.starpilot.audio.alert_volume import AUTO, SPECS, VOLUMES, effective_volume, read_volume
|
||
from openpilot.starpilot.ui import sounds_compact
|
||
from openpilot.starpilot.ui.feature_settings_state import (
|
||
feature_row_top, FeatureInput, FeatureSettingsRequest, FeatureUiAction, row_change, value_buttons, value_done_rect,
|
||
)
|
||
from openpilot.starpilot.ui.presentation import Profile
|
||
from openpilot.starpilot.ui.runtime_app import SOUND_PRESETS
|
||
from openpilot.starpilot.ui.settings_state import Destination, SettingsInput, SettingsState, tile_rects
|
||
from openpilot.starpilot.ui.sounds_owner import SoundsOwner
|
||
from openpilot.starpilot.ui.shell import ShellInput, ShellMode, ShellSnapshot
|
||
|
||
|
||
class SoundsSettingsTests(unittest.TestCase):
|
||
def setUp(self):
|
||
temporary = tempfile.TemporaryDirectory()
|
||
self.addCleanup(temporary.cleanup)
|
||
self.params = Params(temporary.name)
|
||
self.parked = True
|
||
self.owner = SoundsOwner(self.params, lambda: self.parked)
|
||
|
||
def row(self, key):
|
||
return next(row for row in self.owner.snapshot().rows if row.key == key)
|
||
|
||
def large_session(self):
|
||
from openpilot.starpilot.ui.runtime_app import StarShellSession
|
||
session = StarShellSession.__new__(StarShellSession)
|
||
session.profile = Profile.LARGE
|
||
session._mode = ShellMode.SETTINGS
|
||
session.selected = Destination.SOUNDS
|
||
session.sounds_owner = self.owner
|
||
session.sounds_scroll = 0
|
||
session.sounds_edit_key = None
|
||
session._snapshot_cache = None
|
||
session.input = ShellInput(Profile.LARGE, lambda _: None)
|
||
return session
|
||
|
||
def test_absence_auto_no_writes_and_native_registry(self):
|
||
state = self.owner.snapshot()
|
||
self.assertEqual(len(state.rows), 8)
|
||
for key, _, _ in VOLUMES:
|
||
self.assertEqual(self.row(key).value, "Auto")
|
||
self.assertEqual((self.row(key).step, self.row(key).maximum, self.row(key).unit), (5.0, 100.0, "%"))
|
||
self.assertIsNone(read_volume(self.params, key).raw)
|
||
self.assertEqual(self.params.get_type(key).name, "INT")
|
||
|
||
def test_saved_choices_corruption_and_exact_source(self):
|
||
engage = self.row("EngageVolume")
|
||
engage_request = row_change(engage, 1)
|
||
assert engage_request is not None
|
||
self.assertTrue(self.owner.apply(engage_request))
|
||
self.assertEqual(self.params.get("EngageVolume"), 0)
|
||
self.assertFalse(self.owner.apply(engage_request))
|
||
path = Path(self.params.get_param_path("WarningSoftVolume"))
|
||
path.write_bytes(b"\xff")
|
||
corrupt = self.row("WarningSoftVolume")
|
||
self.assertEqual(corrupt.value, "Invalid saved level")
|
||
self.assertEqual(path.read_bytes(), b"\xff")
|
||
repair = row_change(corrupt)
|
||
assert repair is not None
|
||
self.assertTrue(self.owner.apply(repair))
|
||
self.assertEqual(read_volume(self.params, "WarningSoftVolume").value, AUTO)
|
||
self.assertFalse(self.owner.apply(repair))
|
||
|
||
def test_historical_zero_levels_for_refuse_and_disengage(self):
|
||
for key in ("RefuseVolume", "DisengageVolume"):
|
||
request = row_change(self.row(key), 1)
|
||
assert request is not None
|
||
self.assertTrue(self.owner.apply(request))
|
||
self.assertEqual(read_volume(self.params, key).value, 0)
|
||
|
||
def test_fixed_volume_slider_preserves_warning_floor_and_auto(self):
|
||
self.params.put("WarningImmediateVolume", 40, block=True)
|
||
row = self.row("WarningImmediateVolume")
|
||
self.assertEqual((row.value, row.minimum, row.maximum, row.step, row.unit, row.choices), ("40", 25.0, 100.0, 5.0, "%", ("Auto",)))
|
||
self.assertFalse(self.owner.apply(FeatureSettingsRequest(row.key, row.source, "20")))
|
||
request = row_change(row)
|
||
assert request is not None
|
||
self.assertTrue(self.owner.apply(request))
|
||
self.assertEqual(read_volume(self.params, row.key).value, 45)
|
||
auto = row_change(self.row("sounds:auto:WarningImmediateVolume"))
|
||
assert auto is not None
|
||
self.assertTrue(self.owner.apply(auto))
|
||
self.assertEqual(read_volume(self.params, row.key).value, AUTO)
|
||
|
||
def test_parked_and_final_read_guards(self):
|
||
request = row_change(self.row("RefuseVolume"), 1)
|
||
assert request is not None
|
||
self.parked = False
|
||
self.assertFalse(self.owner.apply(request))
|
||
self.assertIsNone(read_volume(self.params, "RefuseVolume").raw)
|
||
self.parked = True
|
||
original = self.params.get_param_path
|
||
calls = 0
|
||
def path(key):
|
||
nonlocal calls
|
||
calls += 1
|
||
if calls == 2:
|
||
raise OSError("lost source")
|
||
return original(key)
|
||
with patch.object(self.params, "get_param_path", side_effect=path):
|
||
self.assertFalse(self.owner.apply(request))
|
||
self.assertIsNone(read_volume(self.params, "RefuseVolume").raw)
|
||
|
||
def test_oversized_and_unreadable_source_does_not_write(self):
|
||
path = Path(self.params.get_param_path("WarningImmediateVolume"))
|
||
original = b"9" * 100_000
|
||
path.write_bytes(original)
|
||
self.assertEqual(read_volume(self.params, "WarningImmediateVolume").value, None)
|
||
oversized = self.row("WarningImmediateVolume")
|
||
self.assertFalse(oversized.available)
|
||
self.assertIsNone(row_change(oversized))
|
||
self.assertEqual(path.read_bytes(), original)
|
||
# Equal bounded prefixes do not authorize edits when the tail changed.
|
||
path.write_bytes(original[:-1] + b"8")
|
||
self.assertFalse(self.owner.apply(FeatureSettingsRequest("WarningImmediateVolume", oversized.source, "Auto")))
|
||
self.assertFalse(self.row("WarningImmediateVolume").available)
|
||
self.assertEqual(path.read_bytes(), original[:-1] + b"8")
|
||
path.unlink()
|
||
path.mkdir()
|
||
unreadable = self.row("WarningImmediateVolume")
|
||
self.assertFalse(unreadable.available)
|
||
self.assertTrue(path.is_dir())
|
||
|
||
def test_soundd_poll_reads_saved_values_without_touching_samples(self):
|
||
sound = Soundd.__new__(Soundd)
|
||
sound.pack_loader = Mock(is_builtin=Mock(return_value=False))
|
||
sound.volume_params = self.params
|
||
sound.saved_volumes = {key: AUTO for key, _, _ in VOLUMES}
|
||
sound.volume_read_at = 0.0
|
||
self.params.put("DisengageVolume", 45, block=True)
|
||
sound.refresh_saved_volumes(1.0)
|
||
self.assertEqual(sound.saved_volumes["DisengageVolume"], 45)
|
||
self.params.put("DisengageVolume", 65, block=True)
|
||
sound.refresh_saved_volumes(1.1)
|
||
self.assertEqual(sound.saved_volumes["DisengageVolume"], 45)
|
||
sound.refresh_saved_volumes(1.3)
|
||
self.assertEqual(sound.saved_volumes["DisengageVolume"], 65)
|
||
Path(self.params.get_param_path("WarningImmediateVolume")).write_bytes(b"0")
|
||
sound.refresh_saved_volumes(1.6)
|
||
self.assertIsNone(sound.saved_volumes["WarningImmediateVolume"])
|
||
self.assertEqual(effective_volume("WarningImmediateVolume", sound.saved_volumes["WarningImmediateVolume"], 0.14,
|
||
immediate_ramp=0.14), 0.14)
|
||
|
||
def test_large_tile_swipe_and_sound_row_request(self):
|
||
actions = []
|
||
settings_input = SettingsInput(Profile.LARGE, actions.append)
|
||
x, y, width, height = tile_rects(SettingsState())[0]
|
||
settings_input.press(x + width / 2, y + height / 2, SettingsState())
|
||
settings_input.release(x + width / 2, y + height / 2, SettingsState())
|
||
self.assertEqual(actions[0].destination.destination, Destination.SOUNDS)
|
||
received = []
|
||
native = FeatureInput(received.append)
|
||
summary = self.large_session().sounds_snapshot()
|
||
native.press(900, 600, summary)
|
||
native.move(720, 600, summary)
|
||
native.release(720, 600, summary)
|
||
self.assertEqual(received, [FeatureUiAction("scroll")])
|
||
received.clear()
|
||
session = self.large_session()
|
||
session.sounds_edit_key = "WarningImmediateVolume"
|
||
state = session.sounds_snapshot()
|
||
_, (x, y, width, height), _ = next(button for button in value_buttons(state, state.rows[0], feature_row_top(state)) if button[0] == "25")
|
||
x, y = x + width / 2, y + height / 2
|
||
native.press(x, y, state)
|
||
native.move(x - 180, y, state)
|
||
native.release(x - 180, y, state)
|
||
self.assertEqual(received, [])
|
||
received.clear()
|
||
native.press(x, y, state)
|
||
native.release(x, y, state)
|
||
self.assertEqual(received[0].kind, "action")
|
||
self.assertEqual(received[0].row.key, "WarningImmediateVolume")
|
||
self.assertEqual(value_buttons(state, received[0].row)[received[0].direction][0], "25")
|
||
|
||
def test_large_session_routes_saved_edit_and_back(self):
|
||
session = self.large_session()
|
||
session._sounds_ui(FeatureUiAction("open", next(row for row in session.sounds_snapshot().rows if row.key == "EngageVolume")))
|
||
row = session.sounds_snapshot().rows[0]
|
||
session._sounds_ui(FeatureUiAction("change", row, 1))
|
||
self.assertIsNone(read_volume(self.params, "EngageVolume").raw)
|
||
session._sounds_ui(FeatureUiAction("action", row, 1))
|
||
self.assertEqual(read_volume(self.params, "EngageVolume").value, 0)
|
||
self.parked = False
|
||
session._sounds_ui(FeatureUiAction("change", self.row("EngageVolume"), 1))
|
||
self.assertEqual(read_volume(self.params, "EngageVolume").value, 0)
|
||
session._sounds_ui(FeatureUiAction("back"))
|
||
self.assertEqual(session.selected, Destination.SOUNDS)
|
||
self.assertIsNone(session.sounds_edit_key)
|
||
session._sounds_ui(FeatureUiAction("back"))
|
||
self.assertEqual(session.selected, Destination.STAR)
|
||
|
||
def test_large_hybrid_presets_and_steps_save_on_release_with_source_guards(self):
|
||
session = self.large_session()
|
||
for expanded in (True, False):
|
||
for key, (_, minimum) in SPECS.items():
|
||
session.sounds_edit_key = key
|
||
for value in ("-", *SOUND_PRESETS, "+"):
|
||
if value not in ("-", "+", "Auto") and int(value) < minimum:
|
||
continue
|
||
self.params.put(key, 42, block=True)
|
||
state = session.sounds_snapshot()
|
||
self.assertEqual(len(state.rows), 1)
|
||
self.assertEqual(state.title, SPECS[key][0])
|
||
state = replace(state, sidebar_expanded=expanded, scroll=0)
|
||
row = state.rows[0]
|
||
self.assertEqual(row.key, key)
|
||
buttons = value_buttons(state, row, feature_row_top(state))
|
||
_, (x, y, width, height), enabled = next(button for button in buttons if button[0] == value)
|
||
self.assertTrue(enabled)
|
||
self.assertLessEqual(x + width, 2094.01)
|
||
self.assertEqual(height, 200)
|
||
x, y = x + width / 2, y + height / 2
|
||
received = []
|
||
native = FeatureInput(received.append)
|
||
native.press(x, y, state)
|
||
self.assertEqual(received, [])
|
||
self.assertEqual(self.params.get(key), 42)
|
||
native.release(x, y, state)
|
||
self.assertEqual(len(received), 1)
|
||
self.assertEqual(received[0].row.source, b"42")
|
||
session._sounds_ui(received[0])
|
||
expected = 37 if value == "-" else 47 if value == "+" else AUTO if value == "Auto" else int(value)
|
||
self.assertEqual(self.params.get(key), expected)
|
||
if value not in ("-", "+"):
|
||
fresh = replace(session.sounds_snapshot(), sidebar_expanded=expanded, scroll=state.scroll)
|
||
self.assertIsNone(FeatureInput.target(x, y, fresh))
|
||
for value in (minimum, 100, AUTO):
|
||
self.params.put(key, value, block=True)
|
||
buttons = value_buttons(session.sounds_snapshot(), session.sounds_snapshot().rows[0])
|
||
self.assertEqual(buttons[0][2], value != minimum and value != AUTO)
|
||
self.assertEqual(buttons[-1][2], value != 100 and value != AUTO)
|
||
row = self.row("EngageVolume")
|
||
session.sounds_edit_key = row.key
|
||
self.params.put(row.key, 60, block=True)
|
||
session._sounds_ui(FeatureUiAction("action", row, 1))
|
||
self.assertEqual(self.params.get(row.key), 60)
|
||
session._mode = ShellMode.ONROAD
|
||
session._sounds_ui(FeatureUiAction("action", self.row(row.key), 1))
|
||
self.assertEqual(self.params.get(row.key), 60)
|
||
session.profile = Profile.COMPACT
|
||
self.assertEqual(session.sounds_snapshot(), self.owner.snapshot())
|
||
self.assertGreater(len(self.owner.snapshot().rows), 8)
|
||
state = session.sounds_snapshot()
|
||
repair = replace(self.row(row.key), step=0, repair_value="Auto", value="Invalid saved level")
|
||
self.assertEqual(value_buttons(state, repair), ())
|
||
self.assertEqual(value_buttons(state, next(row for row in state.rows if row.key == "SoundPack")), ())
|
||
|
||
def test_summary_opens_one_editor_and_returns_to_same_page_without_writes(self):
|
||
session = self.large_session()
|
||
self.params.put("EngageVolume", 40, block=True)
|
||
session.sounds_scroll = 5
|
||
state = replace(session.sounds_snapshot(), scroll=5)
|
||
self.assertEqual(state.page, "sounds:overview")
|
||
self.assertEqual(len(state.rows), 8)
|
||
self.assertTrue(all(value_buttons(state, row) == () for row in state.rows))
|
||
self.assertEqual(next(row for row in state.rows if row.key == "EngageVolume").value, "40")
|
||
received = []
|
||
native = FeatureInput(received.append)
|
||
x, y = 800, feature_row_top(state) + 154 + 77
|
||
native.press(x, y, state)
|
||
self.assertEqual(received, [])
|
||
native.release(x, y, state)
|
||
self.assertEqual(received[0].kind, "open")
|
||
session._sounds_ui(received[0])
|
||
editor = session.sounds_snapshot()
|
||
self.assertEqual(session.sounds_edit_key, "PromptVolume")
|
||
self.assertEqual([row.key for row in editor.rows], ["PromptVolume"])
|
||
self.assertEqual(session.sounds_scroll, 5)
|
||
self.assertIsNone(read_volume(self.params, "PromptVolume").raw)
|
||
received.clear()
|
||
x, y, width, height = value_done_rect(editor)
|
||
native.press(x + width / 2, y + height / 2, editor)
|
||
native.release(x + width / 2, y + height / 2, editor)
|
||
session._sounds_ui(received[0])
|
||
self.assertIsNone(session.sounds_edit_key)
|
||
self.assertEqual(session.sounds_scroll, 5)
|
||
self.assertEqual(session.sounds_snapshot(), replace(state, scroll=0))
|
||
summary_row = next(row for row in state.rows if row.key == "EngageVolume")
|
||
session._sounds_ui(FeatureUiAction("change", summary_row, 1))
|
||
self.assertEqual(self.params.get("EngageVolume"), 40)
|
||
self.parked = False
|
||
blocked = next(row for row in session.sounds_snapshot().rows if row.key == "EngageVolume")
|
||
session._sounds_ui(FeatureUiAction("open", blocked))
|
||
self.assertIsNone(session.sounds_edit_key)
|
||
self.parked = True
|
||
Path(self.params.get_param_path("WarningSoftVolume")).write_bytes(b"bad")
|
||
broken = next(row for row in session.sounds_snapshot().rows if row.key == "WarningSoftVolume")
|
||
session._sounds_ui(FeatureUiAction("open", broken))
|
||
editor = session.sounds_snapshot()
|
||
repair = FeatureInput.target(1900, feature_row_top(editor) + 77, editor)
|
||
self.assertEqual(repair.kind, "change")
|
||
session._sounds_ui(repair)
|
||
self.assertEqual(self.params.get("WarningSoftVolume"), AUTO)
|
||
|
||
def test_editor_touches_dispatch_with_retained_overview_page(self):
|
||
from openpilot.starpilot.ui import runtime_app
|
||
for expanded in (True, False):
|
||
for scroll in (0, 5):
|
||
for key in ("DisengageVolume", "PromptVolume", "SoundPack"):
|
||
for control in ("level", "done", *(("sidebar",) if expanded else ())):
|
||
with self.subTest(expanded=expanded, scroll=scroll, key=key, control=control):
|
||
self.params.put(key, "starpilot" if key == "SoundPack" else 42, block=True)
|
||
session = self.large_session()
|
||
session.sounds_scroll = scroll
|
||
session.sidebar_expanded = expanded
|
||
session.confirmed_offroad = Mock(return_value=True)
|
||
session.favorites = Mock()
|
||
session.view = Mock()
|
||
session.view.sounds.reset.return_value = False
|
||
session._on_destination_change = None
|
||
row = next(row for row in session.sounds_snapshot().rows if row.key == key)
|
||
session._sounds_ui(FeatureUiAction("open", row))
|
||
editor = replace(session.sounds_snapshot(), scroll=0, sidebar_expanded=expanded)
|
||
snapshot = ShellSnapshot(ShellMode.SETTINGS, Mock(), SettingsState(sidebar_expanded=expanded), Mock(),
|
||
selected=Destination.SOUNDS, sounds=editor)
|
||
session.snapshot = Mock(return_value=snapshot)
|
||
session.input = ShellInput(Profile.LARGE, session._emit)
|
||
session.sounds_request = Mock(wraps=session.sounds_request)
|
||
if control == "done":
|
||
x, y, width, height = value_done_rect(editor)
|
||
x, y = x + width / 2, y + height / 2
|
||
elif control == "sidebar":
|
||
x, y = 240, 345
|
||
elif key == "SoundPack":
|
||
_, (x, y, width, height), _ = next(button for button in value_buttons(editor, editor.rows[0], feature_row_top(editor))
|
||
if button[0] == "Stock Sounds")
|
||
x, y = x + width / 2, y + height / 2
|
||
else:
|
||
_, (x, y, width, height), _ = next(button for button in value_buttons(editor, editor.rows[0], feature_row_top(editor))
|
||
if button[0] == "25")
|
||
x, y = x + width / 2, y + height / 2
|
||
with patch.object(runtime_app, "ui_state", SimpleNamespace(started=False, started_frame=0)):
|
||
session.press(ShellMode.SETTINGS, x, y)
|
||
session.move(ShellMode.SETTINGS, x, y)
|
||
self.assertTrue(session.release(ShellMode.SETTINGS, x, y))
|
||
self.assertEqual(session.sounds_scroll, scroll)
|
||
if control == "level":
|
||
session.sounds_request.assert_called_once()
|
||
if key != "SoundPack":
|
||
self.assertEqual(self.params.get(key), 25)
|
||
else:
|
||
self.assertEqual(self.params.get(key), "stock")
|
||
else:
|
||
session.sounds_request.assert_not_called()
|
||
if control == "done":
|
||
self.assertIsNone(session.sounds_edit_key)
|
||
self.assertEqual(session.selected, Destination.SOUNDS)
|
||
else:
|
||
self.assertEqual(session.selected, Destination.STAR)
|
||
|
||
def test_done_returns_from_volume_pack_and_repair_without_writing(self):
|
||
session = self.large_session()
|
||
session.sounds_scroll = 5
|
||
Path(self.params.get_param_path("WarningSoftVolume")).write_bytes(b"bad")
|
||
for expanded in (True, False):
|
||
for key in ("PromptVolume", "SoundPack", "WarningSoftVolume"):
|
||
self.parked = True
|
||
row = next(row for row in session.sounds_snapshot().rows if row.key == key)
|
||
session._sounds_ui(FeatureUiAction("open", row))
|
||
self.parked = False
|
||
state = replace(session.sounds_snapshot(), sidebar_expanded=expanded)
|
||
before = tuple(row.source for row in self.owner.snapshot().rows)
|
||
x, y, width, height = value_done_rect(state)
|
||
x, y = x + width / 2, y + height / 2
|
||
received = []
|
||
native = FeatureInput(received.append)
|
||
for changed in (state, replace(state, sidebar_expanded=not expanded)):
|
||
native.press(x, y, state)
|
||
if changed == state:
|
||
native.move(x - 50, y, state)
|
||
native.release(x, y, changed)
|
||
self.assertEqual(received, [])
|
||
native.press(x, y, state)
|
||
native.release(x, y, state)
|
||
self.assertEqual(received, [FeatureUiAction("back")])
|
||
session._sounds_ui(received[0])
|
||
self.assertIsNone(session.sounds_edit_key)
|
||
self.assertEqual(session.sounds_scroll, 5)
|
||
self.assertEqual(session.selected, Destination.SOUNDS)
|
||
self.assertEqual(tuple(row.source for row in self.owner.snapshot().rows), before)
|
||
|
||
def test_volume_commit_rejects_competing_native_write_before_lock(self):
|
||
from openpilot.starpilot import saved_document
|
||
self.params.put("EngageVolume", 40, block=True)
|
||
row = self.row("EngageVolume")
|
||
acquire = saved_document.acquire_native_lock
|
||
def competing_write(fd):
|
||
self.params.put(row.key, 60, block=True)
|
||
acquire(fd)
|
||
with patch.object(saved_document, "acquire_native_lock", side_effect=competing_write):
|
||
self.assertFalse(self.owner.apply(FeatureSettingsRequest(row.key, row.source, "50")))
|
||
self.assertEqual(self.params.get(row.key), 60)
|
||
root = Path(self.params.get_param_path(row.key)).parent.parent
|
||
self.assertEqual(list(root.glob(".sound-volume-*")), [])
|
||
|
||
def test_compact_child_refreshes_after_saved_edit(self):
|
||
class Button:
|
||
def __init__(self, label, value):
|
||
self.label, self.value, self.click = label, value, None
|
||
def set_click_callback(self, callback):
|
||
self.click = callback
|
||
class Scroller:
|
||
def __init__(self):
|
||
self._scroller = self
|
||
self.items = []
|
||
def add_widgets(self, cards):
|
||
self.items.extend(cards)
|
||
class Session:
|
||
def sounds_snapshot(inner):
|
||
return self.owner.snapshot()
|
||
def sounds_request(inner, request):
|
||
return self.owner.apply(request)
|
||
shown = []
|
||
with patch.object(sounds_compact, "BigButton", Button), patch.object(sounds_compact, "GreyBigButton", Button), \
|
||
patch.object(sounds_compact, "NavScroller", Scroller), patch.object(sounds_compact.gui_app, "push_widget", shown.append):
|
||
compact = sounds_compact.SoundsCompact(Session())
|
||
compact.entry_button().click()
|
||
page = shown[0]
|
||
self.assertEqual(page.items[0].label, "sounds & alerts")
|
||
plus = next(item for item in page.items if item.label == "engagement chime +")
|
||
plus.click()
|
||
self.assertEqual(read_volume(self.params, "EngageVolume").value, 0)
|
||
self.assertIn("0%", next(item for item in page.items if item.label == "engagement chime +").value)
|
||
for key, label, _ in VOLUMES:
|
||
with self.subTest(key=key):
|
||
self.params.put(key, 50, block=True)
|
||
compact._populate(page)
|
||
next(item for item in page.items if item.label == label.lower() + " +").click()
|
||
self.assertEqual(read_volume(self.params, key).value, 55)
|
||
next(item for item in page.items if item.label == label.lower() + " −").click()
|
||
self.assertEqual(read_volume(self.params, key).value, 50)
|
||
stale = next(item for item in page.items if item.label == label.lower() + " +")
|
||
self.params.put(key, 60, block=True)
|
||
stale.click()
|
||
self.assertEqual(read_volume(self.params, key).value, 60)
|
||
compact._populate(page)
|
||
next(item for item in page.items if item.label == "use auto " + label.lower() + " set auto").click()
|
||
self.assertEqual(read_volume(self.params, key).value, AUTO)
|
||
self.assertEqual(next(item for item in page.items if item.label == label.lower() + " +").value, "auto")
|
||
self.parked = False
|
||
next(item for item in page.items if item.label == label.lower() + " +").click()
|
||
self.assertEqual(read_volume(self.params, key).value, AUTO)
|
||
self.parked = True
|
||
|
||
def test_soundd_auto_samples_and_protected_warning_ramp(self):
|
||
sound = Soundd.__new__(Soundd)
|
||
sound.pack_loader = Mock(is_builtin=Mock(return_value=False))
|
||
sound.current_alert = AudibleAlert.engage
|
||
sound.current_sound = AudibleAlert.engage
|
||
sound.current_sound_frame = 0
|
||
sound.pending_stop = False
|
||
sound.current_volume = 0.17
|
||
sound.loaded_sounds = {AudibleAlert.engage: np.array([0.5, -0.5], dtype=np.float32),
|
||
AudibleAlert.warningImmediate: np.array([0.5, -0.5], dtype=np.float32)}
|
||
sound.saved_volumes = {"EngageVolume": AUTO, "WarningImmediateVolume": AUTO}
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), np.array([0.085, -0.085], dtype=np.float32))
|
||
sound.current_sound_frame = 0
|
||
sound.saved_volumes["EngageVolume"] = 0
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), np.zeros(2, dtype=np.float32))
|
||
sound.current_alert = AudibleAlert.warningImmediate
|
||
sound.current_sound = AudibleAlert.warningImmediate
|
||
sound.current_sound_frame = 0
|
||
sound.saved_volumes["WarningImmediateVolume"] = 25
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), np.array([0.125, -0.125], dtype=np.float32))
|
||
sound.current_sound_frame = 0
|
||
sound.current_volume = 1.0
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), np.array([0.5, -0.5], dtype=np.float32))
|
||
self.assertEqual(effective_volume("WarningImmediateVolume", None, 0.17, immediate_ramp=0.17), 0.17)
|
||
|
||
def test_auto_matches_prior_gain_for_every_stock_family_and_timeout(self):
|
||
sound = Soundd.__new__(Soundd)
|
||
sound.pack_loader = Mock(is_builtin=Mock(return_value=False))
|
||
waveform = np.array([0.5, -0.5], dtype=np.float32)
|
||
sound.loaded_sounds = dict.fromkeys(sound_list, waveform)
|
||
sound.saved_volumes = {key: AUTO for key, _, _ in VOLUMES}
|
||
sound.current_volume = 0.17
|
||
sound.pending_stop = False
|
||
for alert in sound_list:
|
||
if alert == CRITICAL_MAX:
|
||
self.assertNotIn(alert, ALERT_VOLUME_KEYS)
|
||
else:
|
||
self.assertIn(alert, ALERT_VOLUME_KEYS)
|
||
sound.current_alert = alert
|
||
sound.current_sound = alert
|
||
sound.current_sound_frame = 0
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), waveform * 0.17)
|
||
# The existing timeout path produces the same immediate-warning alert.
|
||
sound.current_alert = AudibleAlert.none
|
||
sound.current_sound = AudibleAlert.none
|
||
sound.current_sound_frame = 0
|
||
sound.selfdrive_timeout_alert = False
|
||
sm = type("State", (), {"updated": {"selfdriveState": False}})()
|
||
with patch("openpilot.selfdrive.ui.soundd.check_selfdrive_timeout_alert", return_value=True):
|
||
sound.get_audible_alert(sm)
|
||
self.assertEqual(sound.current_alert, AudibleAlert.warningImmediate)
|
||
self.assertTrue(sound.selfdrive_timeout_alert)
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), waveform * 0.17)
|
||
sound.current_sound_frame = 0
|
||
sound.current_volume = 0.83
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), waveform * 0.83)
|
||
|
||
def test_looping_alert_final_chunk_keeps_producing_alert_gain(self):
|
||
sound = Soundd.__new__(Soundd)
|
||
sound.pack_loader = Mock(is_builtin=Mock(return_value=False))
|
||
sound.current_alert = AudibleAlert.promptRepeat
|
||
sound.current_sound = AudibleAlert.promptRepeat
|
||
sound.current_sound_frame = 2
|
||
sound.pending_stop = True
|
||
sound.current_volume = 0.17
|
||
sound.loaded_sounds = {AudibleAlert.promptRepeat: np.array([0.5, -0.5], dtype=np.float32)}
|
||
sound.saved_volumes = {"PromptVolume": 0}
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), np.zeros(2, dtype=np.float32))
|
||
self.assertEqual(sound.current_alert, AudibleAlert.none)
|
||
sound.current_alert = AudibleAlert.promptRepeat
|
||
sound.current_sound = AudibleAlert.promptRepeat
|
||
sound.current_sound_frame = 2
|
||
sound.pending_stop = True
|
||
sound.saved_volumes["PromptVolume"] = AUTO
|
||
np.testing.assert_array_equal(sound.get_sound_data(2), np.array([0.085, -0.085], dtype=np.float32))
|