From af7670a5a53c0e3dca25d606cd6856e1fd255b8d Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 5 Mar 2022 22:37:44 +0100 Subject: [PATCH] Add base entity for Sensibo (#67696) --- .coveragerc | 1 + homeassistant/components/sensibo/climate.py | 57 ++++----------- homeassistant/components/sensibo/entity.py | 77 +++++++++++++++++++++ homeassistant/components/sensibo/number.py | 39 ++--------- 4 files changed, 96 insertions(+), 78 deletions(-) create mode 100644 homeassistant/components/sensibo/entity.py diff --git a/.coveragerc b/.coveragerc index 4144b38cc0f..178c130ac13 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1015,6 +1015,7 @@ omit = homeassistant/components/sensibo/climate.py homeassistant/components/sensibo/coordinator.py homeassistant/components/sensibo/diagnostics.py + homeassistant/components/sensibo/entity.py homeassistant/components/sensibo/number.py homeassistant/components/serial/sensor.py homeassistant/components/serial_pm/sensor.py diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index eeb904cf6a8..cd383367dc4 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -1,11 +1,6 @@ """Support for Sensibo wifi-enabled home thermostats.""" from __future__ import annotations -import asyncio - -from aiohttp.client_exceptions import ClientConnectionError -import async_timeout -from pysensibo.exceptions import AuthenticationError, SensiboError import voluptuous as vol from homeassistant.components.climate import ( @@ -36,15 +31,13 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_platform -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.temperature import convert as convert_temperature -from .const import ALL, DOMAIN, LOGGER, TIMEOUT +from .const import ALL, DOMAIN, LOGGER from .coordinator import SensiboDataUpdateCoordinator +from .entity import SensiboBaseEntity SERVICE_ASSUME_STATE = "assume_state" @@ -126,17 +119,14 @@ async def async_setup_entry( ) -class SensiboClimate(CoordinatorEntity, ClimateEntity): +class SensiboClimate(SensiboBaseEntity, ClimateEntity): """Representation of a Sensibo device.""" - coordinator: SensiboDataUpdateCoordinator - def __init__( self, coordinator: SensiboDataUpdateCoordinator, device_id: str ) -> None: """Initiate SensiboClimate.""" - super().__init__(coordinator) - self._client = coordinator.client + super().__init__(coordinator, device_id) self._attr_unique_id = device_id self._attr_name = coordinator.data[device_id]["name"] self._attr_temperature_unit = ( @@ -146,17 +136,6 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): ) self._attr_supported_features = self.get_features() self._attr_precision = PRECISION_TENTHS - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, coordinator.data[device_id]["id"])}, - name=coordinator.data[device_id]["name"], - connections={(CONNECTION_NETWORK_MAC, coordinator.data[device_id]["mac"])}, - manufacturer="Sensibo", - configuration_url="https://home.sensibo.com/", - model=coordinator.data[device_id]["model"], - sw_version=coordinator.data[device_id]["fw_ver"], - hw_version=coordinator.data[device_id]["fw_type"], - suggested_area=coordinator.data[device_id]["name"], - ) def get_features(self) -> int: """Get supported features.""" @@ -309,26 +288,14 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): self, name: str, value: str | int | bool, assumed_state: bool = False ) -> None: """Set AC state.""" - result = {} - try: - async with async_timeout.timeout(TIMEOUT): - result = await self._client.async_set_ac_state_property( - self.unique_id, - name, - value, - self.coordinator.data[self.unique_id]["ac_states"], - assumed_state, - ) - except ( - ClientConnectionError, - asyncio.TimeoutError, - AuthenticationError, - SensiboError, - ) as err: - raise HomeAssistantError( - f"Failed to set AC state for device {self.name} to Sensibo servers: {err}" - ) from err - LOGGER.debug("Result: %s", result) + params = { + "name": name, + "value": value, + "ac_states": self.coordinator.data[self.unique_id]["ac_states"], + "assumed_state": assumed_state, + } + result = await self.async_send_command("set_ac_state", params) + if result["result"]["status"] == "Success": self.coordinator.data[self.unique_id][AC_STATE_TO_DATA[name]] = value self.async_write_ha_state() diff --git a/homeassistant/components/sensibo/entity.py b/homeassistant/components/sensibo/entity.py new file mode 100644 index 00000000000..22d29d37ead --- /dev/null +++ b/homeassistant/components/sensibo/entity.py @@ -0,0 +1,77 @@ +"""Base entity for Sensibo integration.""" +from __future__ import annotations + +from typing import Any + +import async_timeout + +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, LOGGER, SENSIBO_ERRORS, TIMEOUT +from .coordinator import SensiboDataUpdateCoordinator + + +class SensiboBaseEntity(CoordinatorEntity): + """Representation of a Sensibo numbers.""" + + coordinator: SensiboDataUpdateCoordinator + + def __init__( + self, + coordinator: SensiboDataUpdateCoordinator, + device_id: str, + ) -> None: + """Initiate Sensibo Number.""" + super().__init__(coordinator) + self._device_id = device_id + self._client = coordinator.client + device = coordinator.data[device_id] + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, device["id"])}, + name=device["name"], + connections={(CONNECTION_NETWORK_MAC, device["mac"])}, + manufacturer="Sensibo", + configuration_url="https://home.sensibo.com/", + model=device["model"], + sw_version=device["fw_ver"], + hw_version=device["fw_type"], + suggested_area=device["name"], + ) + + async def async_send_command( + self, command: str, params: dict[str, Any] + ) -> dict[str, Any]: + """Send command to Sensibo api.""" + try: + async with async_timeout.timeout(TIMEOUT): + result = await self.async_send_api_call(command, params) + except SENSIBO_ERRORS as err: + raise HomeAssistantError( + f"Failed to send command {command} for device {self.name} to Sensibo servers: {err}" + ) from err + + LOGGER.debug("Result: %s", result) + return result + + async def async_send_api_call( + self, command: str, params: dict[str, Any] + ) -> dict[str, Any]: + """Send api call.""" + result: dict[str, Any] = {"status": None} + if command == "set_calibration": + result = await self._client.async_set_calibration( + self._device_id, + params["data"], + ) + if command == "set_ac_state": + result = await self._client.async_set_ac_state_property( + self._device_id, + params["name"], + params["value"], + params["ac_states"], + params["assumed_state"], + ) + return result diff --git a/homeassistant/components/sensibo/number.py b/homeassistant/components/sensibo/number.py index 9e531249bf7..c775b8e6ffa 100644 --- a/homeassistant/components/sensibo/number.py +++ b/homeassistant/components/sensibo/number.py @@ -3,19 +3,16 @@ from __future__ import annotations from dataclasses import dataclass -import async_timeout - from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, LOGGER, SENSIBO_ERRORS, TIMEOUT +from .const import DOMAIN from .coordinator import SensiboDataUpdateCoordinator +from .entity import SensiboBaseEntity @dataclass @@ -73,10 +70,9 @@ async def async_setup_entry( ) -class SensiboNumber(CoordinatorEntity, NumberEntity): +class SensiboNumber(SensiboBaseEntity, NumberEntity): """Representation of a Sensibo numbers.""" - coordinator: SensiboDataUpdateCoordinator entity_description: SensiboNumberEntityDescription def __init__( @@ -86,25 +82,12 @@ class SensiboNumber(CoordinatorEntity, NumberEntity): entity_description: SensiboNumberEntityDescription, ) -> None: """Initiate Sensibo Number.""" - super().__init__(coordinator) + super().__init__(coordinator, device_id) self.entity_description = entity_description - self._device_id = device_id - self._client = coordinator.client self._attr_unique_id = f"{device_id}-{entity_description.key}" self._attr_name = ( f"{coordinator.data[device_id]['name']} {entity_description.name}" ) - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, coordinator.data[device_id]["id"])}, - name=coordinator.data[device_id]["name"], - connections={(CONNECTION_NETWORK_MAC, coordinator.data[device_id]["mac"])}, - manufacturer="Sensibo", - configuration_url="https://home.sensibo.com/", - model=coordinator.data[device_id]["model"], - sw_version=coordinator.data[device_id]["fw_ver"], - hw_version=coordinator.data[device_id]["fw_type"], - suggested_area=coordinator.data[device_id]["name"], - ) @property def value(self) -> float: @@ -114,17 +97,7 @@ class SensiboNumber(CoordinatorEntity, NumberEntity): async def async_set_value(self, value: float) -> None: """Set value for calibration.""" data = {self.entity_description.remote_key: value} - try: - async with async_timeout.timeout(TIMEOUT): - result = await self._client.async_set_calibration( - self._device_id, - data, - ) - except SENSIBO_ERRORS as err: - raise HomeAssistantError( - f"Failed to set calibration for device {self.name} to Sensibo servers: {err}" - ) from err - LOGGER.debug("Result: %s", result) + result = await self.async_send_command("set_calibration", {"data": data}) if result["status"] == "success": self.coordinator.data[self._device_id][self.entity_description.key] = value self.async_write_ha_state()