Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Component providing HA sensor support for Ring Door Bell/Chimes."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from dataclasses import dataclass
7 from datetime import datetime
8 from typing import Any, Generic
9 
10 from ring_doorbell import RingCapability, RingEvent
11 from ring_doorbell.const import KIND_DING, KIND_MOTION
12 
14  BinarySensorDeviceClass,
15  BinarySensorEntity,
16  BinarySensorEntityDescription,
17 )
18 from homeassistant.const import Platform
19 from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.event import async_call_at
22 
23 from . import RingConfigEntry
24 from .coordinator import RingListenCoordinator
25 from .entity import (
26  DeprecatedInfo,
27  RingBaseEntity,
28  RingDeviceT,
29  RingEntityDescription,
30  async_check_create_deprecated,
31 )
32 
33 
34 @dataclass(frozen=True, kw_only=True)
36  BinarySensorEntityDescription, RingEntityDescription, Generic[RingDeviceT]
37 ):
38  """Describes Ring binary sensor entity."""
39 
40  capability: RingCapability
41 
42 
43 BINARY_SENSOR_TYPES: tuple[RingBinarySensorEntityDescription, ...] = (
45  key=KIND_DING,
46  translation_key=KIND_DING,
47  device_class=BinarySensorDeviceClass.OCCUPANCY,
48  capability=RingCapability.DING,
49  deprecated_info=DeprecatedInfo(
50  new_platform=Platform.EVENT, breaks_in_ha_version="2025.4.0"
51  ),
52  ),
54  key=KIND_MOTION,
55  translation_key=KIND_MOTION,
56  device_class=BinarySensorDeviceClass.MOTION,
57  capability=RingCapability.MOTION_DETECTION,
58  deprecated_info=DeprecatedInfo(
59  new_platform=Platform.EVENT, breaks_in_ha_version="2025.4.0"
60  ),
61  ),
62 )
63 
64 
66  hass: HomeAssistant,
67  entry: RingConfigEntry,
68  async_add_entities: AddEntitiesCallback,
69 ) -> None:
70  """Set up the Ring binary sensors from a config entry."""
71  ring_data = entry.runtime_data
72  listen_coordinator = ring_data.listen_coordinator
73 
75  RingBinarySensor(device, listen_coordinator, description)
76  for description in BINARY_SENSOR_TYPES
77  for device in ring_data.devices.all_devices
78  if device.has_capability(description.capability)
80  hass,
81  Platform.BINARY_SENSOR,
82  f"{device.id}-{description.key}",
83  description,
84  )
85  )
86 
87 
89  RingBaseEntity[RingListenCoordinator, RingDeviceT], BinarySensorEntity
90 ):
91  """A binary sensor implementation for Ring device."""
92 
93  _active_alert: RingEvent | None = None
94  RingBinarySensorEntityDescription[RingDeviceT]
95 
96  def __init__(
97  self,
98  device: RingDeviceT,
99  coordinator: RingListenCoordinator,
100  description: RingBinarySensorEntityDescription[RingDeviceT],
101  ) -> None:
102  """Initialize a binary sensor for Ring device."""
103  super().__init__(
104  device,
105  coordinator,
106  )
107  self.entity_descriptionentity_description = description
108  self._attr_unique_id_attr_unique_id = f"{device.id}-{description.key}"
109  self._attr_is_on_attr_is_on = False
110  self._active_alert_active_alert: RingEvent | None = None
111  self._cancel_callback_cancel_callback: CALLBACK_TYPE | None = None
112 
113  @callback
114  def _async_handle_event(self, alert: RingEvent) -> None:
115  """Handle the event."""
116  self._attr_is_on_attr_is_on = True
117  self._active_alert_active_alert = alert
118  loop = self.hasshasshass.loop
119  when = loop.time() + alert.expires_in
120  if self._cancel_callback_cancel_callback:
121  self._cancel_callback_cancel_callback()
122  self._cancel_callback_cancel_callback = async_call_at(self.hasshasshass, self._async_cancel_event_async_cancel_event, when)
123 
124  @callback
125  def _async_cancel_event(self, _now: Any) -> None:
126  """Clear the event."""
127  self._cancel_callback_cancel_callback = None
128  self._attr_is_on_attr_is_on = False
129  self._active_alert_active_alert = None
130  self.async_write_ha_stateasync_write_ha_state()
131 
132  def _get_coordinator_alert(self) -> RingEvent | None:
133  return self.coordinator.alerts.get(
134  (self._device_device.device_api_id, self.entity_descriptionentity_description.key)
135  )
136 
137  @callback
138  def _handle_coordinator_update(self) -> None:
139  if alert := self._get_coordinator_alert_get_coordinator_alert():
140  self._async_handle_event_async_handle_event(alert)
142 
143  @property
144  def available(self) -> bool:
145  """Return if entity is available."""
146  return self.coordinator.event_listener.started
147 
148  async def async_update(self) -> None:
149  """All updates are passive."""
150 
151  @property
152  def extra_state_attributes(self) -> Mapping[str, Any] | None:
153  """Return the state attributes."""
154  attrs = super().extra_state_attributes
155 
156  if self._active_alert_active_alert is None:
157  return attrs
158 
159  assert isinstance(attrs, dict)
160  attrs["state"] = self._active_alert_active_alert.state
161  now = self._active_alert_active_alert.now
162  expires_in = self._active_alert_active_alert.expires_in
163  assert now and expires_in
164  attrs["expires_at"] = datetime.fromtimestamp(now + expires_in).isoformat()
165 
166  return attrs
None __init__(self, RingDeviceT device, RingListenCoordinator coordinator, RingBinarySensorEntityDescription[RingDeviceT] description)
None async_setup_entry(HomeAssistant hass, RingConfigEntry entry, AddEntitiesCallback async_add_entities)
bool async_check_create_deprecated(HomeAssistant hass, Platform platform, str unique_id, RingEntityDescription entity_description)
Definition: entity.py:97
CALLBACK_TYPE async_call_at(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, float loop_time)
Definition: event.py:1577