Home Assistant Unofficial Reference 2024.12.1
button.py
Go to the documentation of this file.
1 """Component providing support for Reolink button entities."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from typing import Any
8 
9 from reolink_aio.api import GuardEnum, Host, PtzEnum
10 from reolink_aio.exceptions import ReolinkError
11 import voluptuous as vol
12 
14  ButtonDeviceClass,
15  ButtonEntity,
16  ButtonEntityDescription,
17 )
18 from homeassistant.components.camera import CameraEntityFeature
19 from homeassistant.const import EntityCategory
20 from homeassistant.core import HomeAssistant
21 from homeassistant.exceptions import HomeAssistantError
22 from homeassistant.helpers import config_validation as cv
24  AddEntitiesCallback,
25  async_get_current_platform,
26 )
27 
28 from .entity import (
29  ReolinkChannelCoordinatorEntity,
30  ReolinkChannelEntityDescription,
31  ReolinkHostCoordinatorEntity,
32  ReolinkHostEntityDescription,
33 )
34 from .util import ReolinkConfigEntry, ReolinkData
35 
36 PARALLEL_UPDATES = 0
37 ATTR_SPEED = "speed"
38 SUPPORT_PTZ_SPEED = CameraEntityFeature.STREAM
39 SERVICE_PTZ_MOVE = "ptz_move"
40 
41 
42 @dataclass(frozen=True, kw_only=True)
44  ButtonEntityDescription,
45  ReolinkChannelEntityDescription,
46 ):
47  """A class that describes button entities for a camera channel."""
48 
49  enabled_default: Callable[[Host, int], bool] | None = None
50  method: Callable[[Host, int], Any]
51  ptz_cmd: str | None = None
52 
53 
54 @dataclass(frozen=True, kw_only=True)
56  ButtonEntityDescription,
57  ReolinkHostEntityDescription,
58 ):
59  """A class that describes button entities for the host."""
60 
61  method: Callable[[Host], Any]
62 
63 
64 BUTTON_ENTITIES = (
66  key="ptz_stop",
67  translation_key="ptz_stop",
68  enabled_default=lambda api, ch: api.supported(ch, "pan_tilt"),
69  supported=lambda api, ch: (
70  api.supported(ch, "pan_tilt") or api.supported(ch, "zoom_basic")
71  ),
72  method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.stop.value),
73  ),
75  key="ptz_left",
76  translation_key="ptz_left",
77  supported=lambda api, ch: api.supported(ch, "pan"),
78  method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.left.value),
79  ptz_cmd=PtzEnum.left.value,
80  ),
82  key="ptz_right",
83  translation_key="ptz_right",
84  supported=lambda api, ch: api.supported(ch, "pan"),
85  method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.right.value),
86  ptz_cmd=PtzEnum.right.value,
87  ),
89  key="ptz_up",
90  translation_key="ptz_up",
91  supported=lambda api, ch: api.supported(ch, "tilt"),
92  method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.up.value),
93  ptz_cmd=PtzEnum.up.value,
94  ),
96  key="ptz_down",
97  translation_key="ptz_down",
98  supported=lambda api, ch: api.supported(ch, "tilt"),
99  method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.down.value),
100  ptz_cmd=PtzEnum.down.value,
101  ),
103  key="ptz_zoom_in",
104  translation_key="ptz_zoom_in",
105  entity_registry_enabled_default=False,
106  supported=lambda api, ch: api.supported(ch, "zoom_basic"),
107  method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.zoomin.value),
108  ptz_cmd=PtzEnum.zoomin.value,
109  ),
111  key="ptz_zoom_out",
112  translation_key="ptz_zoom_out",
113  entity_registry_enabled_default=False,
114  supported=lambda api, ch: api.supported(ch, "zoom_basic"),
115  method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.zoomout.value),
116  ptz_cmd=PtzEnum.zoomout.value,
117  ),
119  key="ptz_calibrate",
120  translation_key="ptz_calibrate",
121  entity_category=EntityCategory.CONFIG,
122  supported=lambda api, ch: api.supported(ch, "ptz_callibrate"),
123  method=lambda api, ch: api.ptz_callibrate(ch),
124  ),
126  key="guard_go_to",
127  translation_key="guard_go_to",
128  supported=lambda api, ch: api.supported(ch, "ptz_guard"),
129  method=lambda api, ch: api.set_ptz_guard(ch, command=GuardEnum.goto.value),
130  ),
132  key="guard_set",
133  translation_key="guard_set",
134  entity_category=EntityCategory.CONFIG,
135  supported=lambda api, ch: api.supported(ch, "ptz_guard"),
136  method=lambda api, ch: api.set_ptz_guard(ch, command=GuardEnum.set.value),
137  ),
138 )
139 
140 HOST_BUTTON_ENTITIES = (
142  key="reboot",
143  device_class=ButtonDeviceClass.RESTART,
144  entity_category=EntityCategory.CONFIG,
145  entity_registry_enabled_default=False,
146  supported=lambda api: api.supported(None, "reboot"),
147  method=lambda api: api.reboot(),
148  ),
149 )
150 
151 
153  hass: HomeAssistant,
154  config_entry: ReolinkConfigEntry,
155  async_add_entities: AddEntitiesCallback,
156 ) -> None:
157  """Set up a Reolink button entities."""
158  reolink_data: ReolinkData = config_entry.runtime_data
159 
160  entities: list[ReolinkButtonEntity | ReolinkHostButtonEntity] = [
161  ReolinkButtonEntity(reolink_data, channel, entity_description)
162  for entity_description in BUTTON_ENTITIES
163  for channel in reolink_data.host.api.channels
164  if entity_description.supported(reolink_data.host.api, channel)
165  ]
166  entities.extend(
167  ReolinkHostButtonEntity(reolink_data, entity_description)
168  for entity_description in HOST_BUTTON_ENTITIES
169  if entity_description.supported(reolink_data.host.api)
170  )
171  async_add_entities(entities)
172 
173  platform = async_get_current_platform()
174  platform.async_register_entity_service(
175  SERVICE_PTZ_MOVE,
176  {vol.Required(ATTR_SPEED): cv.positive_int},
177  "async_ptz_move",
178  [SUPPORT_PTZ_SPEED],
179  )
180 
181 
183  """Base button entity class for Reolink IP cameras."""
184 
185  entity_description: ReolinkButtonEntityDescription
186 
187  def __init__(
188  self,
189  reolink_data: ReolinkData,
190  channel: int,
191  entity_description: ReolinkButtonEntityDescription,
192  ) -> None:
193  """Initialize Reolink button entity."""
194  self.entity_descriptionentity_description = entity_description
195  super().__init__(reolink_data, channel)
196 
197  if entity_description.enabled_default is not None:
198  self._attr_entity_registry_enabled_default_attr_entity_registry_enabled_default = (
199  entity_description.enabled_default(self._host_host.api, self._channel_channel)
200  )
201 
202  if (
203  self._host_host.api.supported(channel, "ptz_speed")
204  and entity_description.ptz_cmd is not None
205  ):
206  self._attr_supported_features_attr_supported_features = SUPPORT_PTZ_SPEED
207 
208  async def async_press(self) -> None:
209  """Execute the button action."""
210  try:
211  await self.entity_descriptionentity_description.method(self._host_host.api, self._channel_channel)
212  except ReolinkError as err:
213  raise HomeAssistantError(err) from err
214 
215  async def async_ptz_move(self, **kwargs: Any) -> None:
216  """PTZ move with speed."""
217  speed = kwargs[ATTR_SPEED]
218  try:
219  await self._host_host.api.set_ptz_command(
220  self._channel_channel, command=self.entity_descriptionentity_description.ptz_cmd, speed=speed
221  )
222  except ReolinkError as err:
223  raise HomeAssistantError(err) from err
224 
225 
227  """Base button entity class for Reolink IP cameras."""
228 
229  entity_description: ReolinkHostButtonEntityDescription
230 
231  def __init__(
232  self,
233  reolink_data: ReolinkData,
234  entity_description: ReolinkHostButtonEntityDescription,
235  ) -> None:
236  """Initialize Reolink button entity."""
237  self.entity_descriptionentity_description = entity_description
238  super().__init__(reolink_data)
239 
240  async def async_press(self) -> None:
241  """Execute the button action."""
242  try:
243  await self.entity_descriptionentity_description.method(self._host_host.api)
244  except ReolinkError as err:
245  raise HomeAssistantError(err) from err