Home Assistant Unofficial Reference 2024.12.1
lock.py
Go to the documentation of this file.
1 """Support for Yale lock."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Coroutine
6 import logging
7 from typing import Any
8 
9 from aiohttp import ClientResponseError
10 from yalexs.activity import ActivityType, ActivityTypes
11 from yalexs.lock import Lock, LockStatus
12 from yalexs.util import get_latest_activity, update_lock_detail_from_activity
13 
14 from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntity, LockEntityFeature
15 from homeassistant.const import ATTR_BATTERY_LEVEL
16 from homeassistant.core import HomeAssistant, callback
17 from homeassistant.helpers.entity_platform import AddEntitiesCallback
18 from homeassistant.helpers.restore_state import RestoreEntity
19 import homeassistant.util.dt as dt_util
20 
21 from . import YaleConfigEntry, YaleData
22 from .entity import YaleEntity
23 
24 _LOGGER = logging.getLogger(__name__)
25 
26 LOCK_JAMMED_ERR = 531
27 
28 
30  hass: HomeAssistant,
31  config_entry: YaleConfigEntry,
32  async_add_entities: AddEntitiesCallback,
33 ) -> None:
34  """Set up Yale locks."""
35  data = config_entry.runtime_data
36  async_add_entities(YaleLock(data, lock) for lock in data.locks)
37 
38 
40  """Representation of an Yale lock."""
41 
42  _attr_name = None
43  _lock_status: LockStatus | None = None
44 
45  def __init__(self, data: YaleData, device: Lock) -> None:
46  """Initialize the lock."""
47  super().__init__(data, device, "lock")
48  if self._detail_detail.unlatch_supported:
49  self._attr_supported_features_attr_supported_features = LockEntityFeature.OPEN
50 
51  async def async_lock(self, **kwargs: Any) -> None:
52  """Lock the device."""
53  if self._data_data.push_updates_connected:
54  await self._data_data.async_lock_async(self._device_id_device_id, self._hyper_bridge_hyper_bridge)
55  return
56  await self._call_lock_operation_call_lock_operation(self._data_data.async_lock)
57 
58  async def async_open(self, **kwargs: Any) -> None:
59  """Open/unlatch the device."""
60  if self._data_data.push_updates_connected:
61  await self._data_data.async_unlatch_async(self._device_id_device_id, self._hyper_bridge_hyper_bridge)
62  return
63  await self._call_lock_operation_call_lock_operation(self._data_data.async_unlatch)
64 
65  async def async_unlock(self, **kwargs: Any) -> None:
66  """Unlock the device."""
67  if self._data_data.push_updates_connected:
68  await self._data_data.async_unlock_async(self._device_id_device_id, self._hyper_bridge_hyper_bridge)
69  return
70  await self._call_lock_operation_call_lock_operation(self._data_data.async_unlock)
71 
73  self, lock_operation: Callable[[str], Coroutine[Any, Any, list[ActivityTypes]]]
74  ) -> None:
75  try:
76  activities = await lock_operation(self._device_id_device_id)
77  except ClientResponseError as err:
78  if err.status == LOCK_JAMMED_ERR:
79  self._detail_detail.lock_status = LockStatus.JAMMED
80  self._detail_detail.lock_status_datetime = dt_util.utcnow()
81  else:
82  raise
83  else:
84  for lock_activity in activities:
85  update_lock_detail_from_activity(self._detail_detail, lock_activity)
86 
87  if self._update_lock_status_from_detail_update_lock_status_from_detail():
88  _LOGGER.debug(
89  "async_signal_device_id_update (from lock operation): %s",
90  self._device_id_device_id,
91  )
92  self._data_data.async_signal_device_id_update(self._device_id_device_id)
93 
95  self._attr_available_attr_available = self._detail_detail.bridge_is_online
96 
97  if self._lock_status_lock_status != self._detail_detail.lock_status:
98  self._lock_status_lock_status = self._detail_detail.lock_status
99  return True
100  return False
101 
102  @callback
103  def _update_from_data(self) -> None:
104  """Get the latest state of the sensor and update activity."""
105  detail = self._detail_detail
106  if lock_activity := self._get_latest_get_latest({ActivityType.LOCK_OPERATION}):
107  self._attr_changed_by_attr_changed_by = lock_activity.operated_by
108  lock_activity_without_operator = self._get_latest_get_latest(
109  {ActivityType.LOCK_OPERATION_WITHOUT_OPERATOR}
110  )
111  if latest_activity := get_latest_activity(
112  lock_activity_without_operator, lock_activity
113  ):
114  if latest_activity.was_pushed:
115  self._detail_detail.set_online(True)
116  update_lock_detail_from_activity(detail, latest_activity)
117 
118  if bridge_activity := self._get_latest_get_latest({ActivityType.BRIDGE_OPERATION}):
119  update_lock_detail_from_activity(detail, bridge_activity)
120 
121  self._update_lock_status_from_detail_update_lock_status_from_detail()
122  lock_status = self._lock_status_lock_status
123  if lock_status is None or lock_status is LockStatus.UNKNOWN:
124  self._attr_is_locked_attr_is_locked = None
125  else:
126  self._attr_is_locked_attr_is_locked = lock_status is LockStatus.LOCKED
127  self._attr_is_jammed_attr_is_jammed = lock_status is LockStatus.JAMMED
128  self._attr_is_locking_attr_is_locking = lock_status is LockStatus.LOCKING
129  self._attr_is_unlocking_attr_is_unlocking = lock_status in (
130  LockStatus.UNLOCKING,
131  LockStatus.UNLATCHING,
132  )
133  self._attr_extra_state_attributes_attr_extra_state_attributes = {ATTR_BATTERY_LEVEL: detail.battery_level}
134  if keypad := detail.keypad:
135  self._attr_extra_state_attributes_attr_extra_state_attributes["keypad_battery_level"] = (
136  keypad.battery_level
137  )
138 
139  async def async_added_to_hass(self) -> None:
140  """Restore ATTR_CHANGED_BY on startup since it is likely no longer in the activity log."""
141  await super().async_added_to_hass()
142 
143  if not (last_state := await self.async_get_last_stateasync_get_last_state()):
144  return
145 
146  if ATTR_CHANGED_BY in last_state.attributes:
147  self._attr_changed_by_attr_changed_by = last_state.attributes[ATTR_CHANGED_BY]
DoorbellDetail|LockDetail _detail(self)
Definition: entity.py:53
Activity|None _get_latest(self, set[ActivityType] activity_types)
Definition: entity.py:62
None async_lock(self, **Any kwargs)
Definition: lock.py:51
None async_unlock(self, **Any kwargs)
Definition: lock.py:65
None _call_lock_operation(self, Callable[[str], Coroutine[Any, Any, list[ActivityTypes]]] lock_operation)
Definition: lock.py:74
None async_open(self, **Any kwargs)
Definition: lock.py:58
None __init__(self, YaleData data, Lock device)
Definition: lock.py:45
None async_setup_entry(HomeAssistant hass, YaleConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: lock.py:33