diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index 84ef3a2b7db..349e4f871a3 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -12,6 +12,7 @@ from aiolyric import Lyric from aiolyric.exceptions import LyricAuthenticationException, LyricException from aiolyric.objects.device import LyricDevice from aiolyric.objects.location import LyricLocation +from aiolyric.objects.priority import LyricAccessories, LyricRoom from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -77,6 +78,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: async with asyncio.timeout(60): await lyric.get_locations() + await asyncio.gather( + *( + lyric.get_thermostat_rooms(location.locationID, device.deviceID) + for location in lyric.locations + for device in location.devices + if device.deviceClass == "Thermostat" + ) + ) + except LyricAuthenticationException as exception: # Attempt to refresh the token before failing. # Honeywell appear to have issues keeping tokens saved. @@ -159,8 +169,43 @@ class LyricDeviceEntity(LyricEntity): def device_info(self) -> DeviceInfo: """Return device information about this Honeywell Lyric instance.""" return DeviceInfo( + identifiers={(dr.CONNECTION_NETWORK_MAC, self._mac_id)}, connections={(dr.CONNECTION_NETWORK_MAC, self._mac_id)}, manufacturer="Honeywell", model=self.device.deviceModel, - name=self.device.name, + name=f"{self.device.name} Thermostat", + ) + + +class LyricAccessoryEntity(LyricDeviceEntity): + """Defines a Honeywell Lyric accessory entity, a sub-device of a thermostat.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator[Lyric], + location: LyricLocation, + device: LyricDevice, + room: LyricRoom, + accessory: LyricAccessories, + key: str, + ) -> None: + """Initialize the Honeywell Lyric accessory entity.""" + super().__init__(coordinator, location, device, key) + self._room = room + self._accessory = accessory + + @property + def device_info(self) -> DeviceInfo: + """Return device information about this Honeywell Lyric instance.""" + return DeviceInfo( + identifiers={ + ( + f"{dr.CONNECTION_NETWORK_MAC}_room_accessory", + f"{self._mac_id}_room{self._room.id}_accessory{self._accessory.id}", + ) + }, + manufacturer="Honeywell", + model="RCHTSENSOR", + name=f"{self._room.roomName} Sensor", + via_device=(dr.CONNECTION_NETWORK_MAC, self._mac_id), ) diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index 276336e02cc..64f60fa6611 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -9,6 +9,7 @@ from datetime import datetime, timedelta from aiolyric import Lyric from aiolyric.objects.device import LyricDevice from aiolyric.objects.location import LyricLocation +from aiolyric.objects.priority import LyricAccessories, LyricRoom from homeassistant.components.sensor import ( SensorDeviceClass, @@ -24,7 +25,7 @@ from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import dt as dt_util -from . import LyricDeviceEntity +from . import LyricAccessoryEntity, LyricDeviceEntity from .const import ( DOMAIN, PRESET_HOLD_UNTIL, @@ -50,6 +51,14 @@ class LyricSensorEntityDescription(SensorEntityDescription): suitable_fn: Callable[[LyricDevice], bool] +@dataclass(frozen=True, kw_only=True) +class LyricSensorAccessoryEntityDescription(SensorEntityDescription): + """Class describing Honeywell Lyric room sensor entities.""" + + value_fn: Callable[[LyricRoom, LyricAccessories], StateType | datetime] + suitable_fn: Callable[[LyricRoom, LyricAccessories], bool] + + DEVICE_SENSORS: list[LyricSensorEntityDescription] = [ LyricSensorEntityDescription( key="indoor_temperature", @@ -109,6 +118,26 @@ DEVICE_SENSORS: list[LyricSensorEntityDescription] = [ ), ] +ACCESSORY_SENSORS: list[LyricSensorAccessoryEntityDescription] = [ + LyricSensorAccessoryEntityDescription( + key="room_temperature", + translation_key="room_temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda _, accessory: accessory.temperature, + suitable_fn=lambda _, accessory: accessory.type == "IndoorAirSensor", + ), + LyricSensorAccessoryEntityDescription( + key="room_humidity", + translation_key="room_humidity", + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + value_fn=lambda room, _: room.roomAvgHumidity, + suitable_fn=lambda _, accessory: accessory.type == "IndoorAirSensor", + ), +] + def get_setpoint_status(status: str, time: str) -> str | None: """Get status of the setpoint.""" @@ -147,6 +176,18 @@ async def async_setup_entry( if device_sensor.suitable_fn(device) ) + async_add_entities( + LyricAccessorySensor( + coordinator, accessory_sensor, location, device, room, accessory + ) + for location in coordinator.data.locations + for device in location.devices + for room in coordinator.data.rooms_dict.get(device.macID, {}).values() + for accessory in room.accessories + for accessory_sensor in ACCESSORY_SENSORS + if accessory_sensor.suitable_fn(room, accessory) + ) + class LyricSensor(LyricDeviceEntity, SensorEntity): """Define a Honeywell Lyric sensor.""" @@ -178,3 +219,40 @@ class LyricSensor(LyricDeviceEntity, SensorEntity): def native_value(self) -> StateType | datetime: """Return the state.""" return self.entity_description.value_fn(self.device) + + +class LyricAccessorySensor(LyricAccessoryEntity, SensorEntity): + """Define a Honeywell Lyric sensor.""" + + entity_description: LyricSensorAccessoryEntityDescription + + def __init__( + self, + coordinator: DataUpdateCoordinator[Lyric], + description: LyricSensorAccessoryEntityDescription, + location: LyricLocation, + parentDevice: LyricDevice, + room: LyricRoom, + accessory: LyricAccessories, + ) -> None: + """Initialize.""" + super().__init__( + coordinator, + location, + parentDevice, + room, + accessory, + f"{parentDevice.macID}_room{room.id}_acc{accessory.id}_{description.key}", + ) + self.room = room + self.entity_description = description + if description.device_class == SensorDeviceClass.TEMPERATURE: + if parentDevice.units == "Fahrenheit": + self._attr_native_unit_of_measurement = UnitOfTemperature.FAHRENHEIT + else: + self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS + + @property + def native_value(self) -> StateType | datetime: + """Return the state.""" + return self.entity_description.value_fn(self._room, self._accessory) diff --git a/homeassistant/components/lyric/strings.json b/homeassistant/components/lyric/strings.json index 68bb6292f9e..739ad7fad68 100644 --- a/homeassistant/components/lyric/strings.json +++ b/homeassistant/components/lyric/strings.json @@ -41,6 +41,12 @@ }, "setpoint_status": { "name": "Setpoint status" + }, + "room_temperature": { + "name": "Room temperature" + }, + "room_humidity": { + "name": "Room humidity" } } },