core/homeassistant/components/shelly/entity.py

317 lines
8.7 KiB
Python
Raw Normal View History

"""Shelly entity helper."""
from dataclasses import dataclass
from typing import Any, Callable, Optional, Union
import aioshelly
from homeassistant.core import callback
from homeassistant.helpers import device_registry, entity, update_coordinator
from . import ShellyDeviceRestWrapper, ShellyDeviceWrapper
from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN, REST
from .utils import get_entity_name, get_rest_value_from_path
async def async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, sensors, sensor_class
):
"""Set up entities for block attributes."""
wrapper: ShellyDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
config_entry.entry_id
][COAP]
blocks = []
for block in wrapper.device.blocks:
for sensor_id in block.sensor_ids:
description = sensors.get((block.type, sensor_id))
if description is None:
continue
# Filter out non-existing sensors and sensors without a value
if getattr(block, sensor_id, None) in (-1, None):
continue
blocks.append((block, sensor_id, description))
if not blocks:
return
async_add_entities(
[
sensor_class(wrapper, block, sensor_id, description)
for block, sensor_id, description in blocks
]
)
async def async_setup_entry_rest(
hass, config_entry, async_add_entities, sensors, sensor_class
):
"""Set up entities for REST sensors."""
wrapper: ShellyDeviceRestWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
config_entry.entry_id
][REST]
entities = []
for sensor_id in sensors:
_desc = sensors.get(sensor_id)
if not wrapper.device.settings.get("sleep_mode"):
entities.append(_desc)
if not entities:
return
async_add_entities([sensor_class(wrapper, description) for description in entities])
@dataclass
class BlockAttributeDescription:
"""Class to describe a sensor."""
name: str
# Callable = lambda attr_info: unit
icon: Optional[str] = None
unit: Union[None, str, Callable[[dict], str]] = None
value: Callable[[Any], Any] = lambda val: val
device_class: Optional[str] = None
default_enabled: bool = True
available: Optional[Callable[[aioshelly.Block], bool]] = None
device_state_attributes: Optional[
Callable[[aioshelly.Block], Optional[dict]]
] = None
@dataclass
class RestAttributeDescription:
"""Class to describe a REST sensor."""
path: str
name: str
# Callable = lambda attr_info: unit
icon: Optional[str] = None
unit: Union[None, str, Callable[[dict], str]] = None
value: Callable[[Any], Any] = lambda val: val
device_class: Optional[str] = None
default_enabled: bool = True
attributes: Optional[dict] = None
class ShellyBlockEntity(entity.Entity):
"""Helper class to represent a block."""
def __init__(self, wrapper: ShellyDeviceWrapper, block):
"""Initialize Shelly entity."""
self.wrapper = wrapper
self.block = block
self._name = get_entity_name(wrapper, block)
@property
def name(self):
"""Name of entity."""
return self._name
@property
def should_poll(self):
"""If device should be polled."""
return False
@property
def device_info(self):
"""Device info."""
return {
"connections": {(device_registry.CONNECTION_NETWORK_MAC, self.wrapper.mac)}
}
@property
def available(self):
"""Available."""
return self.wrapper.last_update_success
@property
def unique_id(self):
"""Return unique ID of entity."""
return f"{self.wrapper.mac}-{self.block.description}"
async def async_added_to_hass(self):
"""When entity is added to HASS."""
self.async_on_remove(self.wrapper.async_add_listener(self._update_callback))
async def async_update(self):
"""Update entity with latest info."""
await self.wrapper.async_request_refresh()
@callback
def _update_callback(self):
"""Handle device update."""
self.async_write_ha_state()
class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
"""Switch that controls a relay block on Shelly devices."""
def __init__(
self,
wrapper: ShellyDeviceWrapper,
block: aioshelly.Block,
attribute: str,
description: BlockAttributeDescription,
) -> None:
"""Initialize sensor."""
super().__init__(wrapper, block)
self.attribute = attribute
self.description = description
self.info = block.info(attribute)
unit = self.description.unit
if callable(unit):
unit = unit(self.info)
self._unit = unit
self._unique_id = f"{super().unique_id}-{self.attribute}"
self._name = get_entity_name(wrapper, block, self.description.name)
@property
def unique_id(self):
"""Return unique ID of entity."""
return self._unique_id
@property
def name(self):
"""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
@property
def attribute_value(self):
"""Value of sensor."""
value = getattr(self.block, self.attribute)
if value is None:
return None
return self.description.value(value)
@property
def unit_of_measurement(self):
"""Return unit of sensor."""
return self._unit
@property
def device_class(self):
"""Device class of sensor."""
return self.description.device_class
@property
def icon(self):
"""Icon of sensor."""
return self.description.icon
@property
def available(self):
"""Available."""
available = super().available
if not available or not self.description.available:
return available
return self.description.available(self.block)
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self.description.device_state_attributes is None:
return None
return self.description.device_state_attributes(self.block)
class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity):
"""Class to load info from REST."""
def __init__(
self, wrapper: ShellyDeviceWrapper, description: RestAttributeDescription
) -> None:
"""Initialize sensor."""
super().__init__(wrapper)
self.wrapper = wrapper
self.description = description
self._unit = self.description.unit
self._name = get_entity_name(wrapper, None, self.description.name)
self.path = self.description.path
self._attributes = self.description.attributes
@property
def name(self):
"""Name of sensor."""
return self._name
@property
def device_info(self):
"""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):
"""Available."""
return self.wrapper.last_update_success
@property
def attribute_value(self):
"""Attribute."""
return get_rest_value_from_path(
self.wrapper.device.status, self.description.device_class, self.path
)
@property
def unit_of_measurement(self):
"""Return unit of sensor."""
return self.description.unit
@property
def device_class(self):
"""Device class of sensor."""
return self.description.device_class
@property
def icon(self):
"""Icon of sensor."""
return self.description.icon
@property
def unique_id(self):
"""Return unique ID of entity."""
return f"{self.wrapper.mac}-{self.description.path}"
@property
def device_state_attributes(self) -> dict:
"""Return the state attributes."""
if self._attributes is None:
return None
attributes = dict()
for attrib in self._attributes:
description = attrib.get("description")
attribute_value = get_rest_value_from_path(
self.wrapper.device.status,
self.description.device_class,
attrib.get("path"),
)
attributes[description] = attribute_value
return attributes