Update __main__.py

Extended Preferences loading to initialize and backfill a new alarm_duration_seconds field (default 0).

Added a set_alarm_duration event handler to MicMuteHandler which:

validates and normalizes the duration value,

updates state.preferences.alarm_duration_seconds,

persists the updated preferences to disk.

Kept the existing “single writer” pattern (MicMuteHandler) for preferences/state updates.
This commit is contained in:
imonlinux
2025-11-29 14:37:18 -08:00
committed by GitHub
parent 5931370c63
commit b43b036198
+62 -8
View File
@@ -48,7 +48,7 @@ class MediaPlayers:
tts: MpvMediaPlayer tts: MpvMediaPlayer
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Mic Mute Handler # Mic Mute / Preferences Handler
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class MicMuteHandler(EventHandler): class MicMuteHandler(EventHandler):
@@ -98,6 +98,45 @@ class MicMuteHandler(EventHandler):
self.state.preferences.num_leds = num_leds self.state.preferences.num_leds = num_leds
self.state.save_preferences() self.state.save_preferences()
@subscribe
def set_alarm_duration(self, data: dict):
"""
Event handler to save alarm_duration_seconds to preferences.
Expected payload from MQTT controller:
{ "alarm_duration_seconds": <int> }
Semantics:
0 -> infinite alarm (only stop via Stop wake word / wake word logic)
>0 -> auto-stop alarm after N seconds (plus Stop wake word support)
"""
duration = data.get("alarm_duration_seconds")
if duration is None:
return
try:
duration = int(duration)
except (TypeError, ValueError):
_LOGGER.warning(
"Invalid alarm_duration_seconds value received: %r", duration
)
return
if duration < 0:
_LOGGER.warning(
"Negative alarm_duration_seconds (%d) is not allowed; ignoring",
duration,
)
return
current = getattr(self.state.preferences, "alarm_duration_seconds", 0)
if current != duration:
_LOGGER.debug(
"Updating alarm_duration_seconds: %s -> %s", current, duration
)
self.state.preferences.alarm_duration_seconds = duration
self.state.save_preferences()
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Main Application # Main Application
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -166,7 +205,10 @@ def _init_basics() -> Tuple[Config, asyncio.AbstractEventLoop, EventBus]:
for idx, mic in enumerate(sc.all_microphones(include_loopback=False)): for idx, mic in enumerate(sc.all_microphones(include_loopback=False)):
print(f"[{idx}]", mic.name) print(f"[{idx}]", mic.name)
except Exception as e: except Exception as e:
_LOGGER.error("Error listing input devices (ensure audio backend is working): %s", e) _LOGGER.error(
"Error listing input devices (ensure audio backend is working): %s",
e,
)
sys.exit(0) sys.exit(0)
if args.list_output_devices: if args.list_output_devices:
@@ -210,7 +252,13 @@ def _load_preferences(config: Config) -> Preferences:
else: else:
preferences = Preferences() preferences = Preferences()
preferences.num_leds = getattr(preferences, 'num_leds', config.led.num_leds) # Backwards-compatible defaults / migrations
preferences.num_leds = getattr(preferences, "num_leds", config.led.num_leds)
# New: default alarm_duration_seconds, 0 = infinite until Stop/wake word
preferences.alarm_duration_seconds = getattr(
preferences, "alarm_duration_seconds", 0
)
return preferences return preferences
def _get_microphone(config: Config): def _get_microphone(config: Config):
@@ -294,17 +342,21 @@ def _load_wake_words(config: Config, preferences: Preferences) -> WakeWordData:
return WakeWordData(available, models, active, stop_model) return WakeWordData(available, models, active, stop_model)
def _init_media_players(loop: asyncio.AbstractEventLoop, config: Config, preferences: Preferences) -> MediaPlayers: def _init_media_players(
loop: asyncio.AbstractEventLoop,
config: Config,
preferences: Preferences,
) -> MediaPlayers:
"""Initializes the music and TTS media players.""" """Initializes the music and TTS media players."""
music_player = MpvMediaPlayer( music_player = MpvMediaPlayer(
loop=loop, loop=loop,
device=config.audio.output_device, device=config.audio.output_device,
initial_volume=preferences.volume_level initial_volume=preferences.volume_level,
) )
tts_player = MpvMediaPlayer( tts_player = MpvMediaPlayer(
loop=loop, loop=loop,
device=config.audio.output_device, device=config.audio.output_device,
initial_volume=preferences.volume_level initial_volume=preferences.volume_level,
) )
return MediaPlayers(music=music_player, tts=tts_player) return MediaPlayers(music=music_player, tts=tts_player)
@@ -377,13 +429,15 @@ async def _run_server(state: ServerState, config: Config):
server = await state.loop.create_server( server = await state.loop.create_server(
lambda: VoiceSatelliteProtocol(state), lambda: VoiceSatelliteProtocol(state),
host=config.esphome.host, host=config.esphome.host,
port=config.esphome.port port=config.esphome.port,
) )
discovery = HomeAssistantZeroconf(port=config.esphome.port, name=config.app.name) discovery = HomeAssistantZeroconf(port=config.esphome.port, name=config.app.name)
await discovery.register_server() await discovery.register_server()
async with server: async with server:
_LOGGER.info("Server started (host=%s, port=%s)", config.esphome.host, config.esphome.port) _LOGGER.info(
"Server started (host=%s, port=%s)", config.esphome.host, config.esphome.port
)
await server.serve_forever() await server.serve_forever()