diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index c266e88adc0..7f631c9ee02 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -1,11 +1,13 @@ """Binary sensor for Shelly.""" from __future__ import annotations +from dataclasses import dataclass from typing import Final, cast from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, + BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON @@ -16,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import CONF_SLEEP_PERIOD from .entity import ( BlockAttributeDescription, - RestAttributeDescription, + RestEntityDescription, RpcAttributeDescription, ShellyBlockAttributeEntity, ShellyRestAttributeEntity, @@ -32,6 +34,12 @@ from .utils import ( is_rpc_momentary_input, ) + +@dataclass +class RestBinarySensorDescription(RestEntityDescription, BinarySensorEntityDescription): + """Class to describe a REST binary sensor.""" + + SENSORS: Final = { ("device", "overtemp"): BlockAttributeDescription( name="Overheating", @@ -102,18 +110,20 @@ SENSORS: Final = { } REST_SENSORS: Final = { - "cloud": RestAttributeDescription( + "cloud": RestBinarySensorDescription( + key="cloud", name="Cloud", value=lambda status, _: status["cloud"]["connected"], device_class=BinarySensorDeviceClass.CONNECTIVITY, - default_enabled=False, + entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), - "fwupdate": RestAttributeDescription( + "fwupdate": RestBinarySensorDescription( + key="fwupdate", name="Firmware Update", device_class=BinarySensorDeviceClass.UPDATE, value=lambda status, _: status["update"]["has_update"], - default_enabled=False, + entity_registry_enabled_default=False, extra_state_attributes=lambda status: { "latest_stable_version": status["update"]["new_version"], "installed_version": status["update"]["old_version"], @@ -200,9 +210,13 @@ class BlockBinarySensor(ShellyBlockAttributeEntity, BinarySensorEntity): class RestBinarySensor(ShellyRestAttributeEntity, BinarySensorEntity): """Represent a REST binary sensor entity.""" + entity_description: RestBinarySensorDescription + @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if REST sensor state is on.""" + if self.attribute_value is None: + return None return bool(self.attribute_value) diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 66a87f58122..aa16f66992f 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from collections.abc import Callable +from collections.abc import Callable, Mapping from dataclasses import dataclass import logging from typing import Any, Final, cast @@ -19,7 +19,7 @@ from homeassistant.helpers import ( entity_registry, update_coordinator, ) -from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity import DeviceInfo, EntityCategory, EntityDescription from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import StateType @@ -205,7 +205,7 @@ async def async_setup_entry_rest( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, - sensors: dict[str, RestAttributeDescription], + sensors: Mapping[str, RestEntityDescription], sensor_class: Callable, ) -> None: """Set up entities for REST sensors.""" @@ -271,18 +271,11 @@ class RpcAttributeDescription: @dataclass -class RestAttributeDescription: - """Class to describe a REST sensor.""" +class RestEntityDescription(EntityDescription): + """Class to describe a REST entity.""" - name: str - icon: str | None = None - unit: str | None = None value: Callable[[dict, Any], Any] | None = None - device_class: str | None = None - state_class: str | None = None - default_enabled: bool = True extra_state_attributes: Callable[[dict], dict | None] | None = None - entity_category: EntityCategory | None = None class ShellyBlockEntity(entity.Entity): @@ -494,37 +487,26 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity): class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity): """Class to load info from REST.""" + entity_description: RestEntityDescription + def __init__( self, wrapper: BlockDeviceWrapper, attribute: str, - description: RestAttributeDescription, + description: RestEntityDescription, ) -> None: """Initialize sensor.""" super().__init__(wrapper) self.wrapper = wrapper self.attribute = attribute - self.description = description - self._name = get_block_entity_name(wrapper.device, None, self.description.name) + self.entity_description = description + self._attr_name = get_block_entity_name(wrapper.device, None, description.name) + self._attr_unique_id = f"{wrapper.mac}-{attribute}" + self._attr_device_info = DeviceInfo( + connections={(device_registry.CONNECTION_NETWORK_MAC, wrapper.mac)} + ) self._last_value = None - @property - def name(self) -> str: - """Name of sensor.""" - return self._name - - @property - def device_info(self) -> DeviceInfo: - """Device info.""" - return { - "connections": {(device_registry.CONNECTION_NETWORK_MAC, self.wrapper.mac)} - } - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if it should be enabled by default.""" - return self.description.default_enabled - @property def available(self) -> bool: """Available.""" @@ -533,39 +515,21 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity): @property def attribute_value(self) -> StateType: """Value of sensor.""" - if callable(self.description.value): - self._last_value = self.description.value( + if callable(self.entity_description.value): + self._last_value = self.entity_description.value( self.wrapper.device.status, self._last_value ) return self._last_value - @property - def device_class(self) -> str | None: - """Device class of sensor.""" - return self.description.device_class - - @property - def icon(self) -> str | None: - """Icon of sensor.""" - return self.description.icon - - @property - def unique_id(self) -> str: - """Return unique ID of entity.""" - return f"{self.wrapper.mac}-{self.attribute}" - @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes.""" - if self.description.extra_state_attributes is None: + if self.entity_description.extra_state_attributes is None: return None - return self.description.extra_state_attributes(self.wrapper.device.status) - - @property - def entity_category(self) -> EntityCategory | None: - """Return category of entity.""" - return self.description.entity_category + return self.entity_description.extra_state_attributes( + self.wrapper.device.status + ) class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity): diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 562bf70260d..ee1017290ab 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -1,11 +1,13 @@ """Sensor for Shelly.""" from __future__ import annotations +from dataclasses import dataclass from typing import Final, cast from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, + SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry @@ -29,7 +31,7 @@ from homeassistant.helpers.typing import StateType from .const import CONF_SLEEP_PERIOD, SHAIR_MAX_WORK_HOURS from .entity import ( BlockAttributeDescription, - RestAttributeDescription, + RestEntityDescription, RpcAttributeDescription, ShellyBlockAttributeEntity, ShellyRestAttributeEntity, @@ -41,6 +43,12 @@ from .entity import ( ) from .utils import get_device_entry_gen, get_device_uptime, temperature_unit + +@dataclass +class RestSensorDescription(RestEntityDescription, SensorEntityDescription): + """Class to describe a REST sensor.""" + + SENSORS: Final = { ("device", "battery"): BlockAttributeDescription( name="Battery", @@ -229,20 +237,22 @@ SENSORS: Final = { } REST_SENSORS: Final = { - "rssi": RestAttributeDescription( + "rssi": RestSensorDescription( + key="rssi", name="RSSI", - unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, value=lambda status, _: status["wifi_sta"]["rssi"], device_class=SensorDeviceClass.SIGNAL_STRENGTH, state_class=SensorStateClass.MEASUREMENT, - default_enabled=False, + entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), - "uptime": RestAttributeDescription( + "uptime": RestSensorDescription( + key="uptime", name="Uptime", value=lambda status, last: get_device_uptime(status["uptime"], last), device_class=SensorDeviceClass.TIMESTAMP, - default_enabled=False, + entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), } @@ -359,21 +369,13 @@ class BlockSensor(ShellyBlockAttributeEntity, SensorEntity): class RestSensor(ShellyRestAttributeEntity, SensorEntity): """Represent a REST sensor.""" + entity_description: RestSensorDescription + @property def native_value(self) -> StateType: """Return value of sensor.""" return self.attribute_value - @property - def state_class(self) -> str | None: - """State class of sensor.""" - return self.description.state_class - - @property - def native_unit_of_measurement(self) -> str | None: - """Return unit of sensor.""" - return self.description.unit - class RpcSensor(ShellyRpcAttributeEntity, SensorEntity): """Represent a RPC sensor."""