Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Binary sensor for Shelly."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 from typing import Final, cast
7 
8 from aioshelly.const import RPC_GENERATIONS
9 
11  DOMAIN as BINARY_SENSOR_PLATFORM,
12  BinarySensorDeviceClass,
13  BinarySensorEntity,
14  BinarySensorEntityDescription,
15 )
16 from homeassistant.const import STATE_ON, EntityCategory
17 from homeassistant.core import HomeAssistant
18 from homeassistant.helpers.entity_platform import AddEntitiesCallback
19 from homeassistant.helpers.restore_state import RestoreEntity
20 
21 from .const import CONF_SLEEP_PERIOD
22 from .coordinator import ShellyConfigEntry
23 from .entity import (
24  BlockEntityDescription,
25  RestEntityDescription,
26  RpcEntityDescription,
27  ShellyBlockAttributeEntity,
28  ShellyRestAttributeEntity,
29  ShellyRpcAttributeEntity,
30  ShellySleepingBlockAttributeEntity,
31  ShellySleepingRpcAttributeEntity,
32  async_setup_entry_attribute_entities,
33  async_setup_entry_rest,
34  async_setup_entry_rpc,
35 )
36 from .utils import (
37  async_remove_orphaned_entities,
38  get_device_entry_gen,
39  get_virtual_component_ids,
40  is_block_momentary_input,
41  is_rpc_momentary_input,
42 )
43 
44 
45 @dataclass(frozen=True, kw_only=True)
47  BlockEntityDescription, BinarySensorEntityDescription
48 ):
49  """Class to describe a BLOCK binary sensor."""
50 
51 
52 @dataclass(frozen=True, kw_only=True)
54  """Class to describe a RPC binary sensor."""
55 
56 
57 @dataclass(frozen=True, kw_only=True)
59  """Class to describe a REST binary sensor."""
60 
61 
62 SENSORS: dict[tuple[str, str], BlockBinarySensorDescription] = {
63  ("device", "overtemp"): BlockBinarySensorDescription(
64  key="device|overtemp",
65  name="Overheating",
66  device_class=BinarySensorDeviceClass.PROBLEM,
67  entity_category=EntityCategory.DIAGNOSTIC,
68  ),
69  ("device", "overpower"): BlockBinarySensorDescription(
70  key="device|overpower",
71  name="Overpowering",
72  device_class=BinarySensorDeviceClass.PROBLEM,
73  entity_category=EntityCategory.DIAGNOSTIC,
74  ),
75  ("light", "overpower"): BlockBinarySensorDescription(
76  key="light|overpower",
77  name="Overpowering",
78  device_class=BinarySensorDeviceClass.PROBLEM,
79  entity_category=EntityCategory.DIAGNOSTIC,
80  ),
81  ("relay", "overpower"): BlockBinarySensorDescription(
82  key="relay|overpower",
83  name="Overpowering",
84  device_class=BinarySensorDeviceClass.PROBLEM,
85  entity_category=EntityCategory.DIAGNOSTIC,
86  ),
87  ("sensor", "dwIsOpened"): BlockBinarySensorDescription(
88  key="sensor|dwIsOpened",
89  name="Door",
90  device_class=BinarySensorDeviceClass.OPENING,
91  available=lambda block: cast(int, block.dwIsOpened) != -1,
92  ),
93  ("sensor", "flood"): BlockBinarySensorDescription(
94  key="sensor|flood", name="Flood", device_class=BinarySensorDeviceClass.MOISTURE
95  ),
96  ("sensor", "gas"): BlockBinarySensorDescription(
97  key="sensor|gas",
98  name="Gas",
99  device_class=BinarySensorDeviceClass.GAS,
100  translation_key="gas",
101  value=lambda value: value in ["mild", "heavy"],
102  extra_state_attributes=lambda block: {"detected": block.gas},
103  ),
104  ("sensor", "smoke"): BlockBinarySensorDescription(
105  key="sensor|smoke", name="Smoke", device_class=BinarySensorDeviceClass.SMOKE
106  ),
107  ("sensor", "vibration"): BlockBinarySensorDescription(
108  key="sensor|vibration",
109  name="Vibration",
110  device_class=BinarySensorDeviceClass.VIBRATION,
111  ),
112  ("input", "input"): BlockBinarySensorDescription(
113  key="input|input",
114  name="Input",
115  device_class=BinarySensorDeviceClass.POWER,
116  entity_registry_enabled_default=False,
117  removal_condition=is_block_momentary_input,
118  ),
119  ("relay", "input"): BlockBinarySensorDescription(
120  key="relay|input",
121  name="Input",
122  device_class=BinarySensorDeviceClass.POWER,
123  entity_registry_enabled_default=False,
124  removal_condition=is_block_momentary_input,
125  ),
126  ("device", "input"): BlockBinarySensorDescription(
127  key="device|input",
128  name="Input",
129  device_class=BinarySensorDeviceClass.POWER,
130  entity_registry_enabled_default=False,
131  removal_condition=is_block_momentary_input,
132  ),
133  ("sensor", "extInput"): BlockBinarySensorDescription(
134  key="sensor|extInput",
135  name="External input",
136  device_class=BinarySensorDeviceClass.POWER,
137  entity_registry_enabled_default=False,
138  ),
139  ("sensor", "motion"): BlockBinarySensorDescription(
140  key="sensor|motion", name="Motion", device_class=BinarySensorDeviceClass.MOTION
141  ),
142 }
143 
144 REST_SENSORS: Final = {
146  key="cloud",
147  name="Cloud",
148  value=lambda status, _: status["cloud"]["connected"],
149  device_class=BinarySensorDeviceClass.CONNECTIVITY,
150  entity_registry_enabled_default=False,
151  entity_category=EntityCategory.DIAGNOSTIC,
152  ),
153 }
154 
155 RPC_SENSORS: Final = {
157  key="input",
158  sub_key="state",
159  name="Input",
160  device_class=BinarySensorDeviceClass.POWER,
161  entity_registry_enabled_default=False,
162  removal_condition=is_rpc_momentary_input,
163  ),
165  key="cloud",
166  sub_key="connected",
167  name="Cloud",
168  device_class=BinarySensorDeviceClass.CONNECTIVITY,
169  entity_registry_enabled_default=False,
170  entity_category=EntityCategory.DIAGNOSTIC,
171  ),
172  "external_power": RpcBinarySensorDescription(
173  key="devicepower",
174  sub_key="external",
175  name="External power",
176  value=lambda status, _: status["present"],
177  device_class=BinarySensorDeviceClass.POWER,
178  entity_category=EntityCategory.DIAGNOSTIC,
179  ),
180  "overtemp": RpcBinarySensorDescription(
181  key="switch",
182  sub_key="errors",
183  name="Overheating",
184  device_class=BinarySensorDeviceClass.PROBLEM,
185  value=lambda status, _: False if status is None else "overtemp" in status,
186  entity_category=EntityCategory.DIAGNOSTIC,
187  supported=lambda status: status.get("apower") is not None,
188  ),
189  "overpower": RpcBinarySensorDescription(
190  key="switch",
191  sub_key="errors",
192  name="Overpowering",
193  device_class=BinarySensorDeviceClass.PROBLEM,
194  value=lambda status, _: False if status is None else "overpower" in status,
195  entity_category=EntityCategory.DIAGNOSTIC,
196  supported=lambda status: status.get("apower") is not None,
197  ),
198  "overvoltage": RpcBinarySensorDescription(
199  key="switch",
200  sub_key="errors",
201  name="Overvoltage",
202  device_class=BinarySensorDeviceClass.PROBLEM,
203  value=lambda status, _: False if status is None else "overvoltage" in status,
204  entity_category=EntityCategory.DIAGNOSTIC,
205  supported=lambda status: status.get("apower") is not None,
206  ),
207  "overcurrent": RpcBinarySensorDescription(
208  key="switch",
209  sub_key="errors",
210  name="Overcurrent",
211  device_class=BinarySensorDeviceClass.PROBLEM,
212  value=lambda status, _: False if status is None else "overcurrent" in status,
213  entity_category=EntityCategory.DIAGNOSTIC,
214  supported=lambda status: status.get("apower") is not None,
215  ),
217  key="smoke",
218  sub_key="alarm",
219  name="Smoke",
220  device_class=BinarySensorDeviceClass.SMOKE,
221  ),
222  "restart": RpcBinarySensorDescription(
223  key="sys",
224  sub_key="restart_required",
225  name="Restart required",
226  device_class=BinarySensorDeviceClass.PROBLEM,
227  entity_registry_enabled_default=False,
228  entity_category=EntityCategory.DIAGNOSTIC,
229  ),
230  "boolean": RpcBinarySensorDescription(
231  key="boolean",
232  sub_key="value",
233  has_entity_name=True,
234  ),
235 }
236 
237 
239  hass: HomeAssistant,
240  config_entry: ShellyConfigEntry,
241  async_add_entities: AddEntitiesCallback,
242 ) -> None:
243  """Set up sensors for device."""
244  if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
245  if config_entry.data[CONF_SLEEP_PERIOD]:
247  hass,
248  config_entry,
249  async_add_entities,
250  RPC_SENSORS,
251  RpcSleepingBinarySensor,
252  )
253  else:
254  coordinator = config_entry.runtime_data.rpc
255  assert coordinator
256 
258  hass, config_entry, async_add_entities, RPC_SENSORS, RpcBinarySensor
259  )
260 
261  # the user can remove virtual components from the device configuration, so
262  # we need to remove orphaned entities
263  virtual_binary_sensor_ids = get_virtual_component_ids(
264  coordinator.device.config, BINARY_SENSOR_PLATFORM
265  )
267  hass,
268  config_entry.entry_id,
269  coordinator.mac,
270  BINARY_SENSOR_PLATFORM,
271  virtual_binary_sensor_ids,
272  "boolean",
273  )
274  return
275 
276  if config_entry.data[CONF_SLEEP_PERIOD]:
278  hass,
279  config_entry,
280  async_add_entities,
281  SENSORS,
282  BlockSleepingBinarySensor,
283  )
284  else:
286  hass,
287  config_entry,
288  async_add_entities,
289  SENSORS,
290  BlockBinarySensor,
291  )
293  hass,
294  config_entry,
295  async_add_entities,
296  REST_SENSORS,
297  RestBinarySensor,
298  )
299 
300 
302  """Represent a block binary sensor entity."""
303 
304  entity_description: BlockBinarySensorDescription
305 
306  @property
307  def is_on(self) -> bool:
308  """Return true if sensor state is on."""
309  return bool(self.attribute_valueattribute_value)
310 
311 
313  """Represent a REST binary sensor entity."""
314 
315  entity_description: RestBinarySensorDescription
316 
317  @property
318  def is_on(self) -> bool:
319  """Return true if REST sensor state is on."""
320  return bool(self.attribute_valueattribute_value)
321 
322 
324  """Represent a RPC binary sensor entity."""
325 
326  entity_description: RpcBinarySensorDescription
327 
328  @property
329  def is_on(self) -> bool:
330  """Return true if RPC sensor state is on."""
331  return bool(self.attribute_valueattribute_value)
332 
333 
335  ShellySleepingBlockAttributeEntity, BinarySensorEntity, RestoreEntity
336 ):
337  """Represent a block sleeping binary sensor."""
338 
339  entity_description: BlockBinarySensorDescription
340 
341  async def async_added_to_hass(self) -> None:
342  """Handle entity which will be added."""
343  await super().async_added_to_hass()
344  self.last_statelast_state = await self.async_get_last_stateasync_get_last_state()
345 
346  @property
347  def is_on(self) -> bool | None:
348  """Return true if sensor state is on."""
349  if self.blockblockblock is not None:
350  return bool(self.attribute_valueattribute_value)
351 
352  if self.last_statelast_state is None:
353  return None
354 
355  return self.last_statelast_state.state == STATE_ON
356 
357 
359  ShellySleepingRpcAttributeEntity, BinarySensorEntity, RestoreEntity
360 ):
361  """Represent a RPC sleeping binary sensor entity."""
362 
363  entity_description: RpcBinarySensorDescription
364 
365  async def async_added_to_hass(self) -> None:
366  """Handle entity which will be added."""
367  await super().async_added_to_hass()
368  self.last_statelast_state = await self.async_get_last_stateasync_get_last_state()
369 
370  @property
371  def is_on(self) -> bool | None:
372  """Return true if RPC sensor state is on."""
373  if self.coordinatorcoordinator.device.initialized:
374  return bool(self.attribute_valueattribute_value)
375 
376  if self.last_statelast_state is None:
377  return None
378 
379  return self.last_statelast_state.state == STATE_ON
None async_setup_entry(HomeAssistant hass, ShellyConfigEntry config_entry, AddEntitiesCallback async_add_entities)
None async_setup_entry_rpc(HomeAssistant hass, ShellyConfigEntry config_entry, AddEntitiesCallback async_add_entities, Mapping[str, RpcEntityDescription] sensors, Callable sensor_class)
Definition: entity.py:142
None async_setup_entry_rest(HomeAssistant hass, ShellyConfigEntry config_entry, AddEntitiesCallback async_add_entities, Mapping[str, RestEntityDescription] sensors, Callable sensor_class)
Definition: entity.py:251
None async_setup_entry_attribute_entities(HomeAssistant hass, ShellyConfigEntry config_entry, AddEntitiesCallback async_add_entities, Mapping[tuple[str, str], BlockEntityDescription] sensors, Callable sensor_class)
Definition: entity.py:39
list[str] get_virtual_component_ids(dict[str, Any] config, str platform)
Definition: utils.py:528
None async_remove_orphaned_entities(HomeAssistant hass, str config_entry_id, str mac, str platform, Iterable[str] keys, str|None key_suffix=None)
Definition: utils.py:555
int get_device_entry_gen(ConfigEntry entry)
Definition: utils.py:353