Leds and buttons events (#266)
Use events and commands to control custom onboard leds and buttons. Can be used to communicate with an external Python script via a WebSocket API running in a container alongside LVA.
This commit is contained in:
1 parent
e1770d071c
commit
2d460b6728
51 files changed
+9251
-117
No files matched your search
@@ -6,14 +6,16 @@ from dataclasses import asdict, dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pymicro_wakeword import MicroWakeWord
|
||||
from pyopen_wakeword import OpenWakeWord
|
||||
|
||||
from .entity import (
|
||||
ButtonEventSensorEntity,
|
||||
ESPHomeEntity,
|
||||
LEDLightEntity,
|
||||
MediaPlayerEntity,
|
||||
MicSettingEntity,
|
||||
MuteSwitchEntity,
|
||||
@@ -59,6 +61,23 @@ class AvailableWakeWord:
|
||||
raise ValueError(f"Unexpected wake word type: {self.type}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class LightRegistration:
|
||||
"""Capabilities a peripheral declares for one of its Light entities.
|
||||
|
||||
The peripheral sends this with the register_light command after
|
||||
connecting. LVA materialises a matching LEDLightEntity so HA can
|
||||
control it.
|
||||
"""
|
||||
|
||||
name: str
|
||||
object_id: str
|
||||
icon: str = "mdi:led-strip-variant"
|
||||
effects: List[str] = field(default_factory=list)
|
||||
supports_rgb: bool = True
|
||||
supports_brightness: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class Preferences:
|
||||
active_wake_words: List[Optional[str]] = field(default_factory=list)
|
||||
@@ -91,10 +110,14 @@ class ServerState:
|
||||
music_player: "MpvMediaPlayer"
|
||||
tts_player: "MpvMediaPlayer"
|
||||
wakeup_sound: str
|
||||
start_listening_sound: str
|
||||
processing_sound: str
|
||||
timer_finished_sound: str
|
||||
mute_sound: str
|
||||
unmute_sound: str
|
||||
button_double_press_sound: str
|
||||
button_triple_press_sound: str
|
||||
button_long_press_sound: str
|
||||
preferences: Preferences
|
||||
preferences_path: Path
|
||||
download_dir: Path
|
||||
@@ -104,6 +127,26 @@ class ServerState:
|
||||
satellite: "Optional[VoiceSatelliteProtocol]" = None
|
||||
mute_switch_entity: "Optional[MuteSwitchEntity]" = None
|
||||
thinking_sound_entity: "Optional[ThinkingSoundEntity]" = None
|
||||
button_event_sensor_entity: "Optional[ButtonEventSensorEntity]" = None
|
||||
|
||||
# Lights declared by peripherals via register_light. Survives HA
|
||||
# reconnects so the satellite can rebuild its entities whenever it
|
||||
# is constructed again.
|
||||
pending_lights: "List[LightRegistration]" = field(default_factory=list)
|
||||
# Materialised LightEntities keyed by object_id, so light_command
|
||||
# events can be routed back to the right peripheral hardware.
|
||||
led_light_entities: "Dict[str, LEDLightEntity]" = field(default_factory=dict)
|
||||
|
||||
# True once a peripheral sends register_button. Gates creation of
|
||||
# ButtonEventSensorEntity so the HA device page only shows the button
|
||||
# entity when hardware that actually supports button presses is present.
|
||||
# Survives HA reconnects so the entity is re-registered automatically.
|
||||
pending_button: bool = False
|
||||
|
||||
# Optional peripheral WebSocket API (LEDs, buttons, HAT boards).
|
||||
# Assigned in __main__ before the event loop starts.
|
||||
peripheral_api: "Optional[Any]" = None # PeripheralAPIServer at runtime
|
||||
|
||||
sensitivity_1_number_entity: "Optional[WakeWord1SensitivityNumberEntity]" = None
|
||||
sensitivity_2_number_entity: "Optional[WakeWord2SensitivityNumberEntity]" = None
|
||||
stop_sensitivity_number_entity: "Optional[StopWordSensitivityNumberEntity]" = None
|
||||
@@ -161,6 +204,13 @@ class ServerState:
|
||||
self.save_preferences()
|
||||
_LOGGER.info("Volume saved successfully")
|
||||
|
||||
# Notify peripheral container (thread-safe; may be called from mpv callbacks)
|
||||
api = self.peripheral_api
|
||||
if api is not None:
|
||||
from .peripheral_api import LVAEvent # local import avoids circular dep
|
||||
|
||||
api.emit_event_sync(LVAEvent.VOLUME_CHANGED, {"volume": round(clamped_volume, 3)})
|
||||
|
||||
def persist_mic_gain(self, gain: float) -> None:
|
||||
"""Persist the microphone auto gain value."""
|
||||
gain_int = int(gain)
|
||||
|
||||
Reference in new issue
Block a user