diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index 4afb66f7173..07c5bfeaf89 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -140,18 +140,12 @@ class LyricEntity(CoordinatorEntity): location: LyricLocation, device: LyricDevice, key: str, - name: str, - icon: str | None, ) -> None: """Initialize the Honeywell Lyric entity.""" super().__init__(coordinator) self._key = key - self._name = name - self._icon = icon self._location = location self._mac_id = device.macID - self._device_name = device.name - self._device_model = device.deviceModel self._update_thermostat = coordinator.data.update_thermostat @property @@ -159,16 +153,6 @@ class LyricEntity(CoordinatorEntity): """Return the unique ID for this entity.""" return self._key - @property - def name(self) -> str: - """Return the name of the entity.""" - return self._name - - @property - def icon(self) -> str: - """Return the mdi icon of the entity.""" - return self._icon - @property def location(self) -> LyricLocation: """Get the Lyric Location.""" @@ -189,6 +173,6 @@ class LyricDeviceEntity(LyricEntity): return { "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac_id)}, "manufacturer": "Honeywell", - "model": self._device_model, - "name": self._device_name, + "model": self.device.deviceModel, + "name": self.device.name, } diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 955afe140c9..940740bd397 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -8,7 +8,7 @@ from aiolyric.objects.device import LyricDevice from aiolyric.objects.location import LyricLocation import voluptuous as vol -from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate import ClimateEntity, ClimateEntityDescription from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -99,7 +99,14 @@ async def async_setup_entry( for device in location.devices: entities.append( LyricClimate( - coordinator, location, device, hass.config.units.temperature_unit + coordinator, + ClimateEntityDescription( + key=f"{device.macID}_thermostat", + name=device.name, + ), + location, + device, + hass.config.units.temperature_unit, ) ) @@ -117,9 +124,13 @@ async def async_setup_entry( class LyricClimate(LyricDeviceEntity, ClimateEntity): """Defines a Honeywell Lyric climate entity.""" + coordinator: DataUpdateCoordinator + entity_description: ClimateEntityDescription + def __init__( self, coordinator: DataUpdateCoordinator, + description: ClimateEntityDescription, location: LyricLocation, device: LyricDevice, temperature_unit: str, @@ -148,9 +159,8 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): location, device, f"{device.macID}_thermostat", - device.name, - None, ) + self.entity_description = description @property def supported_features(self) -> int: diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index 868b6262ddc..b5b0ffdeb3d 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -1,10 +1,16 @@ """Support for Honeywell Lyric sensor platform.""" +from dataclasses import dataclass from datetime import datetime, timedelta +from typing import Callable, cast from aiolyric.objects.device import LyricDevice from aiolyric.objects.location import LyricLocation -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import ( + STATE_CLASS_MEASUREMENT, + SensorEntity, + SensorEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -12,6 +18,7 @@ from homeassistant.const import ( DEVICE_CLASS_TIMESTAMP, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import dt as dt_util @@ -33,6 +40,22 @@ LYRIC_SETPOINT_STATUS_NAMES = { } +@dataclass +class LyricSensorEntityDescription(SensorEntityDescription): + """Class describing Honeywell Lyric sensor entities.""" + + value: Callable[[LyricDevice], StateType] = round + + +def get_datetime_from_future_time(time: str) -> datetime: + """Get datetime from future time provided.""" + time = dt_util.parse_time(time) + now = dt_util.utcnow() + if time <= now.time(): + now = now + timedelta(days=1) + return dt_util.as_utc(datetime.combine(now.date(), time)) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities ) -> None: @@ -41,212 +64,126 @@ async def async_setup_entry( entities = [] + def get_setpoint_status(status: str, time: str) -> str: + if status == PRESET_HOLD_UNTIL: + return f"Held until {time}" + return LYRIC_SETPOINT_STATUS_NAMES.get(status, None) + for location in coordinator.data.locations: for device in location.devices: - cls_list = [] if device.indoorTemperature: - cls_list.append(LyricIndoorTemperatureSensor) - if device.outdoorTemperature: - cls_list.append(LyricOutdoorTemperatureSensor) - if device.displayedOutdoorHumidity: - cls_list.append(LyricOutdoorHumiditySensor) - if device.changeableValues: - if device.changeableValues.nextPeriodTime: - cls_list.append(LyricNextPeriodSensor) - if device.changeableValues.thermostatSetpointStatus: - cls_list.append(LyricSetpointStatusSensor) - for cls in cls_list: entities.append( - cls( + LyricSensor( coordinator, + LyricSensorEntityDescription( + key=f"{device.macID}_indoor_temperature", + name="Indoor Temperature", + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + native_unit_of_measurement=hass.config.units.temperature_unit, + value=lambda device: device.indoorTemperature, + ), location, device, - hass.config.units.temperature_unit, ) ) + if device.outdoorTemperature: + entities.append( + LyricSensor( + coordinator, + LyricSensorEntityDescription( + key=f"{device.macID}_outdoor_temperature", + name="Outdoor Temperature", + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + native_unit_of_measurement=hass.config.units.temperature_unit, + value=lambda device: device.outdoorTemperature, + ), + location, + device, + ) + ) + if device.displayedOutdoorHumidity: + entities.append( + LyricSensor( + coordinator, + LyricSensorEntityDescription( + key=f"{device.macID}_outdoor_humidity", + name="Outdoor Humidity", + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + native_unit_of_measurement="%", + value=lambda device: device.displayedOutdoorHumidity, + ), + location, + device, + ) + ) + if device.changeableValues: + if device.changeableValues.nextPeriodTime: + entities.append( + LyricSensor( + coordinator, + LyricSensorEntityDescription( + key=f"{device.macID}_next_period_time", + name="Next Period Time", + device_class=DEVICE_CLASS_TIMESTAMP, + value=lambda device: get_datetime_from_future_time( + device.changeableValues.nextPeriodTime + ).isoformat(), + ), + location, + device, + ) + ) + if device.changeableValues.thermostatSetpointStatus: + entities.append( + LyricSensor( + coordinator, + LyricSensorEntityDescription( + key=f"{device.macID}_setpoint_status", + name="Setpoint Status", + icon="mdi:thermostat", + value=lambda device: get_setpoint_status( + device.changeableValues.thermostatSetpointStatus, + device.changeableValues.nextPeriodTime, + ), + ), + location, + device, + ) + ) async_add_entities(entities, True) class LyricSensor(LyricDeviceEntity, SensorEntity): - """Defines a Honeywell Lyric sensor.""" + """Define a Honeywell Lyric sensor.""" + + coordinator: DataUpdateCoordinator + entity_description: LyricSensorEntityDescription def __init__( self, coordinator: DataUpdateCoordinator, + description: LyricSensorEntityDescription, location: LyricLocation, device: LyricDevice, - key: str, - name: str, - icon: str, - device_class: str = None, - unit_of_measurement: str = None, ) -> None: - """Initialize Honeywell Lyric sensor.""" - self._device_class = device_class - self._unit_of_measurement = unit_of_measurement - - super().__init__(coordinator, location, device, key, name, icon) - - @property - def device_class(self) -> str: - """Return the device class of the sensor.""" - return self._device_class - - @property - def native_unit_of_measurement(self) -> str: - """Return the unit this state is expressed in.""" - return self._unit_of_measurement - - -class LyricIndoorTemperatureSensor(LyricSensor): - """Defines a Honeywell Lyric sensor.""" - - def __init__( - self, - coordinator: DataUpdateCoordinator, - location: LyricLocation, - device: LyricDevice, - unit_of_measurement: str = None, - ) -> None: - """Initialize Honeywell Lyric sensor.""" - + """Initialize.""" super().__init__( coordinator, location, device, - f"{device.macID}_indoor_temperature", - "Indoor Temperature", - None, - DEVICE_CLASS_TEMPERATURE, - unit_of_measurement, + description.key, ) + self.entity_description = description @property - def native_value(self) -> str: - """Return the state of the sensor.""" - return self.device.indoorTemperature - - -class LyricOutdoorTemperatureSensor(LyricSensor): - """Defines a Honeywell Lyric sensor.""" - - def __init__( - self, - coordinator: DataUpdateCoordinator, - location: LyricLocation, - device: LyricDevice, - unit_of_measurement: str = None, - ) -> None: - """Initialize Honeywell Lyric sensor.""" - - super().__init__( - coordinator, - location, - device, - f"{device.macID}_outdoor_temperature", - "Outdoor Temperature", - None, - DEVICE_CLASS_TEMPERATURE, - unit_of_measurement, - ) - - @property - def native_value(self) -> str: - """Return the state of the sensor.""" - return self.device.outdoorTemperature - - -class LyricOutdoorHumiditySensor(LyricSensor): - """Defines a Honeywell Lyric sensor.""" - - def __init__( - self, - coordinator: DataUpdateCoordinator, - location: LyricLocation, - device: LyricDevice, - unit_of_measurement: str = None, - ) -> None: - """Initialize Honeywell Lyric sensor.""" - - super().__init__( - coordinator, - location, - device, - f"{device.macID}_outdoor_humidity", - "Outdoor Humidity", - None, - DEVICE_CLASS_HUMIDITY, - "%", - ) - - @property - def native_value(self) -> str: - """Return the state of the sensor.""" - return self.device.displayedOutdoorHumidity - - -class LyricNextPeriodSensor(LyricSensor): - """Defines a Honeywell Lyric sensor.""" - - def __init__( - self, - coordinator: DataUpdateCoordinator, - location: LyricLocation, - device: LyricDevice, - unit_of_measurement: str = None, - ) -> None: - """Initialize Honeywell Lyric sensor.""" - - super().__init__( - coordinator, - location, - device, - f"{device.macID}_next_period_time", - "Next Period Time", - None, - DEVICE_CLASS_TIMESTAMP, - ) - - @property - def native_value(self) -> datetime: - """Return the state of the sensor.""" - device = self.device - time = dt_util.parse_time(device.changeableValues.nextPeriodTime) - now = dt_util.utcnow() - if time <= now.time(): - now = now + timedelta(days=1) - return dt_util.as_utc(datetime.combine(now.date(), time)) - - -class LyricSetpointStatusSensor(LyricSensor): - """Defines a Honeywell Lyric sensor.""" - - def __init__( - self, - coordinator: DataUpdateCoordinator, - location: LyricLocation, - device: LyricDevice, - unit_of_measurement: str = None, - ) -> None: - """Initialize Honeywell Lyric sensor.""" - - super().__init__( - coordinator, - location, - device, - f"{device.macID}_setpoint_status", - "Setpoint Status", - "mdi:thermostat", - None, - ) - - @property - def native_value(self) -> str: - """Return the state of the sensor.""" - device = self.device - if device.changeableValues.thermostatSetpointStatus == PRESET_HOLD_UNTIL: - return f"Held until {device.changeableValues.nextPeriodTime}" - return LYRIC_SETPOINT_STATUS_NAMES.get( - device.changeableValues.thermostatSetpointStatus, "Unknown" - ) + def native_value(self) -> StateType: + """Return the state.""" + device: LyricDevice = self.device + try: + return cast(StateType, self.entity_description.value(device)) + except TypeError: + return None