Use entity_description in shelly block sensors (#64897)

* Use entity_description in shelly block sensors

* Re-order binary sensor

* Add async_added_to_hass for BlockSleepingBinarySensor

* Undo None

* Build description when restoring block attribute entities

* Move async_added_to_hass back to base class

Co-authored-by: epenet <epenet@users.noreply.github.com>
pull/64971/head
epenet 2022-01-26 15:07:15 +01:00 committed by GitHub
parent 982580b95a
commit dfdbeba7be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 259 additions and 205 deletions

View File

@ -14,10 +14,11 @@ from homeassistant.const import STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_registry import RegistryEntry
from .const import CONF_SLEEP_PERIOD
from .entity import (
BlockAttributeDescription,
BlockEntityDescription,
RestEntityDescription,
RpcEntityDescription,
ShellyBlockAttributeEntity,
@ -35,6 +36,13 @@ from .utils import (
)
@dataclass
class BlockBinarySensorDescription(
BlockEntityDescription, BinarySensorEntityDescription
):
"""Class to describe a BLOCK binary sensor."""
@dataclass
class RpcBinarySensorDescription(RpcEntityDescription, BinarySensorEntityDescription):
"""Class to describe a RPC binary sensor."""
@ -46,71 +54,83 @@ class RestBinarySensorDescription(RestEntityDescription, BinarySensorEntityDescr
SENSORS: Final = {
("device", "overtemp"): BlockAttributeDescription(
("device", "overtemp"): BlockBinarySensorDescription(
key="device|overtemp",
name="Overheating",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
("device", "overpower"): BlockAttributeDescription(
("device", "overpower"): BlockBinarySensorDescription(
key="device|overpower",
name="Overpowering",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
("light", "overpower"): BlockAttributeDescription(
("light", "overpower"): BlockBinarySensorDescription(
key="light|overpower",
name="Overpowering",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
("relay", "overpower"): BlockAttributeDescription(
("relay", "overpower"): BlockBinarySensorDescription(
key="relay|overpower",
name="Overpowering",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
("sensor", "dwIsOpened"): BlockAttributeDescription(
("sensor", "dwIsOpened"): BlockBinarySensorDescription(
key="sensor|dwIsOpened",
name="Door",
device_class=BinarySensorDeviceClass.OPENING,
available=lambda block: cast(int, block.dwIsOpened) != -1,
),
("sensor", "flood"): BlockAttributeDescription(
name="Flood", device_class=BinarySensorDeviceClass.MOISTURE
("sensor", "flood"): BlockBinarySensorDescription(
key="sensor|flood", name="Flood", device_class=BinarySensorDeviceClass.MOISTURE
),
("sensor", "gas"): BlockAttributeDescription(
("sensor", "gas"): BlockBinarySensorDescription(
key="sensor|gas",
name="Gas",
device_class=BinarySensorDeviceClass.GAS,
value=lambda value: value in ["mild", "heavy"],
extra_state_attributes=lambda block: {"detected": block.gas},
),
("sensor", "smoke"): BlockAttributeDescription(
name="Smoke", device_class=BinarySensorDeviceClass.SMOKE
("sensor", "smoke"): BlockBinarySensorDescription(
key="sensor|smoke", name="Smoke", device_class=BinarySensorDeviceClass.SMOKE
),
("sensor", "vibration"): BlockAttributeDescription(
name="Vibration", device_class=BinarySensorDeviceClass.VIBRATION
("sensor", "vibration"): BlockBinarySensorDescription(
key="sensor|vibration",
name="Vibration",
device_class=BinarySensorDeviceClass.VIBRATION,
),
("input", "input"): BlockAttributeDescription(
("input", "input"): BlockBinarySensorDescription(
key="input|input",
name="Input",
device_class=BinarySensorDeviceClass.POWER,
default_enabled=False,
entity_registry_enabled_default=False,
removal_condition=is_block_momentary_input,
),
("relay", "input"): BlockAttributeDescription(
("relay", "input"): BlockBinarySensorDescription(
key="relay|input",
name="Input",
device_class=BinarySensorDeviceClass.POWER,
default_enabled=False,
entity_registry_enabled_default=False,
removal_condition=is_block_momentary_input,
),
("device", "input"): BlockAttributeDescription(
("device", "input"): BlockBinarySensorDescription(
key="device|input",
name="Input",
device_class=BinarySensorDeviceClass.POWER,
default_enabled=False,
entity_registry_enabled_default=False,
removal_condition=is_block_momentary_input,
),
("sensor", "extInput"): BlockAttributeDescription(
("sensor", "extInput"): BlockBinarySensorDescription(
key="sensor|extInput",
name="External Input",
device_class=BinarySensorDeviceClass.POWER,
default_enabled=False,
entity_registry_enabled_default=False,
),
("sensor", "motion"): BlockAttributeDescription(
name="Motion", device_class=BinarySensorDeviceClass.MOTION
("sensor", "motion"): BlockBinarySensorDescription(
key="sensor|motion", name="Motion", device_class=BinarySensorDeviceClass.MOTION
),
}
@ -171,6 +191,16 @@ RPC_SENSORS: Final = {
}
def _build_block_description(entry: RegistryEntry) -> BlockBinarySensorDescription:
"""Build description when restoring block attribute entities."""
return BlockBinarySensorDescription(
key="",
name="",
icon=entry.original_icon,
device_class=entry.original_device_class,
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
@ -189,10 +219,16 @@ async def async_setup_entry(
async_add_entities,
SENSORS,
BlockSleepingBinarySensor,
_build_block_description,
)
else:
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, BlockBinarySensor
hass,
config_entry,
async_add_entities,
SENSORS,
BlockBinarySensor,
_build_block_description,
)
await async_setup_entry_rest(
hass,
@ -206,6 +242,8 @@ async def async_setup_entry(
class BlockBinarySensor(ShellyBlockAttributeEntity, BinarySensorEntity):
"""Represent a block binary sensor entity."""
entity_description: BlockBinarySensorDescription
@property
def is_on(self) -> bool:
"""Return true if sensor state is on."""
@ -241,6 +279,8 @@ class RpcBinarySensor(ShellyRpcAttributeEntity, BinarySensorEntity):
class BlockSleepingBinarySensor(ShellySleepingBlockAttributeEntity, BinarySensorEntity):
"""Represent a block sleeping binary sensor."""
entity_description: BlockBinarySensorDescription
@property
def is_on(self) -> bool:
"""Return true if sensor state is on."""

View File

@ -10,7 +10,6 @@ from typing import Any, Final, cast
from aioshelly.block_device import Block
import async_timeout
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
@ -19,7 +18,7 @@ from homeassistant.helpers import (
entity_registry,
update_coordinator,
)
from homeassistant.helpers.entity import DeviceInfo, EntityCategory, EntityDescription
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import StateType
@ -53,8 +52,11 @@ async def async_setup_entry_attribute_entities(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
sensors: dict[tuple[str, str], BlockAttributeDescription],
sensors: Mapping[tuple[str, str], BlockEntityDescription],
sensor_class: Callable,
description_class: Callable[
[entity_registry.RegistryEntry], BlockEntityDescription
],
) -> None:
"""Set up entities for attributes."""
wrapper: BlockDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
@ -67,7 +69,13 @@ async def async_setup_entry_attribute_entities(
)
else:
await async_restore_block_attribute_entities(
hass, config_entry, async_add_entities, wrapper, sensors, sensor_class
hass,
config_entry,
async_add_entities,
wrapper,
sensors,
sensor_class,
description_class,
)
@ -75,7 +83,7 @@ async def async_setup_block_attribute_entities(
hass: HomeAssistant,
async_add_entities: AddEntitiesCallback,
wrapper: BlockDeviceWrapper,
sensors: dict[tuple[str, str], BlockAttributeDescription],
sensors: Mapping[tuple[str, str], BlockEntityDescription],
sensor_class: Callable,
) -> None:
"""Set up entities for block attributes."""
@ -119,8 +127,11 @@ async def async_restore_block_attribute_entities(
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
wrapper: BlockDeviceWrapper,
sensors: dict[tuple[str, str], BlockAttributeDescription],
sensors: Mapping[tuple[str, str], BlockEntityDescription],
sensor_class: Callable,
description_class: Callable[
[entity_registry.RegistryEntry], BlockEntityDescription
],
) -> None:
"""Restore block attributes entities."""
entities = []
@ -137,12 +148,7 @@ async def async_restore_block_attribute_entities(
continue
attribute = entry.unique_id.split("-")[-1]
description = BlockAttributeDescription(
name="",
icon=entry.original_icon,
unit=entry.unit_of_measurement,
device_class=entry.original_device_class,
)
description = description_class(entry)
entities.append(
sensor_class(wrapper, None, attribute, description, entry, sensors)
@ -232,22 +238,16 @@ async def async_setup_entry_rest(
@dataclass
class BlockAttributeDescription:
"""Class to describe a sensor."""
class BlockEntityDescription(EntityDescription):
"""Class to describe a BLOCK entity."""
name: str
# Callable = lambda attr_info: unit
icon: str | None = None
unit: None | str | Callable[[dict], str] = None
icon_fn: Callable[[dict], str] | None = None
unit_fn: Callable[[dict], str] | None = None
value: Callable[[Any], Any] = lambda val: val
device_class: str | None = None
state_class: str | None = None
default_enabled: bool = True
available: Callable[[Block], bool] | None = None
# Callable (settings, block), return true if entity should be removed
removal_condition: Callable[[dict, Block], bool] | None = None
extra_state_attributes: Callable[[Block], dict | None] | None = None
entity_category: EntityCategory | None = None
@dataclass
@ -283,35 +283,18 @@ class ShellyBlockEntity(entity.Entity):
"""Initialize Shelly entity."""
self.wrapper = wrapper
self.block = block
self._name = get_block_entity_name(wrapper.device, block)
@property
def name(self) -> str:
"""Name of entity."""
return self._name
@property
def should_poll(self) -> bool:
"""If device should be polled."""
return False
@property
def device_info(self) -> DeviceInfo:
"""Device info."""
return {
"connections": {(device_registry.CONNECTION_NETWORK_MAC, self.wrapper.mac)}
}
self._attr_name = get_block_entity_name(wrapper.device, block)
self._attr_should_poll = False
self._attr_device_info = DeviceInfo(
connections={(device_registry.CONNECTION_NETWORK_MAC, wrapper.mac)}
)
self._attr_unique_id = f"{wrapper.mac}-{block.description}"
@property
def available(self) -> bool:
"""Available."""
return self.wrapper.last_update_success
@property
def unique_id(self) -> str:
"""Return unique ID of entity."""
return f"{self.wrapper.mac}-{self.block.description}"
async def async_added_to_hass(self) -> None:
"""When entity is added to HASS."""
self.async_on_remove(self.wrapper.async_add_listener(self._update_callback))
@ -404,41 +387,22 @@ class ShellyRpcEntity(entity.Entity):
class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
"""Helper class to represent a block attribute."""
entity_description: BlockEntityDescription
def __init__(
self,
wrapper: BlockDeviceWrapper,
block: Block,
attribute: str,
description: BlockAttributeDescription,
description: BlockEntityDescription,
) -> None:
"""Initialize sensor."""
super().__init__(wrapper, block)
self.attribute = attribute
self.description = description
self.entity_description = description
unit = self.description.unit
if callable(unit):
unit = unit(block.info(attribute))
self._unit: None | str | Callable[[dict], str] = unit
self._unique_id: str = f"{super().unique_id}-{self.attribute}"
self._name = get_block_entity_name(wrapper.device, block, self.description.name)
@property
def unique_id(self) -> str:
"""Return unique ID of entity."""
return self._unique_id
@property
def name(self) -> str:
"""Name of sensor."""
return self._name
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if it should be enabled by default."""
return self.description.default_enabled
self._attr_unique_id: str = f"{super().unique_id}-{self.attribute}"
self._attr_name = get_block_entity_name(wrapper.device, block, description.name)
@property
def attribute_value(self) -> StateType:
@ -446,40 +410,25 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
if (value := getattr(self.block, self.attribute)) is None:
return None
return cast(StateType, self.description.value(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
return cast(StateType, self.entity_description.value(value))
@property
def available(self) -> bool:
"""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.block)
return self.entity_description.available(self.block)
@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.block)
@property
def entity_category(self) -> EntityCategory | None:
"""Return category of entity."""
return self.description.entity_category
return self.entity_description.extra_state_attributes(self.block)
class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
@ -601,9 +550,9 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
wrapper: BlockDeviceWrapper,
block: Block | None,
attribute: str,
description: BlockAttributeDescription,
description: BlockEntityDescription,
entry: entity_registry.RegistryEntry | None = None,
sensors: dict[tuple[str, str], BlockAttributeDescription] | None = None,
sensors: Mapping[tuple[str, str], BlockEntityDescription] | None = None,
) -> None:
"""Initialize the sleeping sensor."""
self.sensors = sensors
@ -611,20 +560,16 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
self.wrapper = wrapper
self.attribute = attribute
self.block: Block | None = block # type: ignore[assignment]
self.description = description
self._unit = self.description.unit
self.entity_description = description
if block is not None:
if callable(self._unit):
self._unit = self._unit(block.info(attribute))
self._unique_id = f"{self.wrapper.mac}-{block.description}-{attribute}"
self._name = get_block_entity_name(
self.wrapper.device, block, self.description.name
self._attr_unique_id = f"{self.wrapper.mac}-{block.description}-{attribute}"
self._attr_name = get_block_entity_name(
self.wrapper.device, block, self.entity_description.name
)
elif entry is not None:
self._unique_id = entry.unique_id
self._name = cast(str, entry.original_name)
self._attr_unique_id = entry.unique_id
self._attr_name = cast(str, entry.original_name)
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
@ -634,7 +579,6 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
if last_state is not None:
self.last_state = last_state.state
self.description.state_class = last_state.attributes.get(ATTR_STATE_CLASS)
@callback
def _update_callback(self) -> None:
@ -647,7 +591,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
super()._update_callback()
return
_, entity_block, entity_sensor = self.unique_id.split("-")
_, entity_block, entity_sensor = self._attr_unique_id.split("-")
assert self.wrapper.device.blocks
@ -664,7 +608,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
continue
self.block = block
self.description = description
self.entity_description = description
_LOGGER.debug("Entity %s attached to block", self.name)
super()._update_callback()

View File

@ -1,9 +1,12 @@
"""Sensor for Shelly."""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Final, cast
from aioshelly.block_device import Block
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
@ -26,11 +29,13 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_registry import RegistryEntry
from homeassistant.helpers.typing import StateType
from . import BlockDeviceWrapper
from .const import CONF_SLEEP_PERIOD, SHAIR_MAX_WORK_HOURS
from .entity import (
BlockAttributeDescription,
BlockEntityDescription,
RestEntityDescription,
RpcEntityDescription,
ShellyBlockAttributeEntity,
@ -44,6 +49,11 @@ from .entity import (
from .utils import get_device_entry_gen, get_device_uptime, temperature_unit
@dataclass
class BlockSensorDescription(BlockEntityDescription, SensorEntityDescription):
"""Class to describe a BLOCK sensor."""
@dataclass
class RpcSensorDescription(RpcEntityDescription, SensorEntityDescription):
"""Class to describe a RPC sensor."""
@ -55,170 +65,193 @@ class RestSensorDescription(RestEntityDescription, SensorEntityDescription):
SENSORS: Final = {
("device", "battery"): BlockAttributeDescription(
("device", "battery"): BlockSensorDescription(
key="device|battery",
name="Battery",
unit=PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
removal_condition=lambda settings, _: settings.get("external_power") == 1,
available=lambda block: cast(int, block.battery) != -1,
entity_category=EntityCategory.DIAGNOSTIC,
),
("device", "deviceTemp"): BlockAttributeDescription(
("device", "deviceTemp"): BlockSensorDescription(
key="device|deviceTemp",
name="Device Temperature",
unit=temperature_unit,
unit_fn=temperature_unit,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
default_enabled=False,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
("emeter", "current"): BlockAttributeDescription(
("emeter", "current"): BlockSensorDescription(
key="emeter|current",
name="Current",
unit=ELECTRIC_CURRENT_AMPERE,
native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
value=lambda value: value,
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
),
("light", "power"): BlockAttributeDescription(
("light", "power"): BlockSensorDescription(
key="light|power",
name="Power",
unit=POWER_WATT,
native_unit_of_measurement=POWER_WATT,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
default_enabled=False,
entity_registry_enabled_default=False,
),
("device", "power"): BlockAttributeDescription(
("device", "power"): BlockSensorDescription(
key="device|power",
name="Power",
unit=POWER_WATT,
native_unit_of_measurement=POWER_WATT,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
("emeter", "power"): BlockAttributeDescription(
("emeter", "power"): BlockSensorDescription(
key="emeter|power",
name="Power",
unit=POWER_WATT,
native_unit_of_measurement=POWER_WATT,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
("device", "voltage"): BlockAttributeDescription(
("device", "voltage"): BlockSensorDescription(
key="device|voltage",
name="Voltage",
unit=ELECTRIC_POTENTIAL_VOLT,
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
default_enabled=False,
entity_registry_enabled_default=False,
),
("emeter", "voltage"): BlockAttributeDescription(
("emeter", "voltage"): BlockSensorDescription(
key="emeter|voltage",
name="Voltage",
unit=ELECTRIC_POTENTIAL_VOLT,
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
("emeter", "powerFactor"): BlockAttributeDescription(
("emeter", "powerFactor"): BlockSensorDescription(
key="emeter|powerFactor",
name="Power Factor",
unit=PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value=lambda value: round(value * 100, 1),
device_class=SensorDeviceClass.POWER_FACTOR,
state_class=SensorStateClass.MEASUREMENT,
),
("relay", "power"): BlockAttributeDescription(
("relay", "power"): BlockSensorDescription(
key="relay|power",
name="Power",
unit=POWER_WATT,
native_unit_of_measurement=POWER_WATT,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
("roller", "rollerPower"): BlockAttributeDescription(
("roller", "rollerPower"): BlockSensorDescription(
key="roller|rollerPower",
name="Power",
unit=POWER_WATT,
native_unit_of_measurement=POWER_WATT,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
("device", "energy"): BlockAttributeDescription(
("device", "energy"): BlockSensorDescription(
key="device|energy",
name="Energy",
unit=ENERGY_KILO_WATT_HOUR,
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
value=lambda value: round(value / 60 / 1000, 2),
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
("emeter", "energy"): BlockAttributeDescription(
("emeter", "energy"): BlockSensorDescription(
key="emeter|energy",
name="Energy",
unit=ENERGY_KILO_WATT_HOUR,
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
value=lambda value: round(value / 1000, 2),
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
("emeter", "energyReturned"): BlockAttributeDescription(
("emeter", "energyReturned"): BlockSensorDescription(
key="emeter|energyReturned",
name="Energy Returned",
unit=ENERGY_KILO_WATT_HOUR,
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
value=lambda value: round(value / 1000, 2),
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
("light", "energy"): BlockAttributeDescription(
("light", "energy"): BlockSensorDescription(
key="light|energy",
name="Energy",
unit=ENERGY_KILO_WATT_HOUR,
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
value=lambda value: round(value / 60 / 1000, 2),
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
default_enabled=False,
entity_registry_enabled_default=False,
),
("relay", "energy"): BlockAttributeDescription(
("relay", "energy"): BlockSensorDescription(
key="relay|energy",
name="Energy",
unit=ENERGY_KILO_WATT_HOUR,
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
value=lambda value: round(value / 60 / 1000, 2),
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
("roller", "rollerEnergy"): BlockAttributeDescription(
("roller", "rollerEnergy"): BlockSensorDescription(
key="roller|rollerEnergy",
name="Energy",
unit=ENERGY_KILO_WATT_HOUR,
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
value=lambda value: round(value / 60 / 1000, 2),
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
("sensor", "concentration"): BlockAttributeDescription(
("sensor", "concentration"): BlockSensorDescription(
key="sensor|concentration",
name="Gas Concentration",
unit=CONCENTRATION_PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
icon="mdi:gauge",
state_class=SensorStateClass.MEASUREMENT,
),
("sensor", "extTemp"): BlockAttributeDescription(
("sensor", "extTemp"): BlockSensorDescription(
key="sensor|extTemp",
name="Temperature",
unit=temperature_unit,
unit_fn=temperature_unit,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
available=lambda block: cast(int, block.extTemp) != 999,
),
("sensor", "humidity"): BlockAttributeDescription(
("sensor", "humidity"): BlockSensorDescription(
key="sensor|humidity",
name="Humidity",
unit=PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
available=lambda block: cast(int, block.humidity) != 999,
),
("sensor", "luminosity"): BlockAttributeDescription(
("sensor", "luminosity"): BlockSensorDescription(
key="sensor|luminosity",
name="Luminosity",
unit=LIGHT_LUX,
native_unit_of_measurement=LIGHT_LUX,
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
available=lambda block: cast(int, block.luminosity) != -1,
),
("sensor", "tilt"): BlockAttributeDescription(
("sensor", "tilt"): BlockSensorDescription(
key="sensor|tilt",
name="Tilt",
unit=DEGREE,
native_unit_of_measurement=DEGREE,
icon="mdi:angle-acute",
state_class=SensorStateClass.MEASUREMENT,
),
("relay", "totalWorkTime"): BlockAttributeDescription(
("relay", "totalWorkTime"): BlockSensorDescription(
key="relay|totalWorkTime",
name="Lamp Life",
unit=PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
icon="mdi:progress-wrench",
value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1),
extra_state_attributes=lambda block: {
@ -226,14 +259,16 @@ SENSORS: Final = {
},
entity_category=EntityCategory.DIAGNOSTIC,
),
("adc", "adc"): BlockAttributeDescription(
("adc", "adc"): BlockSensorDescription(
key="adc|adc",
name="ADC",
unit=ELECTRIC_POTENTIAL_VOLT,
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
value=lambda value: round(value, 1),
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
("sensor", "sensorOp"): BlockAttributeDescription(
("sensor", "sensorOp"): BlockSensorDescription(
key="sensor|sensorOp",
name="Operation",
icon="mdi:cog-transfer",
value=lambda value: value,
@ -328,6 +363,17 @@ RPC_SENSORS: Final = {
}
def _build_block_description(entry: RegistryEntry) -> BlockSensorDescription:
"""Build description when restoring block attribute entities."""
return BlockSensorDescription(
key="",
name="",
icon=entry.original_icon,
native_unit_of_measurement=entry.unit_of_measurement,
device_class=entry.original_device_class,
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
@ -341,11 +387,21 @@ async def async_setup_entry(
if config_entry.data[CONF_SLEEP_PERIOD]:
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, BlockSleepingSensor
hass,
config_entry,
async_add_entities,
SENSORS,
BlockSleepingSensor,
_build_block_description,
)
else:
await async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, SENSORS, BlockSensor
hass,
config_entry,
async_add_entities,
SENSORS,
BlockSensor,
_build_block_description,
)
await async_setup_entry_rest(
hass, config_entry, async_add_entities, REST_SENSORS, RestSensor
@ -355,21 +411,27 @@ async def async_setup_entry(
class BlockSensor(ShellyBlockAttributeEntity, SensorEntity):
"""Represent a block sensor."""
entity_description: BlockSensorDescription
def __init__(
self,
wrapper: BlockDeviceWrapper,
block: Block,
attribute: str,
description: BlockSensorDescription,
) -> None:
"""Initialize sensor."""
super().__init__(wrapper, block, attribute, description)
self._attr_native_unit_of_measurement = description.native_unit_of_measurement
if unit_fn := description.unit_fn:
self._attr_native_unit_of_measurement = unit_fn(block.info(attribute))
@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 cast(str, self._unit)
class RestSensor(ShellyRestAttributeEntity, SensorEntity):
"""Represent a REST sensor."""
@ -396,6 +458,24 @@ class RpcSensor(ShellyRpcAttributeEntity, SensorEntity):
class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity):
"""Represent a block sleeping sensor."""
entity_description: BlockSensorDescription
def __init__(
self,
wrapper: BlockDeviceWrapper,
block: Block | None,
attribute: str,
description: BlockSensorDescription,
entry: RegistryEntry | None = None,
sensors: Mapping[tuple[str, str], BlockSensorDescription] | None = None,
) -> None:
"""Initialize the sleeping sensor."""
super().__init__(wrapper, block, attribute, description, entry, sensors)
self._attr_native_unit_of_measurement = description.native_unit_of_measurement
if block and (unit_fn := description.unit_fn):
self._attr_native_unit_of_measurement = unit_fn(block.info(attribute))
@property
def native_value(self) -> StateType:
"""Return value of sensor."""
@ -403,13 +483,3 @@ class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity):
return self.attribute_value
return self.last_state
@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 cast(str, self._unit)