diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 4a394692dd8..b9f0740ea0c 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -365,27 +365,8 @@ class GuardianEntity(CoordinatorEntity[GuardianDataUpdateCoordinator]): """Initialize.""" super().__init__(coordinator) - self._attr_extra_state_attributes = {} self.entity_description = description - @callback - def _async_update_from_latest_data(self) -> None: - """Update the entity's underlying data. - - This should be extended by Guardian platforms. - """ - - @callback - def _handle_coordinator_update(self) -> None: - """Respond to a DataUpdateCoordinator update.""" - self._async_update_from_latest_data() - self.async_write_ha_state() - - async def async_added_to_hass(self) -> None: - """Handle entity which will be added.""" - await super().async_added_to_hass() - self._async_update_from_latest_data() - class PairedSensorEntity(GuardianEntity): """Define a Guardian paired sensor entity.""" @@ -410,14 +391,14 @@ class PairedSensorEntity(GuardianEntity): self._attr_unique_id = f"{paired_sensor_uid}_{description.key}" -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ValveControllerEntityDescriptionMixin: """Define an entity description mixin for valve controller entities.""" api_category: str -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ValveControllerEntityDescription( EntityDescription, ValveControllerEntityDescriptionMixin ): diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index 179158ab512..abe005aae33 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -1,7 +1,9 @@ """Binary sensors for the Elexa Guardian integration.""" from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass +from typing import Any from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, @@ -39,24 +41,35 @@ SENSOR_KIND_LEAK_DETECTED = "leak_detected" SENSOR_KIND_MOVED = "moved" -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) +class PairedSensorBinarySensorDescription(BinarySensorEntityDescription): + """Describe a Guardian paired sensor binary sensor.""" + + is_on_fn: Callable[[dict[str, Any]], bool] + + +@dataclass(frozen=True, kw_only=True) class ValveControllerBinarySensorDescription( BinarySensorEntityDescription, ValveControllerEntityDescription ): """Describe a Guardian valve controller binary sensor.""" + is_on_fn: Callable[[dict[str, Any]], bool] + PAIRED_SENSOR_DESCRIPTIONS = ( - BinarySensorEntityDescription( + PairedSensorBinarySensorDescription( key=SENSOR_KIND_LEAK_DETECTED, translation_key="leak", device_class=BinarySensorDeviceClass.MOISTURE, + is_on_fn=lambda data: data["wet"], ), - BinarySensorEntityDescription( + PairedSensorBinarySensorDescription( key=SENSOR_KIND_MOVED, translation_key="moved", device_class=BinarySensorDeviceClass.MOVING, entity_category=EntityCategory.DIAGNOSTIC, + is_on_fn=lambda data: data["moved"], ), ) @@ -66,6 +79,7 @@ VALVE_CONTROLLER_DESCRIPTIONS = ( translation_key="leak", device_class=BinarySensorDeviceClass.MOISTURE, api_category=API_SYSTEM_ONBOARD_SENSOR_STATUS, + is_on_fn=lambda data: data["wet"], ), ) @@ -133,7 +147,7 @@ async def async_setup_entry( class PairedSensorBinarySensor(PairedSensorEntity, BinarySensorEntity): """Define a binary sensor related to a Guardian valve controller.""" - entity_description: BinarySensorEntityDescription + entity_description: PairedSensorBinarySensorDescription def __init__( self, @@ -146,13 +160,10 @@ class PairedSensorBinarySensor(PairedSensorEntity, BinarySensorEntity): self._attr_is_on = True - @callback - def _async_update_from_latest_data(self) -> None: - """Update the entity's underlying data.""" - if self.entity_description.key == SENSOR_KIND_LEAK_DETECTED: - self._attr_is_on = self.coordinator.data["wet"] - elif self.entity_description.key == SENSOR_KIND_MOVED: - self._attr_is_on = self.coordinator.data["moved"] + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + return self.entity_description.is_on_fn(self.coordinator.data) class ValveControllerBinarySensor(ValveControllerEntity, BinarySensorEntity): @@ -171,8 +182,7 @@ class ValveControllerBinarySensor(ValveControllerEntity, BinarySensorEntity): self._attr_is_on = True - @callback - def _async_update_from_latest_data(self) -> None: - """Update the entity.""" - if self.entity_description.key == SENSOR_KIND_LEAK_DETECTED: - self._attr_is_on = self.coordinator.data["wet"] + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + return self.entity_description.is_on_fn(self.coordinator.data) diff --git a/homeassistant/components/guardian/button.py b/homeassistant/components/guardian/button.py index 7a931f35019..485de90f1d8 100644 --- a/homeassistant/components/guardian/button.py +++ b/homeassistant/components/guardian/button.py @@ -23,21 +23,14 @@ from . import GuardianData, ValveControllerEntity, ValveControllerEntityDescript from .const import API_SYSTEM_DIAGNOSTICS, DOMAIN -@dataclass(frozen=True) -class GuardianButtonEntityDescriptionMixin: - """Define an mixin for button entities.""" - - push_action: Callable[[Client], Awaitable] - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ValveControllerButtonDescription( - ButtonEntityDescription, - ValveControllerEntityDescription, - GuardianButtonEntityDescriptionMixin, + ButtonEntityDescription, ValveControllerEntityDescription ): """Describe a Guardian valve controller button.""" + push_action: Callable[[Client], Awaitable] + BUTTON_KIND_REBOOT = "reboot" BUTTON_KIND_RESET_VALVE_DIAGNOSTICS = "reset_valve_diagnostics" diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 68833234b15..85adaddb7f2 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -1,7 +1,9 @@ """Sensors for the Elexa Guardian integration.""" from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass +from typing import Any from homeassistant.components.sensor import ( SensorDeviceClass, @@ -19,6 +21,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType from . import ( GuardianData, @@ -39,25 +42,36 @@ SENSOR_KIND_TEMPERATURE = "temperature" SENSOR_KIND_UPTIME = "uptime" -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) +class PairedSensorDescription(SensorEntityDescription): + """Describe a Guardian paired sensor.""" + + value_fn: Callable[[dict[str, Any]], StateType] + + +@dataclass(frozen=True, kw_only=True) class ValveControllerSensorDescription( SensorEntityDescription, ValveControllerEntityDescription ): """Describe a Guardian valve controller sensor.""" + value_fn: Callable[[dict[str, Any]], StateType] + PAIRED_SENSOR_DESCRIPTIONS = ( - SensorEntityDescription( + PairedSensorDescription( key=SENSOR_KIND_BATTERY, device_class=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfElectricPotential.VOLT, + value_fn=lambda data: data["battery"], ), - SensorEntityDescription( + PairedSensorDescription( key=SENSOR_KIND_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, state_class=SensorStateClass.MEASUREMENT, + value_fn=lambda data: data["temperature"], ), ) VALVE_CONTROLLER_DESCRIPTIONS = ( @@ -67,6 +81,7 @@ VALVE_CONTROLLER_DESCRIPTIONS = ( native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, state_class=SensorStateClass.MEASUREMENT, api_category=API_SYSTEM_ONBOARD_SENSOR_STATUS, + value_fn=lambda data: data["temperature"], ), ValveControllerSensorDescription( key=SENSOR_KIND_UPTIME, @@ -75,6 +90,7 @@ VALVE_CONTROLLER_DESCRIPTIONS = ( entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfTime.MINUTES, api_category=API_SYSTEM_DIAGNOSTICS, + value_fn=lambda data: data["uptime"], ), ) @@ -125,15 +141,12 @@ async def async_setup_entry( class PairedSensorSensor(PairedSensorEntity, SensorEntity): """Define a binary sensor related to a Guardian valve controller.""" - entity_description: SensorEntityDescription + entity_description: PairedSensorDescription - @callback - def _async_update_from_latest_data(self) -> None: - """Update the entity's underlying data.""" - if self.entity_description.key == SENSOR_KIND_BATTERY: - self._attr_native_value = self.coordinator.data["battery"] - elif self.entity_description.key == SENSOR_KIND_TEMPERATURE: - self._attr_native_value = self.coordinator.data["temperature"] + @property + def native_value(self) -> StateType: + """Return the value reported by the sensor.""" + return self.entity_description.value_fn(self.coordinator.data) class ValveControllerSensor(ValveControllerEntity, SensorEntity): @@ -141,10 +154,7 @@ class ValveControllerSensor(ValveControllerEntity, SensorEntity): entity_description: ValveControllerSensorDescription - @callback - def _async_update_from_latest_data(self) -> None: - """Update the entity's underlying data.""" - if self.entity_description.key == SENSOR_KIND_TEMPERATURE: - self._attr_native_value = self.coordinator.data["temperature"] - elif self.entity_description.key == SENSOR_KIND_UPTIME: - self._attr_native_value = self.coordinator.data["uptime"] + @property + def native_value(self) -> StateType: + """Return the value reported by the sensor.""" + return self.entity_description.value_fn(self.coordinator.data) diff --git a/homeassistant/components/guardian/switch.py b/homeassistant/components/guardian/switch.py index 98179c1922f..81f06ba4356 100644 --- a/homeassistant/components/guardian/switch.py +++ b/homeassistant/components/guardian/switch.py @@ -1,7 +1,7 @@ """Switches for the Elexa Guardian integration.""" from __future__ import annotations -from collections.abc import Awaitable, Callable +from collections.abc import Awaitable, Callable, Mapping from dataclasses import dataclass from typing import Any @@ -11,7 +11,7 @@ from aioguardian.errors import GuardianError from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -28,21 +28,25 @@ ATTR_TRAVEL_COUNT = "travel_count" SWITCH_KIND_ONBOARD_AP = "onboard_ap" SWITCH_KIND_VALVE = "valve" - -@dataclass(frozen=True) -class SwitchDescriptionMixin: - """Define an entity description mixin for Guardian switches.""" - - off_action: Callable[[Client], Awaitable] - on_action: Callable[[Client], Awaitable] +ON_STATES = { + "start_opening", + "opening", + "finish_opening", + "opened", +} -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ValveControllerSwitchDescription( - SwitchEntityDescription, ValveControllerEntityDescription, SwitchDescriptionMixin + SwitchEntityDescription, ValveControllerEntityDescription ): """Describe a Guardian valve controller switch.""" + extra_state_attributes_fn: Callable[[dict[str, Any]], Mapping[str, Any]] + is_on_fn: Callable[[dict[str, Any]], bool] + off_fn: Callable[[Client], Awaitable] + on_fn: Callable[[Client], Awaitable] + async def _async_disable_ap(client: Client) -> None: """Disable the onboard AP.""" @@ -70,17 +74,29 @@ VALVE_CONTROLLER_DESCRIPTIONS = ( translation_key="onboard_access_point", icon="mdi:wifi", entity_category=EntityCategory.CONFIG, + extra_state_attributes_fn=lambda data: { + ATTR_CONNECTED_CLIENTS: data.get("ap_clients"), + ATTR_STATION_CONNECTED: data["station_connected"], + }, api_category=API_WIFI_STATUS, - off_action=_async_disable_ap, - on_action=_async_enable_ap, + is_on_fn=lambda data: data["ap_enabled"], + off_fn=_async_disable_ap, + on_fn=_async_enable_ap, ), ValveControllerSwitchDescription( key=SWITCH_KIND_VALVE, translation_key="valve_controller", icon="mdi:water", api_category=API_VALVE_STATUS, - off_action=_async_close_valve, - on_action=_async_open_valve, + extra_state_attributes_fn=lambda data: { + ATTR_AVG_CURRENT: data["average_current"], + ATTR_INST_CURRENT: data["instantaneous_current"], + ATTR_INST_CURRENT_DDT: data["instantaneous_current_ddt"], + ATTR_TRAVEL_COUNT: data["travel_count"], + }, + is_on_fn=lambda data: data["state"] in ON_STATES, + off_fn=_async_close_valve, + on_fn=_async_open_valve, ), ) @@ -100,13 +116,6 @@ async def async_setup_entry( class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): """Define a switch related to a Guardian valve controller.""" - ON_STATES = { - "start_opening", - "opening", - "finish_opening", - "opened", - } - entity_description: ValveControllerSwitchDescription def __init__( @@ -120,29 +129,15 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): self._client = data.client - @callback - def _async_update_from_latest_data(self) -> None: - """Update the entity.""" - if self.entity_description.key == SWITCH_KIND_ONBOARD_AP: - self._attr_extra_state_attributes.update( - { - ATTR_CONNECTED_CLIENTS: self.coordinator.data.get("ap_clients"), - ATTR_STATION_CONNECTED: self.coordinator.data["station_connected"], - } - ) - self._attr_is_on = self.coordinator.data["ap_enabled"] - elif self.entity_description.key == SWITCH_KIND_VALVE: - self._attr_is_on = self.coordinator.data["state"] in self.ON_STATES - self._attr_extra_state_attributes.update( - { - ATTR_AVG_CURRENT: self.coordinator.data["average_current"], - ATTR_INST_CURRENT: self.coordinator.data["instantaneous_current"], - ATTR_INST_CURRENT_DDT: self.coordinator.data[ - "instantaneous_current_ddt" - ], - ATTR_TRAVEL_COUNT: self.coordinator.data["travel_count"], - } - ) + @property + def extra_state_attributes(self) -> Mapping[str, Any]: + """Return entity specific state attributes.""" + return self.entity_description.extra_state_attributes_fn(self.coordinator.data) + + @property + def is_on(self) -> bool: + """Return True if entity is on.""" + return self.entity_description.is_on_fn(self.coordinator.data) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" @@ -151,7 +146,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): try: async with self._client: - await self.entity_description.off_action(self._client) + await self.entity_description.off_fn(self._client) except GuardianError as err: raise HomeAssistantError( f'Error while turning "{self.entity_id}" off: {err}' @@ -167,7 +162,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): try: async with self._client: - await self.entity_description.on_action(self._client) + await self.entity_description.on_fn(self._client) except GuardianError as err: raise HomeAssistantError( f'Error while turning "{self.entity_id}" on: {err}'