Restore stop word sensitivity from preferences on startup (#394)

* Restore stop word sensitivity from preferences on startup

The Home Assistant number entity persists preferences.stop_word_sensitivity
whenever it changes, but nothing ever read it back. main() did not pass
stop_word_threshold when constructing ServerState, so it always took the
dataclass default of 0.5, and lowering the sensitivity in Home Assistant
silently reverted at the next restart.

volume and mic_volume are restored at startup, and the two wake word
sensitivities are restored when wake models load. Only the stop word was
missed, so the block that syncs all three sensitivity entities back to Home
Assistant was pushing a default it had never loaded.

Restored alongside volume and clamped the same way. The resolution lives in a
small helper so it can be covered by tests.

* Move the threshold helper to models.py so tests do not import __main__

CI has no PulseAudio daemon. Importing linux_voice_assistant.__main__ pulls in
`import soundcard as sc`, and soundcard/pulseaudio.py instantiates _PulseAudio()
at module scope, which asserts on a live connection. So the four new tests
failed on the runner while passing locally, where a daemon happens to be
running.

That is also why no other test in the suite imports __main__, and why the
existing tests in test_main.py re-implement the logic they check rather than
calling it.

initial_stop_word_threshold() now lives in models.py, which owns both fields it
resolves between: Preferences.stop_word_sensitivity and
ServerState.stop_word_threshold. It imports cleanly, test_models.py already
imports it, and the reference to the ServerState default becomes local.

Behaviour is unchanged. Verified by reproducing the CI environment locally with
PULSE_SERVER pointed at a nonexistent socket: 4 failed before, 298 pass after.

---------

Co-authored-by: ryan felder <7475484+ryanfelder@users.noreply.github.com>
Co-authored-by: Omar Amin <65174210+omaramin-2000@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-08-18 02:07:33 +03:00
1 parent a44292654e
commit d1f5761f75
3 files changed
+44 -1

No files matched your search

+6 -1
View File
@@ -18,7 +18,7 @@ from getmac import get_mac_address # type: ignore
from pymicro_wakeword import MicroWakeWord, MicroWakeWordFeatures
from pyopen_wakeword import OpenWakeWord, OpenWakeWordFeatures
from .models import Preferences, ServerState, WakeWordType
from .models import Preferences, ServerState, WakeWordType, initial_stop_word_threshold
from .mpv_player import MpvMediaPlayer
from .peripheral_api import LVAEvent, PeripheralAPIServer
from .satellite import VoiceSatelliteProtocol
@@ -379,6 +379,10 @@ async def main() -> None:
initial_volume = max(0.0, min(1.0, float(initial_volume)))
preferences.volume = initial_volume
# Load stop word sensitivity from preferences on startup, and ensure it's between 0.0 and 1.0
initial_threshold = initial_stop_word_threshold(preferences.stop_word_sensitivity)
preferences.stop_word_sensitivity = initial_threshold
if args.enable_thinking_sound:
preferences.thinking_sound = 1
@@ -441,6 +445,7 @@ async def main() -> None:
output_only=args.output_only,
download_dir=args.download_dir,
volume=initial_volume,
stop_word_threshold=initial_threshold,
mic_volume=preferences.mic_volume,
mic_auto_gain=preferences.mic_auto_gain,
mic_noise_suppression=preferences.mic_noise_suppression,
+11
View File
@@ -265,3 +265,14 @@ class ServerState:
self.preferences.mic_volume = volume_int
_LOGGER.info("Saving mic_volume %s to %s", volume_int, self.preferences_path)
self.save_preferences()
def initial_stop_word_threshold(saved_sensitivity: Optional[float]) -> float:
"""
Resolve the stop word probability cutoff to start from, clamped to 0.0-1.0.
:param saved_sensitivity: Value persisted in preferences, or None if it has never been set.
"""
if saved_sensitivity is None:
return ServerState.stop_word_threshold
return max(0.0, min(1.0, float(saved_sensitivity)))