From 17c41f47835d667c630cde85c4069e810160bf76 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 31 Jan 2022 22:14:59 +0100 Subject: [PATCH] Introduce number platform for Shelly (#64207) * Introduce number platform for Shelly * coverage * Rework based on review comment * Improve logic around channel * Remove unused value * rebase * Removed redundant properties * Update homeassistant/components/shelly/number.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Remove channel workaround as currently not needed Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- .coveragerc | 1 + homeassistant/components/shelly/__init__.py | 1 + homeassistant/components/shelly/number.py | 132 ++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 homeassistant/components/shelly/number.py diff --git a/.coveragerc b/.coveragerc index 0ae80a22a47..7cca9c005ce 100644 --- a/.coveragerc +++ b/.coveragerc @@ -985,6 +985,7 @@ omit = homeassistant/components/shelly/climate.py homeassistant/components/shelly/entity.py homeassistant/components/shelly/light.py + homeassistant/components/shelly/number.py homeassistant/components/shelly/sensor.py homeassistant/components/shelly/utils.py homeassistant/components/sht31/sensor.py diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 3ab87ad9d0e..d60a8aabb1a 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -80,6 +80,7 @@ BLOCK_PLATFORMS: Final = [ BLOCK_SLEEPING_PLATFORMS: Final = [ Platform.BINARY_SENSOR, Platform.CLIMATE, + Platform.NUMBER, Platform.SENSOR, ] RPC_PLATFORMS: Final = [ diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py new file mode 100644 index 00000000000..27773c629c0 --- /dev/null +++ b/homeassistant/components/shelly/number.py @@ -0,0 +1,132 @@ +"""Number for Shelly.""" +from __future__ import annotations + +import asyncio +from dataclasses import dataclass +import logging +from typing import Any, Final, cast + +import async_timeout + +from homeassistant.components.number import ( + NumberEntity, + NumberEntityDescription, + NumberMode, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE +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 AIOSHELLY_DEVICE_TIMEOUT_SEC, CONF_SLEEP_PERIOD +from .entity import ( + BlockEntityDescription, + ShellySleepingBlockAttributeEntity, + async_setup_entry_attribute_entities, +) +from .utils import get_device_entry_gen + +_LOGGER: Final = logging.getLogger(__name__) + + +@dataclass +class BlockNumberDescription(BlockEntityDescription, NumberEntityDescription): + """Class to describe a BLOCK sensor.""" + + mode: NumberMode = NumberMode("slider") + rest_path: str = "" + rest_arg: str = "" + + +NUMBERS: Final = { + ("device", "valvePos"): BlockNumberDescription( + key="device|valvepos", + icon="mdi:pipe-valve", + name="Valve Position", + unit_of_measurement=PERCENTAGE, + available=lambda block: cast(int, block.valveError) != 1, + entity_category=EntityCategory.CONFIG, + min_value=0, + max_value=100, + step=1, + mode=NumberMode("slider"), + rest_path="thermostat/0", + rest_arg="pos", + ), +} + + +def _build_block_description(entry: RegistryEntry) -> BlockNumberDescription: + """Build description when restoring block attribute entities.""" + assert entry.capabilities + return BlockNumberDescription( + key="", + name="", + icon=entry.original_icon, + unit_of_measurement=entry.unit_of_measurement, + device_class=entry.original_device_class, + min_value=cast(float, entry.capabilities.get("min")), + max_value=cast(float, entry.capabilities.get("max")), + step=cast(float, entry.capabilities.get("step")), + mode=cast(NumberMode, entry.capabilities.get("mode")), + ) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up numbers for device.""" + if get_device_entry_gen(config_entry) == 2: + return + + if config_entry.data[CONF_SLEEP_PERIOD]: + await async_setup_entry_attribute_entities( + hass, + config_entry, + async_add_entities, + NUMBERS, + BlockSleepingNumber, + _build_block_description, + ) + + +class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, NumberEntity): + """Represent a block sleeping number.""" + + entity_description: BlockNumberDescription + + @property + def value(self) -> float: + """Return value of number.""" + if self.block is not None: + return cast(float, self.attribute_value) + + return cast(float, self.last_state) + + async def async_set_value(self, value: float) -> None: + """Set value.""" + # Example for Shelly Valve: http://192.168.188.187/thermostat/0?pos=13.0 + await self._set_state_full_path( + self.entity_description.rest_path, + {self.entity_description.rest_arg: value}, + ) + self.async_write_ha_state() + + async def _set_state_full_path(self, path: str, params: Any) -> Any: + """Set block state (HTTP request).""" + + _LOGGER.debug("Setting state for entity %s, state: %s", self.name, params) + try: + async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC): + return await self.wrapper.device.http_request("get", path, params) + except (asyncio.TimeoutError, OSError) as err: + _LOGGER.error( + "Setting state for entity %s failed, state: %s, error: %s", + self.name, + params, + repr(err), + )