Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Reolink parent entity class."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 
8 from reolink_aio.api import DUAL_LENS_MODELS, Chime, Host
9 
10 from homeassistant.core import callback
11 from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
12 from homeassistant.helpers.entity import EntityDescription
14  CoordinatorEntity,
15  DataUpdateCoordinator,
16 )
17 
18 from . import ReolinkData
19 from .const import DOMAIN
20 
21 
22 @dataclass(frozen=True, kw_only=True)
24  """A class that describes entities for Reolink."""
25 
26  cmd_key: str | None = None
27  cmd_id: int | None = None
28 
29 
30 @dataclass(frozen=True, kw_only=True)
32  """A class that describes entities for a camera channel."""
33 
34  supported: Callable[[Host, int], bool] = lambda api, ch: True
35 
36 
37 @dataclass(frozen=True, kw_only=True)
39  """A class that describes host entities."""
40 
41  supported: Callable[[Host], bool] = lambda api: True
42 
43 
44 @dataclass(frozen=True, kw_only=True)
46  """A class that describes entities for a chime."""
47 
48  supported: Callable[[Chime], bool] = lambda chime: True
49 
50 
51 class ReolinkHostCoordinatorEntity(CoordinatorEntity[DataUpdateCoordinator[None]]):
52  """Parent class for entities that control the Reolink NVR itself, without a channel.
53 
54  A camera connected directly to HomeAssistant without using a NVR is in the reolink API
55  basically a NVR with a single channel that has the camera connected to that channel.
56  """
57 
58  _attr_has_entity_name = True
59  entity_description: ReolinkEntityDescription
60 
61  def __init__(
62  self,
63  reolink_data: ReolinkData,
64  coordinator: DataUpdateCoordinator[None] | None = None,
65  ) -> None:
66  """Initialize ReolinkHostCoordinatorEntity."""
67  if coordinator is None:
68  coordinator = reolink_data.device_coordinator
69  super().__init__(coordinator)
70 
71  self._host_host = reolink_data.host
72  self._attr_unique_id_attr_unique_id = f"{self._host.unique_id}_{self.entity_description.key}"
73 
74  http_s = "https" if self._host_host.api.use_https else "http"
75  self._conf_url_conf_url = f"{http_s}://{self._host.api.host}:{self._host.api.port}"
76  self._dev_id_dev_id = self._host_host.unique_id
77  self._attr_device_info_attr_device_info = DeviceInfo(
78  identifiers={(DOMAIN, self._dev_id_dev_id)},
79  connections={(CONNECTION_NETWORK_MAC, self._host_host.api.mac_address)},
80  name=self._host_host.api.nvr_name,
81  model=self._host_host.api.model,
82  model_id=self._host_host.api.item_number,
83  manufacturer=self._host_host.api.manufacturer,
84  hw_version=self._host_host.api.hardware_version,
85  sw_version=self._host_host.api.sw_version,
86  serial_number=self._host_host.api.uid,
87  configuration_url=self._conf_url_conf_url,
88  )
89 
90  @property
91  def available(self) -> bool:
92  """Return True if entity is available."""
93  return self._host_host.api.session_active and super().available
94 
95  @callback
96  def _push_callback(self) -> None:
97  """Handle incoming TCP push event."""
98  self.async_write_ha_state()
99 
100  def register_callback(self, unique_id: str, cmd_id: int) -> None:
101  """Register callback for TCP push events."""
102  self._host_host.api.baichuan.register_callback( # pragma: no cover
103  unique_id, self._push_callback_push_callback, cmd_id
104  )
105 
106  async def async_added_to_hass(self) -> None:
107  """Entity created."""
108  await super().async_added_to_hass()
109  cmd_key = self.entity_description.cmd_key
110  cmd_id = self.entity_description.cmd_id
111  if cmd_key is not None:
112  self._host_host.async_register_update_cmd(cmd_key)
113  if cmd_id is not None and self._attr_unique_id_attr_unique_id is not None:
114  self.register_callbackregister_callback(self._attr_unique_id_attr_unique_id, cmd_id)
115 
116  async def async_will_remove_from_hass(self) -> None:
117  """Entity removed."""
118  cmd_key = self.entity_description.cmd_key
119  cmd_id = self.entity_description.cmd_id
120  if cmd_key is not None:
121  self._host_host.async_unregister_update_cmd(cmd_key)
122  if cmd_id is not None and self._attr_unique_id_attr_unique_id is not None:
123  self._host_host.api.baichuan.unregister_callback(self._attr_unique_id_attr_unique_id)
124 
125  await super().async_will_remove_from_hass()
126 
127  async def async_update(self) -> None:
128  """Force full update from the generic entity update service."""
129  self._host_host.last_wake = 0
130  await super().async_update()
131 
132 
134  """Parent class for Reolink hardware camera entities connected to a channel of the NVR."""
135 
136  def __init__(
137  self,
138  reolink_data: ReolinkData,
139  channel: int,
140  coordinator: DataUpdateCoordinator[None] | None = None,
141  ) -> None:
142  """Initialize ReolinkChannelCoordinatorEntity for a hardware camera connected to a channel of the NVR."""
143  super().__init__(reolink_data, coordinator)
144 
145  self._channel_channel = channel
146  if self._host_host.api.supported(channel, "UID"):
147  self._attr_unique_id_attr_unique_id_attr_unique_id = f"{self._host.unique_id}_{self._host.api.camera_uid(channel)}_{self.entity_description.key}"
148  else:
149  self._attr_unique_id_attr_unique_id_attr_unique_id = (
150  f"{self._host.unique_id}_{channel}_{self.entity_description.key}"
151  )
152 
153  dev_ch = channel
154  if self._host_host.api.model in DUAL_LENS_MODELS:
155  dev_ch = 0
156 
157  if self._host_host.api.is_nvr:
158  if self._host_host.api.supported(dev_ch, "UID"):
159  self._dev_id_dev_id_dev_id = (
160  f"{self._host.unique_id}_{self._host.api.camera_uid(dev_ch)}"
161  )
162  else:
163  self._dev_id_dev_id_dev_id = f"{self._host.unique_id}_ch{dev_ch}"
164 
166  identifiers={(DOMAIN, self._dev_id_dev_id_dev_id)},
167  via_device=(DOMAIN, self._host_host.unique_id),
168  name=self._host_host.api.camera_name(dev_ch),
169  model=self._host_host.api.camera_model(dev_ch),
170  manufacturer=self._host_host.api.manufacturer,
171  hw_version=self._host_host.api.camera_hardware_version(dev_ch),
172  sw_version=self._host_host.api.camera_sw_version(dev_ch),
173  serial_number=self._host_host.api.camera_uid(dev_ch),
174  configuration_url=self._conf_url_conf_url,
175  )
176 
177  @property
178  def available(self) -> bool:
179  """Return True if entity is available."""
180  return super().available and self._host_host.api.camera_online(self._channel_channel)
181 
182  def register_callback(self, unique_id: str, cmd_id: int) -> None:
183  """Register callback for TCP push events."""
184  self._host_host.api.baichuan.register_callback(
185  unique_id, self._push_callback_push_callback, cmd_id, self._channel_channel
186  )
187 
188  async def async_added_to_hass(self) -> None:
189  """Entity created."""
190  await super().async_added_to_hass()
191  cmd_key = self.entity_description.cmd_key
192  if cmd_key is not None:
193  self._host_host.async_register_update_cmd(cmd_key, self._channel_channel)
194 
195  async def async_will_remove_from_hass(self) -> None:
196  """Entity removed."""
197  cmd_key = self.entity_description.cmd_key
198  if cmd_key is not None:
199  self._host_host.async_unregister_update_cmd(cmd_key, self._channel_channel)
200 
201  await super().async_will_remove_from_hass()
202 
203 
205  """Parent class for Reolink chime entities connected."""
206 
207  def __init__(
208  self,
209  reolink_data: ReolinkData,
210  chime: Chime,
211  coordinator: DataUpdateCoordinator[None] | None = None,
212  ) -> None:
213  """Initialize ReolinkChimeCoordinatorEntity for a chime."""
214  super().__init__(reolink_data, chime.channel, coordinator)
215 
216  self._chime_chime = chime
217 
219  f"{self._host.unique_id}_chime{chime.dev_id}_{self.entity_description.key}"
220  )
221  cam_dev_id = self._dev_id_dev_id_dev_id_dev_id
222  self._dev_id_dev_id_dev_id_dev_id = f"{self._host.unique_id}_chime{chime.dev_id}"
223 
225  identifiers={(DOMAIN, self._dev_id_dev_id_dev_id_dev_id)},
226  via_device=(DOMAIN, cam_dev_id),
227  name=chime.name,
228  model="Reolink Chime",
229  manufacturer=self._host_host.api.manufacturer,
230  serial_number=str(chime.dev_id),
231  configuration_url=self._conf_url_conf_url,
232  )
233 
234  @property
235  def available(self) -> bool:
236  """Return True if entity is available."""
237  return self._chime_chime.online and super().available