Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Yale sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from typing import Any, cast
8 
9 from yalexs.activity import ActivityType, LockOperationActivity
10 from yalexs.doorbell import Doorbell
11 from yalexs.keypad import KeypadDetail
12 from yalexs.lock import LockDetail
13 
15  RestoreSensor,
16  SensorDeviceClass,
17  SensorEntity,
18  SensorEntityDescription,
19  SensorStateClass,
20 )
21 from homeassistant.const import (
22  ATTR_ENTITY_PICTURE,
23  PERCENTAGE,
24  STATE_UNAVAILABLE,
25  EntityCategory,
26 )
27 from homeassistant.core import HomeAssistant, callback
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
29 
30 from . import YaleConfigEntry
31 from .const import (
32  ATTR_OPERATION_AUTORELOCK,
33  ATTR_OPERATION_KEYPAD,
34  ATTR_OPERATION_MANUAL,
35  ATTR_OPERATION_METHOD,
36  ATTR_OPERATION_REMOTE,
37  ATTR_OPERATION_TAG,
38  OPERATION_METHOD_AUTORELOCK,
39  OPERATION_METHOD_KEYPAD,
40  OPERATION_METHOD_MANUAL,
41  OPERATION_METHOD_MOBILE_DEVICE,
42  OPERATION_METHOD_REMOTE,
43  OPERATION_METHOD_TAG,
44 )
45 from .entity import YaleDescriptionEntity, YaleEntity
46 
47 
48 def _retrieve_device_battery_state(detail: LockDetail) -> int:
49  """Get the latest state of the sensor."""
50  return detail.battery_level
51 
52 
53 def _retrieve_linked_keypad_battery_state(detail: KeypadDetail) -> int | None:
54  """Get the latest state of the sensor."""
55  return detail.battery_percentage
56 
57 
58 @dataclass(frozen=True, kw_only=True)
59 class YaleSensorEntityDescription[T: LockDetail | KeypadDetail](
60  SensorEntityDescription
61 ):
62  """Mixin for required keys."""
63 
64  value_fn: Callable[[T], int | None]
65 
66 
67 SENSOR_TYPE_DEVICE_BATTERY = YaleSensorEntityDescription[LockDetail](
68  key="device_battery",
69  entity_category=EntityCategory.DIAGNOSTIC,
70  state_class=SensorStateClass.MEASUREMENT,
71  value_fn=_retrieve_device_battery_state,
72 )
73 
74 SENSOR_TYPE_KEYPAD_BATTERY = YaleSensorEntityDescription[KeypadDetail](
75  key="linked_keypad_battery",
76  entity_category=EntityCategory.DIAGNOSTIC,
77  state_class=SensorStateClass.MEASUREMENT,
78  value_fn=_retrieve_linked_keypad_battery_state,
79 )
80 
81 
83  hass: HomeAssistant,
84  config_entry: YaleConfigEntry,
85  async_add_entities: AddEntitiesCallback,
86 ) -> None:
87  """Set up the Yale sensors."""
88  data = config_entry.runtime_data
89  entities: list[SensorEntity] = []
90 
91  for device in data.locks:
92  detail = data.get_device_detail(device.device_id)
93  entities.append(YaleOperatorSensor(data, device, "lock_operator"))
94  if SENSOR_TYPE_DEVICE_BATTERY.value_fn(detail):
95  entities.append(
96  YaleBatterySensor[LockDetail](data, device, SENSOR_TYPE_DEVICE_BATTERY)
97  )
98  if keypad := detail.keypad:
99  entities.append(
100  YaleBatterySensor[KeypadDetail](
101  data, keypad, SENSOR_TYPE_KEYPAD_BATTERY
102  )
103  )
104 
105  entities.extend(
106  YaleBatterySensor[Doorbell](data, device, SENSOR_TYPE_DEVICE_BATTERY)
107  for device in data.doorbells
108  if SENSOR_TYPE_DEVICE_BATTERY.value_fn(data.get_device_detail(device.device_id))
109  )
110 
111  async_add_entities(entities)
112 
113 
115  """Representation of an Yale lock operation sensor."""
116 
117  _attr_translation_key = "operator"
118  _operated_remote: bool | None = None
119  _operated_keypad: bool | None = None
120  _operated_manual: bool | None = None
121  _operated_tag: bool | None = None
122  _operated_autorelock: bool | None = None
123 
124  @callback
125  def _update_from_data(self) -> None:
126  """Get the latest state of the sensor and update activity."""
127  self._attr_available_attr_available = True
128  if lock_activity := self._get_latest_get_latest({ActivityType.LOCK_OPERATION}):
129  lock_activity = cast(LockOperationActivity, lock_activity)
130  self._attr_native_value_attr_native_value = lock_activity.operated_by
131  self._operated_remote_operated_remote = lock_activity.operated_remote
132  self._operated_keypad_operated_keypad = lock_activity.operated_keypad
133  self._operated_manual_operated_manual = lock_activity.operated_manual
134  self._operated_tag_operated_tag = lock_activity.operated_tag
135  self._operated_autorelock_operated_autorelock = lock_activity.operated_autorelock
136  self._attr_entity_picture_attr_entity_picture = lock_activity.operator_thumbnail_url
137 
138  @property
139  def extra_state_attributes(self) -> dict[str, Any]:
140  """Return the device specific state attributes."""
141  attributes: dict[str, Any] = {}
142 
143  if self._operated_remote_operated_remote is not None:
144  attributes[ATTR_OPERATION_REMOTE] = self._operated_remote_operated_remote
145  if self._operated_keypad_operated_keypad is not None:
146  attributes[ATTR_OPERATION_KEYPAD] = self._operated_keypad_operated_keypad
147  if self._operated_manual_operated_manual is not None:
148  attributes[ATTR_OPERATION_MANUAL] = self._operated_manual_operated_manual
149  if self._operated_tag_operated_tag is not None:
150  attributes[ATTR_OPERATION_TAG] = self._operated_tag_operated_tag
151  if self._operated_autorelock_operated_autorelock is not None:
152  attributes[ATTR_OPERATION_AUTORELOCK] = self._operated_autorelock_operated_autorelock
153 
154  if self._operated_remote_operated_remote:
155  attributes[ATTR_OPERATION_METHOD] = OPERATION_METHOD_REMOTE
156  elif self._operated_keypad_operated_keypad:
157  attributes[ATTR_OPERATION_METHOD] = OPERATION_METHOD_KEYPAD
158  elif self._operated_manual_operated_manual:
159  attributes[ATTR_OPERATION_METHOD] = OPERATION_METHOD_MANUAL
160  elif self._operated_tag_operated_tag:
161  attributes[ATTR_OPERATION_METHOD] = OPERATION_METHOD_TAG
162  elif self._operated_autorelock_operated_autorelock:
163  attributes[ATTR_OPERATION_METHOD] = OPERATION_METHOD_AUTORELOCK
164  else:
165  attributes[ATTR_OPERATION_METHOD] = OPERATION_METHOD_MOBILE_DEVICE
166 
167  return attributes
168 
169  async def async_added_to_hass(self) -> None:
170  """Restore ATTR_CHANGED_BY on startup since it is likely no longer in the activity log."""
171  await super().async_added_to_hass()
172 
173  last_state = await self.async_get_last_state()
174  last_sensor_state = await self.async_get_last_sensor_dataasync_get_last_sensor_data()
175  if (
176  not last_state
177  or not last_sensor_state
178  or last_state.state == STATE_UNAVAILABLE
179  ):
180  return
181 
182  self._attr_native_value_attr_native_value = last_sensor_state.native_value
183  last_attrs = last_state.attributes
184  if ATTR_ENTITY_PICTURE in last_attrs:
185  self._attr_entity_picture_attr_entity_picture = last_attrs[ATTR_ENTITY_PICTURE]
186  if ATTR_OPERATION_REMOTE in last_attrs:
187  self._operated_remote_operated_remote = last_attrs[ATTR_OPERATION_REMOTE]
188  if ATTR_OPERATION_KEYPAD in last_attrs:
189  self._operated_keypad_operated_keypad = last_attrs[ATTR_OPERATION_KEYPAD]
190  if ATTR_OPERATION_MANUAL in last_attrs:
191  self._operated_manual_operated_manual = last_attrs[ATTR_OPERATION_MANUAL]
192  if ATTR_OPERATION_TAG in last_attrs:
193  self._operated_tag_operated_tag = last_attrs[ATTR_OPERATION_TAG]
194  if ATTR_OPERATION_AUTORELOCK in last_attrs:
195  self._operated_autorelock_operated_autorelock = last_attrs[ATTR_OPERATION_AUTORELOCK]
196 
197 
198 class YaleBatterySensor[T: LockDetail | KeypadDetail](
199  YaleDescriptionEntity, SensorEntity
200 ):
201  """Representation of an Yale sensor."""
202 
203  entity_description: YaleSensorEntityDescription[T]
204  _attr_device_class = SensorDeviceClass.BATTERY
205  _attr_native_unit_of_measurement = PERCENTAGE
206 
207  @callback
208  def _update_from_data(self) -> None:
209  """Get the latest state of the sensor."""
210  self._attr_native_value = self.entity_description.value_fn(self._detail)
211  self._attr_available = self._attr_native_value is not None
SensorExtraStoredData|None async_get_last_sensor_data(self)
Definition: __init__.py:934
Activity|None _get_latest(self, set[ActivityType] activity_types)
Definition: entity.py:62
int|None _retrieve_linked_keypad_battery_state(KeypadDetail detail)
Definition: sensor.py:53
None async_setup_entry(HomeAssistant hass, YaleConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:86
int _retrieve_device_battery_state(LockDetail detail)
Definition: sensor.py:48