Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Honeywell Lyric sensor platform."""
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 
9 from aiolyric import Lyric
10 from aiolyric.objects.device import LyricDevice
11 from aiolyric.objects.location import LyricLocation
12 from aiolyric.objects.priority import LyricAccessory, LyricRoom
13 
15  SensorDeviceClass,
16  SensorEntity,
17  SensorEntityDescription,
18  SensorStateClass,
19 )
20 from homeassistant.config_entries import ConfigEntry
21 from homeassistant.const import PERCENTAGE, UnitOfTemperature
22 from homeassistant.core import HomeAssistant
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 from homeassistant.helpers.typing import StateType
25 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
26 from homeassistant.util import dt as dt_util
27 
28 from .const import (
29  DOMAIN,
30  PRESET_HOLD_UNTIL,
31  PRESET_NO_HOLD,
32  PRESET_PERMANENT_HOLD,
33  PRESET_TEMPORARY_HOLD,
34  PRESET_VACATION_HOLD,
35 )
36 from .entity import LyricAccessoryEntity, LyricDeviceEntity
37 
38 LYRIC_SETPOINT_STATUS_NAMES = {
39  PRESET_NO_HOLD: "Following Schedule",
40  PRESET_PERMANENT_HOLD: "Held Permanently",
41  PRESET_TEMPORARY_HOLD: "Held Temporarily",
42  PRESET_VACATION_HOLD: "Holiday",
43 }
44 
45 
46 @dataclass(frozen=True, kw_only=True)
48  """Class describing Honeywell Lyric sensor entities."""
49 
50  value_fn: Callable[[LyricDevice], StateType | datetime]
51  suitable_fn: Callable[[LyricDevice], bool]
52 
53 
54 @dataclass(frozen=True, kw_only=True)
56  """Class describing Honeywell Lyric room sensor entities."""
57 
58  value_fn: Callable[[LyricRoom, LyricAccessory], StateType | datetime]
59  suitable_fn: Callable[[LyricRoom, LyricAccessory], bool]
60 
61 
62 DEVICE_SENSORS: list[LyricSensorEntityDescription] = [
64  key="indoor_temperature",
65  translation_key="indoor_temperature",
66  device_class=SensorDeviceClass.TEMPERATURE,
67  state_class=SensorStateClass.MEASUREMENT,
68  value_fn=lambda device: device.indoor_temperature,
69  suitable_fn=lambda device: device.indoor_temperature,
70  ),
72  key="indoor_humidity",
73  translation_key="indoor_humidity",
74  device_class=SensorDeviceClass.HUMIDITY,
75  state_class=SensorStateClass.MEASUREMENT,
76  native_unit_of_measurement=PERCENTAGE,
77  value_fn=lambda device: device.indoor_humidity,
78  suitable_fn=lambda device: device.indoor_humidity,
79  ),
81  key="outdoor_temperature",
82  translation_key="outdoor_temperature",
83  device_class=SensorDeviceClass.TEMPERATURE,
84  state_class=SensorStateClass.MEASUREMENT,
85  value_fn=lambda device: device.outdoor_temperature,
86  suitable_fn=lambda device: device.outdoor_temperature,
87  ),
89  key="outdoor_humidity",
90  translation_key="outdoor_humidity",
91  device_class=SensorDeviceClass.HUMIDITY,
92  state_class=SensorStateClass.MEASUREMENT,
93  native_unit_of_measurement=PERCENTAGE,
94  value_fn=lambda device: device.displayed_outdoor_humidity,
95  suitable_fn=lambda device: device.displayed_outdoor_humidity,
96  ),
98  key="next_period_time",
99  translation_key="next_period_time",
100  device_class=SensorDeviceClass.TIMESTAMP,
101  value_fn=lambda device: get_datetime_from_future_time(
102  device.changeable_values.next_period_time
103  ),
104  suitable_fn=lambda device: (
105  device.changeable_values and device.changeable_values.next_period_time
106  ),
107  ),
109  key="setpoint_status",
110  translation_key="setpoint_status",
111  value_fn=lambda device: get_setpoint_status(
112  device.changeable_values.thermostat_setpoint_status,
113  device.changeable_values.next_period_time,
114  ),
115  suitable_fn=lambda device: (
116  device.changeable_values
117  and device.changeable_values.thermostat_setpoint_status
118  ),
119  ),
120 ]
121 
122 ACCESSORY_SENSORS: list[LyricSensorAccessoryEntityDescription] = [
124  key="room_temperature",
125  translation_key="room_temperature",
126  device_class=SensorDeviceClass.TEMPERATURE,
127  state_class=SensorStateClass.MEASUREMENT,
128  value_fn=lambda _, accessory: accessory.temperature,
129  suitable_fn=lambda _, accessory: accessory.type == "IndoorAirSensor",
130  ),
132  key="room_humidity",
133  translation_key="room_humidity",
134  device_class=SensorDeviceClass.HUMIDITY,
135  state_class=SensorStateClass.MEASUREMENT,
136  native_unit_of_measurement=PERCENTAGE,
137  value_fn=lambda room, _: room.room_avg_humidity,
138  suitable_fn=lambda _, accessory: accessory.type == "IndoorAirSensor",
139  ),
140 ]
141 
142 
143 def get_setpoint_status(status: str, time: str) -> str | None:
144  """Get status of the setpoint."""
145  if status == PRESET_HOLD_UNTIL:
146  return f"Held until {time}"
147  return LYRIC_SETPOINT_STATUS_NAMES.get(status)
148 
149 
150 def get_datetime_from_future_time(time_str: str) -> datetime:
151  """Get datetime from future time provided."""
152  time = dt_util.parse_time(time_str)
153  if time is None:
154  raise ValueError(f"Unable to parse time {time_str}")
155  now = dt_util.utcnow()
156  if time <= now.time():
157  now = now + timedelta(days=1)
158  return dt_util.as_utc(datetime.combine(now.date(), time))
159 
160 
162  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
163 ) -> None:
164  """Set up the Honeywell Lyric sensor platform based on a config entry."""
165  coordinator: DataUpdateCoordinator[Lyric] = hass.data[DOMAIN][entry.entry_id]
166 
168  LyricSensor(
169  coordinator,
170  device_sensor,
171  location,
172  device,
173  )
174  for location in coordinator.data.locations
175  for device in location.devices
176  for device_sensor in DEVICE_SENSORS
177  if device_sensor.suitable_fn(device)
178  )
179 
182  coordinator, accessory_sensor, location, device, room, accessory
183  )
184  for location in coordinator.data.locations
185  for device in location.devices
186  for room in coordinator.data.rooms_dict.get(device.mac_id, {}).values()
187  for accessory in room.accessories
188  for accessory_sensor in ACCESSORY_SENSORS
189  if accessory_sensor.suitable_fn(room, accessory)
190  )
191 
192 
194  """Define a Honeywell Lyric sensor."""
195 
196  entity_description: LyricSensorEntityDescription
197 
198  def __init__(
199  self,
200  coordinator: DataUpdateCoordinator[Lyric],
201  description: LyricSensorEntityDescription,
202  location: LyricLocation,
203  device: LyricDevice,
204  ) -> None:
205  """Initialize."""
206  super().__init__(
207  coordinator,
208  location,
209  device,
210  f"{device.mac_id}_{description.key}",
211  )
212  self.entity_descriptionentity_description = description
213  if description.device_class == SensorDeviceClass.TEMPERATURE:
214  if device.units == "Fahrenheit":
215  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = UnitOfTemperature.FAHRENHEIT
216  else:
217  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
218 
219  @property
220  def native_value(self) -> StateType | datetime:
221  """Return the state."""
222  return self.entity_descriptionentity_description.value_fn(self.devicedevice)
223 
224 
226  """Define a Honeywell Lyric sensor."""
227 
228  entity_description: LyricSensorAccessoryEntityDescription
229 
230  def __init__(
231  self,
232  coordinator: DataUpdateCoordinator[Lyric],
233  description: LyricSensorAccessoryEntityDescription,
234  location: LyricLocation,
235  parentDevice: LyricDevice,
236  room: LyricRoom,
237  accessory: LyricAccessory,
238  ) -> None:
239  """Initialize."""
240  super().__init__(
241  coordinator,
242  location,
243  parentDevice,
244  room,
245  accessory,
246  f"{parentDevice.mac_id}_room{room.id}_acc{accessory.id}_{description.key}",
247  )
248  self.entity_descriptionentity_description = description
249  if description.device_class == SensorDeviceClass.TEMPERATURE:
250  if parentDevice.units == "Fahrenheit":
251  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = UnitOfTemperature.FAHRENHEIT
252  else:
253  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
254 
255  @property
256  def native_value(self) -> StateType | datetime:
257  """Return the state."""
258  return self.entity_descriptionentity_description.value_fn(self.roomroom, self.accessoryaccessory)
None __init__(self, DataUpdateCoordinator[Lyric] coordinator, LyricSensorAccessoryEntityDescription description, LyricLocation location, LyricDevice parentDevice, LyricRoom room, LyricAccessory accessory)
Definition: sensor.py:238
None __init__(self, DataUpdateCoordinator[Lyric] coordinator, LyricSensorEntityDescription description, LyricLocation location, LyricDevice device)
Definition: sensor.py:204
str|None get_setpoint_status(str status, str time)
Definition: sensor.py:143
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:163
datetime get_datetime_from_future_time(str time_str)
Definition: sensor.py:150