Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Support for Yale binary sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from datetime import datetime, timedelta
8 from functools import partial
9 import logging
10 
11 from yalexs.activity import Activity, ActivityType
12 from yalexs.doorbell import DoorbellDetail
13 from yalexs.lock import LockDetail, LockDoorStatus
14 from yalexs.manager.const import ACTIVITY_UPDATE_INTERVAL
15 from yalexs.util import update_lock_detail_from_activity
16 
18  BinarySensorDeviceClass,
19  BinarySensorEntity,
20  BinarySensorEntityDescription,
21 )
22 from homeassistant.const import EntityCategory
23 from homeassistant.core import HomeAssistant, callback
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 from homeassistant.helpers.event import async_call_later
26 
27 from . import YaleConfigEntry, YaleData
28 from .entity import YaleDescriptionEntity
29 from .util import (
30  retrieve_ding_activity,
31  retrieve_doorbell_motion_activity,
32  retrieve_online_state,
33  retrieve_time_based_activity,
34 )
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 TIME_TO_RECHECK_DETECTION = timedelta(
39  seconds=ACTIVITY_UPDATE_INTERVAL.total_seconds() * 3
40 )
41 
42 
43 @dataclass(frozen=True, kw_only=True)
45  """Describes Yale binary_sensor entity."""
46 
47  value_fn: Callable[[YaleData, DoorbellDetail | LockDetail], Activity | None]
48  is_time_based: bool
49 
50 
51 SENSOR_TYPE_DOOR = BinarySensorEntityDescription(
52  key="open",
53  device_class=BinarySensorDeviceClass.DOOR,
54 )
55 
56 SENSOR_TYPES_VIDEO_DOORBELL = (
58  key="motion",
59  device_class=BinarySensorDeviceClass.MOTION,
60  value_fn=retrieve_doorbell_motion_activity,
61  is_time_based=True,
62  ),
64  key="image capture",
65  translation_key="image_capture",
66  value_fn=partial(
67  retrieve_time_based_activity, {ActivityType.DOORBELL_IMAGE_CAPTURE}
68  ),
69  is_time_based=True,
70  ),
72  key="online",
73  device_class=BinarySensorDeviceClass.CONNECTIVITY,
74  entity_category=EntityCategory.DIAGNOSTIC,
75  value_fn=retrieve_online_state,
76  is_time_based=False,
77  ),
78 )
79 
80 
81 SENSOR_TYPES_DOORBELL: tuple[YaleDoorbellBinarySensorEntityDescription, ...] = (
83  key="ding",
84  translation_key="ding",
85  device_class=BinarySensorDeviceClass.OCCUPANCY,
86  value_fn=retrieve_ding_activity,
87  is_time_based=True,
88  ),
89 )
90 
91 
93  hass: HomeAssistant,
94  config_entry: YaleConfigEntry,
95  async_add_entities: AddEntitiesCallback,
96 ) -> None:
97  """Set up the Yale binary sensors."""
98  data = config_entry.runtime_data
99  entities: list[BinarySensorEntity] = []
100 
101  for lock in data.locks:
102  detail = data.get_device_detail(lock.device_id)
103  if detail.doorsense:
104  entities.append(YaleDoorBinarySensor(data, lock, SENSOR_TYPE_DOOR))
105 
106  if detail.doorbell:
107  entities.extend(
108  YaleDoorbellBinarySensor(data, lock, description)
109  for description in SENSOR_TYPES_DOORBELL
110  )
111 
112  entities.extend(
113  YaleDoorbellBinarySensor(data, doorbell, description)
114  for description in SENSOR_TYPES_DOORBELL + SENSOR_TYPES_VIDEO_DOORBELL
115  for doorbell in data.doorbells
116  )
117  async_add_entities(entities)
118 
119 
121  """Representation of an Yale Door binary sensor."""
122 
123  _attr_device_class = BinarySensorDeviceClass.DOOR
124  description: BinarySensorEntityDescription
125 
126  @callback
127  def _update_from_data(self) -> None:
128  """Get the latest state of the sensor and update activity."""
129  if door_activity := self._get_latest_get_latest({ActivityType.DOOR_OPERATION}):
130  update_lock_detail_from_activity(self._detail_detail, door_activity)
131  if door_activity.was_pushed:
132  self._detail_detail.set_online(True)
133 
134  if bridge_activity := self._get_latest_get_latest({ActivityType.BRIDGE_OPERATION}):
135  update_lock_detail_from_activity(self._detail_detail, bridge_activity)
136  self._attr_available_attr_available = self._detail_detail.bridge_is_online
137  self._attr_is_on_attr_is_on = self._detail_detail.door_state == LockDoorStatus.OPEN
138 
139 
141  """Representation of an Yale binary sensor."""
142 
143  entity_description: YaleDoorbellBinarySensorEntityDescription
144  _check_for_off_update_listener: Callable[[], None] | None = None
145 
146  @callback
147  def _update_from_data(self) -> None:
148  """Get the latest state of the sensor."""
149  self._cancel_any_pending_updates_cancel_any_pending_updates()
150  self._attr_is_on_attr_is_on = bool(
151  self.entity_descriptionentity_description.value_fn(self._data_data, self._detail_detail)
152  )
153 
154  if self.entity_descriptionentity_description.is_time_based:
155  self._attr_available_attr_available = retrieve_online_state(self._data_data, self._detail_detail)
156  self._schedule_update_to_recheck_turn_off_sensor_schedule_update_to_recheck_turn_off_sensor()
157  else:
158  self._attr_available_attr_available = True
159 
160  @callback
161  def _async_scheduled_update(self, now: datetime) -> None:
162  """Timer callback for sensor update."""
163  self._check_for_off_update_listener_check_for_off_update_listener = None
164  self._update_from_data_update_from_data_update_from_data()
165  if not self.is_on:
166  self.async_write_ha_stateasync_write_ha_state()
167 
169  """Schedule an update to recheck the sensor to see if it is ready to turn off."""
170  # If the sensor is already off there is nothing to do
171  if not self.is_on:
172  return
173  self._check_for_off_update_listener_check_for_off_update_listener = async_call_later(
174  self.hasshass, TIME_TO_RECHECK_DETECTION, self._async_scheduled_update_async_scheduled_update
175  )
176 
177  def _cancel_any_pending_updates(self) -> None:
178  """Cancel any updates to recheck a sensor to see if it is ready to turn off."""
179  if not self._check_for_off_update_listener_check_for_off_update_listener:
180  return
181  _LOGGER.debug("%s: canceled pending update", self.entity_identity_id)
182  self._check_for_off_update_listener_check_for_off_update_listener()
183  self._check_for_off_update_listener_check_for_off_update_listener = None
184 
185  async def async_will_remove_from_hass(self) -> None:
186  """When removing cancel any scheduled updates."""
187  self._cancel_any_pending_updates_cancel_any_pending_updates()
188  await super().async_will_remove_from_hass()
DoorbellDetail|LockDetail _detail(self)
Definition: entity.py:53
Activity|None _get_latest(self, set[ActivityType] activity_types)
Definition: entity.py:62
bool retrieve_online_state(AugustData data, DoorbellDetail|LockDetail detail)
Definition: util.py:73
None async_setup_entry(HomeAssistant hass, YaleConfigEntry config_entry, AddEntitiesCallback async_add_entities)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
Definition: event.py:1597