Save preferences to JSON file

This commit is contained in:
Michael Hansen
2025-09-15 11:06:09 -05:00
parent ea901a1c14
commit 2c937e3cb3
5 changed files with 58 additions and 4 deletions
+1
View File
@@ -16,3 +16,4 @@ venv/
*.egg-info/ *.egg-info/
dist/ dist/
preferences.json
+5
View File
@@ -1,5 +1,10 @@
# Changelog # Changelog
## Unreleased
- Save active wake words to preferences JSON file
- Refactor main into separate files
## 1.0.0 ## 1.0.0
- Initial release - Initial release
+23 -2
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import asyncio import asyncio
import json import json
@@ -12,7 +11,7 @@ from typing import Dict
import sounddevice as sd import sounddevice as sd
from .microwakeword import MicroWakeWord from .microwakeword import MicroWakeWord
from .models import AvailableWakeWord, ServerState from .models import AvailableWakeWord, Preferences, ServerState
from .mpv_player import MpvMediaPlayer from .mpv_player import MpvMediaPlayer
from .satellite import VoiceSatelliteProtocol from .satellite import VoiceSatelliteProtocol
from .util import get_mac, is_arm from .util import get_mac, is_arm
@@ -57,6 +56,9 @@ async def main() -> None:
parser.add_argument( parser.add_argument(
"--timer-finished-sound", default=str(_SOUNDS_DIR / "timer_finished.flac") "--timer-finished-sound", default=str(_SOUNDS_DIR / "timer_finished.flac")
) )
#
parser.add_argument("--preferences-file", default=_REPO_DIR / "preferences.json")
#
parser.add_argument( parser.add_argument(
"--host", "--host",
default="0.0.0.0", default="0.0.0.0",
@@ -93,11 +95,28 @@ async def main() -> None:
_LOGGER.debug("Available wake words: %s", list(sorted(available_wake_words.keys()))) _LOGGER.debug("Available wake words: %s", list(sorted(available_wake_words.keys())))
# Load preferences
preferences_path = Path(args.preferences_file)
if preferences_path.exists():
_LOGGER.debug("Loading preferences: %s", preferences_path)
with open(preferences_path, "r", encoding="utf-8") as preferences_file:
preferences_dict = json.load(preferences_file)
preferences = Preferences(**preferences_dict)
else:
preferences = Preferences()
libtensorflowlite_c_path = _LIB_DIR / "libtensorflowlite_c.so" libtensorflowlite_c_path = _LIB_DIR / "libtensorflowlite_c.so"
_LOGGER.debug("libtensorflowlite_c path: %s", libtensorflowlite_c_path) _LOGGER.debug("libtensorflowlite_c path: %s", libtensorflowlite_c_path)
# Load wake/stop models # Load wake/stop models
wake_config_path = wake_word_dir / f"{args.wake_model}.json" wake_config_path = wake_word_dir / f"{args.wake_model}.json"
if preferences.active_wake_words:
wake_word_id = preferences.active_wake_words[0]
maybe_wake_config_path = wake_word_dir / f"{wake_word_id}.json"
if maybe_wake_config_path.exists():
# Override with last set
wake_config_path = maybe_wake_config_path
_LOGGER.debug("Loading wake model: %s", wake_config_path) _LOGGER.debug("Loading wake model: %s", wake_config_path)
wake_model = MicroWakeWord.from_config(wake_config_path, libtensorflowlite_c_path) wake_model = MicroWakeWord.from_config(wake_config_path, libtensorflowlite_c_path)
@@ -117,6 +136,8 @@ async def main() -> None:
tts_player=MpvMediaPlayer(device=args.audio_output_device), tts_player=MpvMediaPlayer(device=args.audio_output_device),
wakeup_sound=args.wakeup_sound, wakeup_sound=args.wakeup_sound,
timer_finished_sound=args.timer_finished_sound, timer_finished_sound=args.timer_finished_sound,
preferences=preferences,
preferences_path=preferences_path,
) )
process_audio_thread = threading.Thread( process_audio_thread = threading.Thread(
+21 -1
View File
@@ -1,6 +1,8 @@
"""Shared models.""" """Shared models."""
from dataclasses import dataclass import json
import logging
from dataclasses import asdict, dataclass, field
from pathlib import Path from pathlib import Path
from queue import Queue from queue import Queue
from typing import TYPE_CHECKING, Dict, List, Optional from typing import TYPE_CHECKING, Dict, List, Optional
@@ -11,6 +13,8 @@ if TYPE_CHECKING:
from .mpv_player import MpvMediaPlayer from .mpv_player import MpvMediaPlayer
from .satellite import VoiceSatelliteProtocol from .satellite import VoiceSatelliteProtocol
_LOGGER = logging.getLogger(__name__)
@dataclass @dataclass
class AvailableWakeWord: class AvailableWakeWord:
@@ -20,6 +24,11 @@ class AvailableWakeWord:
config_path: Path config_path: Path
@dataclass
class Preferences:
active_wake_words: List[str] = field(default_factory=list)
@dataclass @dataclass
class ServerState: class ServerState:
name: str name: str
@@ -33,5 +42,16 @@ class ServerState:
tts_player: "MpvMediaPlayer" tts_player: "MpvMediaPlayer"
wakeup_sound: str wakeup_sound: str
timer_finished_sound: str timer_finished_sound: str
preferences: Preferences
preferences_path: Path
media_player_entity: "Optional[MediaPlayerEntity]" = None media_player_entity: "Optional[MediaPlayerEntity]" = None
satellite: "Optional[VoiceSatelliteProtocol]" = None satellite: "Optional[VoiceSatelliteProtocol]" = None
def save_preferences(self) -> None:
"""Save preferences as JSON."""
_LOGGER.debug("Saving preferences: %s", self.preferences_path)
self.preferences_path.parent.mkdir(parents=True, exist_ok=True)
with open(self.preferences_path, "w", encoding="utf-8") as preferences_file:
json.dump(
asdict(self.preferences), preferences_file, ensure_ascii=False, indent=4
)
+8 -1
View File
@@ -3,7 +3,7 @@
import logging import logging
import time import time
from collections.abc import Iterable from collections.abc import Iterable
from typing import Dict, Optional from typing import Dict, Optional, Set
# pylint: disable=no-name-in-module # pylint: disable=no-name-in-module
from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined] from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined]
@@ -181,9 +181,12 @@ class VoiceSatelliteProtocol(APIServer):
_LOGGER.info("Connected to Home Assistant") _LOGGER.info("Connected to Home Assistant")
elif isinstance(msg, VoiceAssistantSetConfiguration): elif isinstance(msg, VoiceAssistantSetConfiguration):
# TODO: support multiple wake words # TODO: support multiple wake words
active_wake_words: Set[str] = set()
for wake_word_id in msg.active_wake_words: for wake_word_id in msg.active_wake_words:
if wake_word_id == self.state.wake_word.id: if wake_word_id == self.state.wake_word.id:
# Already active # Already active
active_wake_words.add(wake_word_id)
break break
model_info = self.state.available_wake_words.get(wake_word_id) model_info = self.state.available_wake_words.get(wake_word_id)
@@ -197,8 +200,12 @@ class VoiceSatelliteProtocol(APIServer):
) )
_LOGGER.info("Wake word set: %s", self.state.wake_word.wake_word) _LOGGER.info("Wake word set: %s", self.state.wake_word.wake_word)
active_wake_words.add(wake_word_id)
break break
self.state.preferences.active_wake_words = list(active_wake_words)
self.state.save_preferences()
def handle_audio(self, audio_chunk: bytes) -> None: def handle_audio(self, audio_chunk: bytes) -> None:
if not self._is_streaming_audio: if not self._is_streaming_audio: