Use entity_description in shelly rest sensors (#64843)

* Use entity_description in shelly rest sensors

* Use _attr_device_info

* Adjust _attr_unique_id and _attr_device_info

Co-authored-by: epenet <epenet@users.noreply.github.com>
pull/64854/head
epenet 2022-01-24 18:43:43 +01:00 committed by GitHub
parent d1d33f0dc5
commit c8a63d4ffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 78 deletions

View File

@ -1,11 +1,13 @@
"""Binary sensor for Shelly.""" """Binary sensor for Shelly."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from typing import Final, cast from typing import Final, cast
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
BinarySensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_ON from homeassistant.const import STATE_ON
@ -16,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import CONF_SLEEP_PERIOD from .const import CONF_SLEEP_PERIOD
from .entity import ( from .entity import (
BlockAttributeDescription, BlockAttributeDescription,
RestAttributeDescription, RestEntityDescription,
RpcAttributeDescription, RpcAttributeDescription,
ShellyBlockAttributeEntity, ShellyBlockAttributeEntity,
ShellyRestAttributeEntity, ShellyRestAttributeEntity,
@ -32,6 +34,12 @@ from .utils import (
is_rpc_momentary_input, is_rpc_momentary_input,
) )
@dataclass
class RestBinarySensorDescription(RestEntityDescription, BinarySensorEntityDescription):
"""Class to describe a REST binary sensor."""
SENSORS: Final = { SENSORS: Final = {
("device", "overtemp"): BlockAttributeDescription( ("device", "overtemp"): BlockAttributeDescription(
name="Overheating", name="Overheating",
@ -102,18 +110,20 @@ SENSORS: Final = {
} }
REST_SENSORS: Final = { REST_SENSORS: Final = {
"cloud": RestAttributeDescription( "cloud": RestBinarySensorDescription(
key="cloud",
name="Cloud", name="Cloud",
value=lambda status, _: status["cloud"]["connected"], value=lambda status, _: status["cloud"]["connected"],
device_class=BinarySensorDeviceClass.CONNECTIVITY, device_class=BinarySensorDeviceClass.CONNECTIVITY,
default_enabled=False, entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
"fwupdate": RestAttributeDescription( "fwupdate": RestBinarySensorDescription(
key="fwupdate",
name="Firmware Update", name="Firmware Update",
device_class=BinarySensorDeviceClass.UPDATE, device_class=BinarySensorDeviceClass.UPDATE,
value=lambda status, _: status["update"]["has_update"], value=lambda status, _: status["update"]["has_update"],
default_enabled=False, entity_registry_enabled_default=False,
extra_state_attributes=lambda status: { extra_state_attributes=lambda status: {
"latest_stable_version": status["update"]["new_version"], "latest_stable_version": status["update"]["new_version"],
"installed_version": status["update"]["old_version"], "installed_version": status["update"]["old_version"],
@ -200,9 +210,13 @@ class BlockBinarySensor(ShellyBlockAttributeEntity, BinarySensorEntity):
class RestBinarySensor(ShellyRestAttributeEntity, BinarySensorEntity): class RestBinarySensor(ShellyRestAttributeEntity, BinarySensorEntity):
"""Represent a REST binary sensor entity.""" """Represent a REST binary sensor entity."""
entity_description: RestBinarySensorDescription
@property @property
def is_on(self) -> bool: def is_on(self) -> bool | None:
"""Return true if REST sensor state is on.""" """Return true if REST sensor state is on."""
if self.attribute_value is None:
return None
return bool(self.attribute_value) return bool(self.attribute_value)

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable from collections.abc import Callable, Mapping
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
from typing import Any, Final, cast from typing import Any, Final, cast
@ -19,7 +19,7 @@ from homeassistant.helpers import (
entity_registry, entity_registry,
update_coordinator, 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.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
@ -205,7 +205,7 @@ async def async_setup_entry_rest(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
sensors: dict[str, RestAttributeDescription], sensors: Mapping[str, RestEntityDescription],
sensor_class: Callable, sensor_class: Callable,
) -> None: ) -> None:
"""Set up entities for REST sensors.""" """Set up entities for REST sensors."""
@ -271,18 +271,11 @@ class RpcAttributeDescription:
@dataclass @dataclass
class RestAttributeDescription: class RestEntityDescription(EntityDescription):
"""Class to describe a REST sensor.""" """Class to describe a REST entity."""
name: str
icon: str | None = None
unit: str | None = None
value: Callable[[dict, Any], Any] | 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 extra_state_attributes: Callable[[dict], dict | None] | None = None
entity_category: EntityCategory | None = None
class ShellyBlockEntity(entity.Entity): class ShellyBlockEntity(entity.Entity):
@ -494,37 +487,26 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity): class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
"""Class to load info from REST.""" """Class to load info from REST."""
entity_description: RestEntityDescription
def __init__( def __init__(
self, self,
wrapper: BlockDeviceWrapper, wrapper: BlockDeviceWrapper,
attribute: str, attribute: str,
description: RestAttributeDescription, description: RestEntityDescription,
) -> None: ) -> None:
"""Initialize sensor.""" """Initialize sensor."""
super().__init__(wrapper) super().__init__(wrapper)
self.wrapper = wrapper self.wrapper = wrapper
self.attribute = attribute self.attribute = attribute
self.description = description self.entity_description = description
self._name = get_block_entity_name(wrapper.device, None, self.description.name) 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 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 @property
def available(self) -> bool: def available(self) -> bool:
"""Available.""" """Available."""
@ -533,39 +515,21 @@ class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
@property @property
def attribute_value(self) -> StateType: def attribute_value(self) -> StateType:
"""Value of sensor.""" """Value of sensor."""
if callable(self.description.value): if callable(self.entity_description.value):
self._last_value = self.description.value( self._last_value = self.entity_description.value(
self.wrapper.device.status, self._last_value self.wrapper.device.status, self._last_value
) )
return 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 @property
def extra_state_attributes(self) -> dict[str, Any] | None: def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes.""" """Return the state attributes."""
if self.description.extra_state_attributes is None: if self.entity_description.extra_state_attributes is None:
return None return None
return self.description.extra_state_attributes(self.wrapper.device.status) return self.entity_description.extra_state_attributes(
self.wrapper.device.status
@property )
def entity_category(self) -> EntityCategory | None:
"""Return category of entity."""
return self.description.entity_category
class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity): class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):

View File

@ -1,11 +1,13 @@
"""Sensor for Shelly.""" """Sensor for Shelly."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from typing import Final, cast from typing import Final, cast
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription,
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry 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 .const import CONF_SLEEP_PERIOD, SHAIR_MAX_WORK_HOURS
from .entity import ( from .entity import (
BlockAttributeDescription, BlockAttributeDescription,
RestAttributeDescription, RestEntityDescription,
RpcAttributeDescription, RpcAttributeDescription,
ShellyBlockAttributeEntity, ShellyBlockAttributeEntity,
ShellyRestAttributeEntity, ShellyRestAttributeEntity,
@ -41,6 +43,12 @@ from .entity import (
) )
from .utils import get_device_entry_gen, get_device_uptime, temperature_unit 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 = { SENSORS: Final = {
("device", "battery"): BlockAttributeDescription( ("device", "battery"): BlockAttributeDescription(
name="Battery", name="Battery",
@ -229,20 +237,22 @@ SENSORS: Final = {
} }
REST_SENSORS: Final = { REST_SENSORS: Final = {
"rssi": RestAttributeDescription( "rssi": RestSensorDescription(
key="rssi",
name="RSSI", name="RSSI",
unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
value=lambda status, _: status["wifi_sta"]["rssi"], value=lambda status, _: status["wifi_sta"]["rssi"],
device_class=SensorDeviceClass.SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
default_enabled=False, entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
"uptime": RestAttributeDescription( "uptime": RestSensorDescription(
key="uptime",
name="Uptime", name="Uptime",
value=lambda status, last: get_device_uptime(status["uptime"], last), value=lambda status, last: get_device_uptime(status["uptime"], last),
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
default_enabled=False, entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
} }
@ -359,21 +369,13 @@ class BlockSensor(ShellyBlockAttributeEntity, SensorEntity):
class RestSensor(ShellyRestAttributeEntity, SensorEntity): class RestSensor(ShellyRestAttributeEntity, SensorEntity):
"""Represent a REST sensor.""" """Represent a REST sensor."""
entity_description: RestSensorDescription
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType:
"""Return value of sensor.""" """Return value of sensor."""
return self.attribute_value 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): class RpcSensor(ShellyRpcAttributeEntity, SensorEntity):
"""Represent a RPC sensor.""" """Represent a RPC sensor."""