diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 7f631c9ee02..8eaeafcd3f4 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -19,7 +19,7 @@ from .const import CONF_SLEEP_PERIOD from .entity import ( BlockAttributeDescription, RestEntityDescription, - RpcAttributeDescription, + RpcEntityDescription, ShellyBlockAttributeEntity, ShellyRestAttributeEntity, ShellyRpcAttributeEntity, @@ -35,6 +35,11 @@ from .utils import ( ) +@dataclass +class RpcBinarySensorDescription(RpcEntityDescription, BinarySensorEntityDescription): + """Class to describe a RPC binary sensor.""" + + @dataclass class RestBinarySensorDescription(RestEntityDescription, BinarySensorEntityDescription): """Class to describe a REST binary sensor.""" @@ -134,28 +139,28 @@ REST_SENSORS: Final = { } RPC_SENSORS: Final = { - "input": RpcAttributeDescription( + "input": RpcBinarySensorDescription( key="input", sub_key="state", name="Input", device_class=BinarySensorDeviceClass.POWER, - default_enabled=False, + entity_registry_enabled_default=False, removal_condition=is_rpc_momentary_input, ), - "cloud": RpcAttributeDescription( + "cloud": RpcBinarySensorDescription( key="cloud", sub_key="connected", name="Cloud", device_class=BinarySensorDeviceClass.CONNECTIVITY, - default_enabled=False, + entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), - "fwupdate": RpcAttributeDescription( + "fwupdate": RpcBinarySensorDescription( key="sys", sub_key="available_updates", name="Firmware Update", device_class=BinarySensorDeviceClass.UPDATE, - default_enabled=False, + entity_registry_enabled_default=False, extra_state_attributes=lambda status, shelly: { "latest_stable_version": status.get("stable", {"version": ""})["version"], "installed_version": shelly["ver"], @@ -223,9 +228,13 @@ class RestBinarySensor(ShellyRestAttributeEntity, BinarySensorEntity): class RpcBinarySensor(ShellyRpcAttributeEntity, BinarySensorEntity): """Represent a RPC binary sensor entity.""" + entity_description: RpcBinarySensorDescription + @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if RPC 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 aa16f66992f..2b0c310e5a7 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -158,7 +158,7 @@ async def async_setup_entry_rpc( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, - sensors: dict[str, RpcAttributeDescription], + sensors: Mapping[str, RpcEntityDescription], sensor_class: Callable, ) -> None: """Set up entities for REST sensors.""" @@ -188,7 +188,7 @@ async def async_setup_entry_rpc( unique_id = f"{wrapper.mac}-{key}-{sensor_id}" await async_remove_shelly_entity(hass, domain, unique_id) else: - if description.should_poll: + if description.use_polling_wrapper: entities.append( sensor_class(polling_wrapper, key, sensor_id, description) ) @@ -251,23 +251,21 @@ class BlockAttributeDescription: @dataclass -class RpcAttributeDescription: - """Class to describe a RPC sensor.""" +class RpcEntityRequiredKeysMixin: + """Class for RPC entity required keys.""" - key: str sub_key: str - name: str - icon: str | None = None - unit: str | None = None + + +@dataclass +class RpcEntityDescription(EntityDescription, RpcEntityRequiredKeysMixin): + """Class to describe a RPC entity.""" + value: Callable[[Any, Any], Any] | None = None - device_class: str | None = None - state_class: str | None = None - default_enabled: bool = True available: Callable[[dict], bool] | None = None removal_condition: Callable[[dict, str], bool] | None = None extra_state_attributes: Callable[[dict, dict], dict | None] | None = None - entity_category: EntityCategory | None = None - should_poll: bool = False + use_polling_wrapper: bool = False @dataclass @@ -535,35 +533,36 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity): class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity): """Helper class to represent a rpc attribute.""" + entity_description: RpcEntityDescription + def __init__( self, wrapper: RpcDeviceWrapper, key: str, attribute: str, - description: RpcAttributeDescription, + description: RpcEntityDescription, ) -> None: """Initialize sensor.""" super().__init__(wrapper, key) - self.sub_key = description.sub_key self.attribute = attribute - self.description = description + self.entity_description = description self._attr_unique_id = f"{super().unique_id}-{attribute}" self._attr_name = get_rpc_entity_name(wrapper.device, key, description.name) - self._attr_entity_registry_enabled_default = description.default_enabled - self._attr_device_class = description.device_class - self._attr_icon = description.icon self._last_value = None @property def attribute_value(self) -> StateType: """Value of sensor.""" - if callable(self.description.value): - self._last_value = self.description.value( - self.wrapper.device.status[self.key][self.sub_key], self._last_value + if callable(self.entity_description.value): + self._last_value = self.entity_description.value( + self.wrapper.device.status[self.key][self.entity_description.sub_key], + self._last_value, ) else: - self._last_value = self.wrapper.device.status[self.key][self.sub_key] + self._last_value = self.wrapper.device.status[self.key][ + self.entity_description.sub_key + ] return self._last_value @@ -572,31 +571,26 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity): """Available.""" available = super().available - if not available or not self.description.available: + if not available or not self.entity_description.available: return available - return self.description.available( - self.wrapper.device.status[self.key][self.sub_key] + return self.entity_description.available( + self.wrapper.device.status[self.key][self.entity_description.sub_key] ) @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 assert self.wrapper.device.shelly - return self.description.extra_state_attributes( - self.wrapper.device.status[self.key][self.sub_key], + return self.entity_description.extra_state_attributes( + self.wrapper.device.status[self.key][self.entity_description.sub_key], self.wrapper.device.shelly, ) - @property - def entity_category(self) -> EntityCategory | None: - """Return category of entity.""" - return self.description.entity_category - class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEntity): """Represent a shelly sleeping block attribute entity.""" diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index ee1017290ab..4339fc1c6fa 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -32,7 +32,7 @@ from .const import CONF_SLEEP_PERIOD, SHAIR_MAX_WORK_HOURS from .entity import ( BlockAttributeDescription, RestEntityDescription, - RpcAttributeDescription, + RpcEntityDescription, ShellyBlockAttributeEntity, ShellyRestAttributeEntity, ShellyRpcAttributeEntity, @@ -44,6 +44,11 @@ from .entity import ( from .utils import get_device_entry_gen, get_device_uptime, temperature_unit +@dataclass +class RpcSensorDescription(RpcEntityDescription, SensorEntityDescription): + """Class to describe a RPC sensor.""" + + @dataclass class RestSensorDescription(RestEntityDescription, SensorEntityDescription): """Class to describe a REST sensor.""" @@ -259,66 +264,66 @@ REST_SENSORS: Final = { RPC_SENSORS: Final = { - "power": RpcAttributeDescription( + "power": RpcSensorDescription( key="switch", sub_key="apower", name="Power", - unit=POWER_WATT, + native_unit_of_measurement=POWER_WATT, value=lambda status, _: round(float(status), 1), device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, ), - "voltage": RpcAttributeDescription( + "voltage": RpcSensorDescription( key="switch", sub_key="voltage", name="Voltage", - unit=ELECTRIC_POTENTIAL_VOLT, + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, value=lambda status, _: round(float(status), 1), device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, - default_enabled=False, + entity_registry_enabled_default=False, ), - "energy": RpcAttributeDescription( + "energy": RpcSensorDescription( key="switch", sub_key="aenergy", name="Energy", - unit=ENERGY_KILO_WATT_HOUR, + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, value=lambda status, _: round(status["total"] / 1000, 2), device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), - "temperature": RpcAttributeDescription( + "temperature": RpcSensorDescription( key="switch", sub_key="temperature", name="Temperature", - unit=TEMP_CELSIUS, + native_unit_of_measurement=TEMP_CELSIUS, value=lambda status, _: round(status["tC"], 1), device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - default_enabled=False, + entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, - should_poll=True, + use_polling_wrapper=True, ), - "rssi": RpcAttributeDescription( + "rssi": RpcSensorDescription( key="wifi", sub_key="rssi", name="RSSI", - unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, device_class=SensorDeviceClass.SIGNAL_STRENGTH, state_class=SensorStateClass.MEASUREMENT, - default_enabled=False, + entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, - should_poll=True, + use_polling_wrapper=True, ), - "uptime": RpcAttributeDescription( + "uptime": RpcSensorDescription( key="sys", sub_key="uptime", name="Uptime", value=get_device_uptime, device_class=SensorDeviceClass.TIMESTAMP, - default_enabled=False, + entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, - should_poll=True, + use_polling_wrapper=True, ), } @@ -380,21 +385,13 @@ class RestSensor(ShellyRestAttributeEntity, SensorEntity): class RpcSensor(ShellyRpcAttributeEntity, SensorEntity): """Represent a RPC sensor.""" + entity_description: RpcSensorDescription + @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 BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity): """Represent a block sleeping sensor."""