Home Assistant Unofficial Reference 2024.12.1
devices.py
Go to the documentation of this file.
1 """Class to manage devices."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Iterator
6 from dataclasses import dataclass, field
7 
8 from voip_utils import CallInfo, VoipDatagramProtocol
9 
10 from homeassistant.config_entries import ConfigEntry
11 from homeassistant.core import Event, HomeAssistant, callback
12 from homeassistant.helpers import device_registry as dr, entity_registry as er
13 
14 from .const import DOMAIN
15 
16 
17 @dataclass
18 class VoIPDevice:
19  """Class to store device."""
20 
21  voip_id: str
22  device_id: str
23  is_active: bool = False
24  update_listeners: list[Callable[[VoIPDevice], None]] = field(default_factory=list)
25  protocol: VoipDatagramProtocol | None = None
26 
27  @callback
28  def set_is_active(self, active: bool) -> None:
29  """Set active state."""
30  self.is_activeis_active = active
31  for listener in self.update_listeners:
32  listener(self)
33 
34  @callback
36  self, listener: Callable[[VoIPDevice], None]
37  ) -> Callable[[], None]:
38  """Listen for updates."""
39  self.update_listeners.append(listener)
40  return lambda: self.update_listeners.remove(listener)
41 
42  @callback
43  def async_allow_call(self, hass: HomeAssistant) -> bool:
44  """Return if call is allowed."""
45  ent_reg = er.async_get(hass)
46 
47  allowed_call_entity_id = ent_reg.async_get_entity_id(
48  "switch", DOMAIN, f"{self.voip_id}-allow_call"
49  )
50  # If 2 requests come in fast, the device registry entry has been created
51  # but entity might not exist yet.
52  if allowed_call_entity_id is None:
53  return False
54 
55  if state := hass.states.get(allowed_call_entity_id):
56  return state.state == "on"
57 
58  return False
59 
60  def get_pipeline_entity_id(self, hass: HomeAssistant) -> str | None:
61  """Return entity id for pipeline select."""
62  ent_reg = er.async_get(hass)
63  return ent_reg.async_get_entity_id("select", DOMAIN, f"{self.voip_id}-pipeline")
64 
65  def get_vad_sensitivity_entity_id(self, hass: HomeAssistant) -> str | None:
66  """Return entity id for VAD sensitivity."""
67  ent_reg = er.async_get(hass)
68  return ent_reg.async_get_entity_id(
69  "select", DOMAIN, f"{self.voip_id}-vad_sensitivity"
70  )
71 
72 
74  """Class to store devices."""
75 
76  def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
77  """Initialize VoIP devices."""
78  self.hasshass = hass
79  self.config_entryconfig_entry = config_entry
80  self._new_device_listeners: list[Callable[[VoIPDevice], None]] = []
81  self.devicesdevices: dict[str, VoIPDevice] = {}
82 
83  @callback
84  def async_setup(self) -> None:
85  """Set up devices."""
86  for device in dr.async_entries_for_config_entry(
87  dr.async_get(self.hasshass), self.config_entryconfig_entry.entry_id
88  ):
89  voip_id = next(
90  (item[1] for item in device.identifiers if item[0] == DOMAIN), None
91  )
92  if voip_id is None:
93  continue
94  self.devicesdevices[voip_id] = VoIPDevice(
95  voip_id=voip_id,
96  device_id=device.id,
97  )
98 
99  @callback
100  def async_device_removed(ev: Event[dr.EventDeviceRegistryUpdatedData]) -> None:
101  """Handle device removed."""
102  removed_id = ev.data["device_id"]
103  self.devicesdevices = {
104  voip_id: voip_device
105  for voip_id, voip_device in self.devicesdevices.items()
106  if voip_device.device_id != removed_id
107  }
108 
109  self.config_entryconfig_entry.async_on_unload(
110  self.hasshass.bus.async_listen(
111  dr.EVENT_DEVICE_REGISTRY_UPDATED,
112  async_device_removed,
113  callback(lambda event_data: event_data["action"] == "remove"),
114  )
115  )
116 
117  @callback
119  self, listener: Callable[[VoIPDevice], None]
120  ) -> None:
121  """Add a new device listener."""
122  self._new_device_listeners.append(listener)
123 
124  @callback
125  def async_get_or_create(self, call_info: CallInfo) -> VoIPDevice:
126  """Get or create a device."""
127  user_agent = call_info.headers.get("user-agent", "")
128  user_agent_parts = user_agent.split()
129  if len(user_agent_parts) == 3 and user_agent_parts[0] == "Grandstream":
130  manuf = user_agent_parts[0]
131  model = user_agent_parts[1]
132  fw_version = user_agent_parts[2]
133  else:
134  manuf = None
135  model = user_agent if user_agent else None
136  fw_version = None
137 
138  dev_reg = dr.async_get(self.hasshass)
139  voip_id = call_info.caller_ip
140  voip_device = self.devicesdevices.get(voip_id)
141 
142  if voip_device is not None:
143  device = dev_reg.async_get(voip_device.device_id)
144  if device and fw_version and device.sw_version != fw_version:
145  dev_reg.async_update_device(device.id, sw_version=fw_version)
146 
147  return voip_device
148 
149  device = dev_reg.async_get_or_create(
150  config_entry_id=self.config_entryconfig_entry.entry_id,
151  identifiers={(DOMAIN, voip_id)},
152  name=voip_id,
153  manufacturer=manuf,
154  model=model,
155  sw_version=fw_version,
156  configuration_url=f"http://{call_info.caller_ip}",
157  )
158  voip_device = self.devicesdevices[voip_id] = VoIPDevice(
159  voip_id=voip_id,
160  device_id=device.id,
161  )
162  for listener in self._new_device_listeners:
163  listener(voip_device)
164 
165  return voip_device
166 
167  def __iter__(self) -> Iterator[VoIPDevice]:
168  """Iterate over devices."""
169  return iter(self.devicesdevices.values())
Callable[[], None] async_listen_update(self, Callable[[VoIPDevice], None] listener)
Definition: devices.py:37
bool async_allow_call(self, HomeAssistant hass)
Definition: devices.py:43
str|None get_vad_sensitivity_entity_id(self, HomeAssistant hass)
Definition: devices.py:65
str|None get_pipeline_entity_id(self, HomeAssistant hass)
Definition: devices.py:60
None async_add_new_device_listener(self, Callable[[VoIPDevice], None] listener)
Definition: devices.py:120
VoIPDevice async_get_or_create(self, CallInfo call_info)
Definition: devices.py:125
None __init__(self, HomeAssistant hass, ConfigEntry config_entry)
Definition: devices.py:76
bool remove(self, _T matcher)
Definition: match.py:214
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88