Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Provide functionality to wake word."""
2 
3 from __future__ import annotations
4 
5 from abc import abstractmethod
6 import asyncio
7 from collections.abc import AsyncIterable
8 import logging
9 from typing import final
10 
11 import voluptuous as vol
12 
13 from homeassistant.components import websocket_api
14 from homeassistant.config_entries import ConfigEntry
15 from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, EntityCategory
16 from homeassistant.core import HomeAssistant, callback
17 from homeassistant.helpers import config_validation as cv
18 from homeassistant.helpers.entity_component import EntityComponent
19 from homeassistant.helpers.restore_state import RestoreEntity
20 from homeassistant.helpers.typing import ConfigType
21 from homeassistant.util import dt as dt_util
22 from homeassistant.util.hass_dict import HassKey
23 
24 from .const import DOMAIN
25 from .models import DetectionResult, WakeWord
26 
27 __all__ = [
28  "async_default_entity",
29  "async_get_wake_word_detection_entity",
30  "DetectionResult",
31  "DOMAIN",
32  "WakeWord",
33  "WakeWordDetectionEntity",
34 ]
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
39 DATA_COMPONENT: HassKey[EntityComponent[WakeWordDetectionEntity]] = HassKey(DOMAIN)
40 
41 TIMEOUT_FETCH_WAKE_WORDS = 10
42 
43 
44 @callback
45 def async_default_entity(hass: HomeAssistant) -> str | None:
46  """Return the entity id of the default engine."""
47  return next(iter(hass.states.async_entity_ids(DOMAIN)), None)
48 
49 
50 @callback
52  hass: HomeAssistant, entity_id: str
53 ) -> WakeWordDetectionEntity | None:
54  """Return wake word entity."""
55  return hass.data[DATA_COMPONENT].get_entity(entity_id)
56 
57 
58 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
59  """Set up wake word."""
60  websocket_api.async_register_command(hass, websocket_entity_info)
61 
62  component = hass.data[DATA_COMPONENT] = EntityComponent[WakeWordDetectionEntity](
63  _LOGGER, DOMAIN, hass
64  )
65  component.register_shutdown()
66 
67  return True
68 
69 
70 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
71  """Set up a config entry."""
72  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
73 
74 
75 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
76  """Unload a config entry."""
77  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
78 
79 
81  """Represent a single wake word provider."""
82 
83  _attr_entity_category = EntityCategory.DIAGNOSTIC
84  _attr_should_poll = False
85  __last_detected: str | None = None
86 
87  @property
88  @final
89  def state(self) -> str | None:
90  """Return the state of the entity."""
91  return self.__last_detected__last_detected
92 
93  @abstractmethod
94  async def get_supported_wake_words(self) -> list[WakeWord]:
95  """Return a list of supported wake words."""
96 
97  @abstractmethod
99  self, stream: AsyncIterable[tuple[bytes, int]], wake_word_id: str | None
100  ) -> DetectionResult | None:
101  """Try to detect wake word(s) in an audio stream with timestamps.
102 
103  Audio must be 16Khz sample rate with 16-bit mono PCM samples.
104  """
105 
107  self, stream: AsyncIterable[tuple[bytes, int]], wake_word_id: str | None
108  ) -> DetectionResult | None:
109  """Try to detect wake word(s) in an audio stream with timestamps.
110 
111  Audio must be 16Khz sample rate with 16-bit mono PCM samples.
112  """
113  result = await self._async_process_audio_stream_async_process_audio_stream(stream, wake_word_id)
114  if result is not None:
115  # Update last detected only when there is a detection
116  self.__last_detected__last_detected = dt_util.utcnow().isoformat()
117  self.async_write_ha_stateasync_write_ha_state()
118 
119  return result
120 
121  async def async_internal_added_to_hass(self) -> None:
122  """Call when the entity is added to hass."""
123  await super().async_internal_added_to_hass()
124  state = await self.async_get_last_stateasync_get_last_state()
125  if (
126  state is not None
127  and state.state is not None
128  and state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
129  ):
130  self.__last_detected__last_detected = state.state
131 
132 
133 @websocket_api.websocket_command( { "type": "wake_word/info", vol.Required("entity_id"): cv.entity_domain(DOMAIN),
134  }
135 )
136 @websocket_api.async_response
137 async def websocket_entity_info(
138  hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
139 ) -> None:
140  """Get info about wake word entity."""
141  entity = hass.data[DATA_COMPONENT].get_entity(msg["entity_id"])
142 
143  if entity is None:
144  connection.send_error(
145  msg["id"], websocket_api.ERR_NOT_FOUND, "Entity not found"
146  )
147  return
148 
149  try:
150  async with asyncio.timeout(TIMEOUT_FETCH_WAKE_WORDS):
151  wake_words = await entity.get_supported_wake_words()
152  except TimeoutError:
153  connection.send_error(
154  msg["id"], websocket_api.ERR_TIMEOUT, "Timeout fetching wake words"
155  )
156  return
157 
158  connection.send_result(
159  msg["id"],
160  {"wake_words": wake_words},
161  )
162 
DetectionResult|None _async_process_audio_stream(self, AsyncIterable[tuple[bytes, int]] stream, str|None wake_word_id)
Definition: __init__.py:100
DetectionResult|None async_process_audio_stream(self, AsyncIterable[tuple[bytes, int]] stream, str|None wake_word_id)
Definition: __init__.py:108
CalendarEntity get_entity(HomeAssistant hass, str entity_id)
Definition: trigger.py:96
None websocket_entity_info(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg)
Definition: __init__.py:142
str|None async_default_entity(HomeAssistant hass)
Definition: __init__.py:45
WakeWordDetectionEntity|None async_get_wake_word_detection_entity(HomeAssistant hass, str entity_id)
Definition: __init__.py:53
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:75
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:58
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:70