Add Shelly support for REST sensors (#40429)
This commit is contained in:
parent
403514ccb3
commit
d8b067ebf9
9 changed files with 311 additions and 16 deletions
|
@ -4,12 +4,60 @@ from typing import Any, Callable, Optional, Union
|
|||
|
||||
import aioshelly
|
||||
|
||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import device_registry, entity
|
||||
from homeassistant.helpers import device_registry, entity, update_coordinator
|
||||
|
||||
from . import ShellyDeviceWrapper
|
||||
from .const import DATA_CONFIG_ENTRY, DOMAIN
|
||||
from .utils import get_entity_name
|
||||
from . import ShellyDeviceRestWrapper, ShellyDeviceWrapper
|
||||
from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN, REST
|
||||
from .utils import get_entity_name, get_rest_value_from_path
|
||||
|
||||
|
||||
def temperature_unit(block_info: dict) -> str:
|
||||
"""Detect temperature unit."""
|
||||
if block_info[aioshelly.BLOCK_VALUE_UNIT] == "F":
|
||||
return TEMP_FAHRENHEIT
|
||||
return TEMP_CELSIUS
|
||||
|
||||
|
||||
def shelly_naming(self, block, entity_type: str):
|
||||
"""Naming for switch and sensors."""
|
||||
|
||||
entity_name = self.wrapper.name
|
||||
if not block:
|
||||
return f"{entity_name} {self.description.name}"
|
||||
|
||||
channels = 0
|
||||
mode = block.type + "s"
|
||||
if "num_outputs" in self.wrapper.device.shelly:
|
||||
channels = self.wrapper.device.shelly["num_outputs"]
|
||||
if (
|
||||
self.wrapper.model in ["SHSW-21", "SHSW-25"]
|
||||
and self.wrapper.device.settings["mode"] == "roller"
|
||||
):
|
||||
channels = 1
|
||||
if block.type == "emeter" and "num_emeters" in self.wrapper.device.shelly:
|
||||
channels = self.wrapper.device.shelly["num_emeters"]
|
||||
if channels > 1 and block.type != "device":
|
||||
# Shelly EM (SHEM) with firmware v1.8.1 doesn't have "name" key; will be fixed in next firmware release
|
||||
if "name" in self.wrapper.device.settings[mode][int(block.channel)]:
|
||||
entity_name = self.wrapper.device.settings[mode][int(block.channel)]["name"]
|
||||
else:
|
||||
entity_name = None
|
||||
if not entity_name:
|
||||
if self.wrapper.model == "SHEM-3":
|
||||
base = ord("A")
|
||||
else:
|
||||
base = ord("1")
|
||||
entity_name = f"{self.wrapper.name} channel {chr(int(block.channel)+base)}"
|
||||
|
||||
if entity_type == "switch":
|
||||
return entity_name
|
||||
|
||||
if entity_type == "sensor":
|
||||
return f"{entity_name} {self.description.name}"
|
||||
|
||||
raise ValueError
|
||||
|
||||
|
||||
async def async_setup_entry_attribute_entities(
|
||||
|
@ -18,7 +66,7 @@ async def async_setup_entry_attribute_entities(
|
|||
"""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:
|
||||
|
@ -44,6 +92,27 @@ async def async_setup_entry_attribute_entities(
|
|||
)
|
||||
|
||||
|
||||
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."""
|
||||
|
@ -60,6 +129,21 @@ class BlockAttributeDescription:
|
|||
] = 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."""
|
||||
|
||||
|
@ -133,7 +217,7 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
|||
|
||||
self._unit = unit
|
||||
self._unique_id = f"{super().unique_id}-{self.attribute}"
|
||||
self._name = get_entity_name(wrapper, block, self.description.name)
|
||||
self._name = shelly_naming(self, block, "sensor")
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
|
@ -187,3 +271,85 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity):
|
|||
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 = shelly_naming(self, None, "sensor")
|
||||
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):
|
||||
"""Return the state attributes."""
|
||||
|
||||
if self._attributes is None:
|
||||
return None
|
||||
|
||||
_description = self._attributes.get("description")
|
||||
_attribute_value = get_rest_value_from_path(
|
||||
self.wrapper.device.status,
|
||||
self.description.device_class,
|
||||
self._attributes.get("path"),
|
||||
)
|
||||
|
||||
return {_description: _attribute_value}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue