diff --git a/homeassistant/components/sensibo/__init__.py b/homeassistant/components/sensibo/__init__.py index ee3cba7d001..29730216899 100644 --- a/homeassistant/components/sensibo/__init__.py +++ b/homeassistant/components/sensibo/__init__.py @@ -1,4 +1,4 @@ -"""The sensibo component.""" +"""The Sensibo component.""" from __future__ import annotations from pysensibo.exceptions import AuthenticationError diff --git a/homeassistant/components/sensibo/binary_sensor.py b/homeassistant/components/sensibo/binary_sensor.py index 3a61570701d..88800a9b2a8 100644 --- a/homeassistant/components/sensibo/binary_sensor.py +++ b/homeassistant/components/sensibo/binary_sensor.py @@ -65,7 +65,6 @@ MOTION_SENSOR_TYPES: tuple[SensiboMotionBinarySensorEntityDescription, ...] = ( device_class=BinarySensorDeviceClass.CONNECTIVITY, entity_category=EntityCategory.DIAGNOSTIC, name="Alive", - icon="mdi:wifi", value_fn=lambda data: data.alive, ), SensiboMotionBinarySensorEntityDescription( @@ -104,7 +103,6 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, device_class=BinarySensorDeviceClass.CONNECTIVITY, name="Pure Boost linked with AC", - icon="mdi:connection", value_fn=lambda data: data.pure_ac_integration, ), SensiboDeviceBinarySensorEntityDescription( @@ -112,7 +110,6 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, device_class=BinarySensorDeviceClass.CONNECTIVITY, name="Pure Boost linked with presence", - icon="mdi:connection", value_fn=lambda data: data.pure_geo_integration, ), SensiboDeviceBinarySensorEntityDescription( @@ -120,7 +117,6 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, device_class=BinarySensorDeviceClass.CONNECTIVITY, name="Pure Boost linked with indoor air quality", - icon="mdi:connection", value_fn=lambda data: data.pure_measure_integration, ), SensiboDeviceBinarySensorEntityDescription( @@ -128,12 +124,13 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, device_class=BinarySensorDeviceClass.CONNECTIVITY, name="Pure Boost linked with outdoor air quality", - icon="mdi:connection", value_fn=lambda data: data.pure_prime_integration, ), FILTER_CLEAN_REQUIRED_DESCRIPTION, ) +DESCRIPTION_BY_MODELS = {"pure": PURE_SENSOR_TYPES} + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -161,15 +158,10 @@ async def async_setup_entry( ) entities.extend( SensiboDeviceSensor(coordinator, device_id, description) - for description in PURE_SENSOR_TYPES for device_id, device_data in coordinator.data.parsed.items() - if device_data.model == "pure" - ) - entities.extend( - SensiboDeviceSensor(coordinator, device_id, description) - for description in DEVICE_SENSOR_TYPES - for device_id, device_data in coordinator.data.parsed.items() - if device_data.model != "pure" + for description in DESCRIPTION_BY_MODELS.get( + device_data.model, DEVICE_SENSOR_TYPES + ) ) async_add_entities(entities) diff --git a/homeassistant/components/sensibo/button.py b/homeassistant/components/sensibo/button.py index ad8a525aebb..b91bddaf882 100644 --- a/homeassistant/components/sensibo/button.py +++ b/homeassistant/components/sensibo/button.py @@ -1,24 +1,44 @@ """Button platform for Sensibo integration.""" from __future__ import annotations +from dataclasses import dataclass +from typing import Any + +from pysensibo.model import SensiboDevice + from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import SensiboDataUpdateCoordinator -from .entity import SensiboDeviceBaseEntity +from .entity import SensiboDeviceBaseEntity, async_handle_api_call PARALLEL_UPDATES = 0 -DEVICE_BUTTON_TYPES: ButtonEntityDescription = ButtonEntityDescription( + +@dataclass +class SensiboEntityDescriptionMixin: + """Mixin values for Sensibo entities.""" + + data_key: str + + +@dataclass +class SensiboButtonEntityDescription( + ButtonEntityDescription, SensiboEntityDescriptionMixin +): + """Class describing Sensibo Button entities.""" + + +DEVICE_BUTTON_TYPES = SensiboButtonEntityDescription( key="reset_filter", name="Reset filter", icon="mdi:air-filter", entity_category=EntityCategory.CONFIG, + data_key="filter_clean", ) @@ -29,26 +49,22 @@ async def async_setup_entry( coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - entities: list[SensiboDeviceButton] = [] - - entities.extend( + async_add_entities( SensiboDeviceButton(coordinator, device_id, DEVICE_BUTTON_TYPES) for device_id, device_data in coordinator.data.parsed.items() ) - async_add_entities(entities) - class SensiboDeviceButton(SensiboDeviceBaseEntity, ButtonEntity): """Representation of a Sensibo Device Binary Sensor.""" - entity_description: ButtonEntityDescription + entity_description: SensiboButtonEntityDescription def __init__( self, coordinator: SensiboDataUpdateCoordinator, device_id: str, - entity_description: ButtonEntityDescription, + entity_description: SensiboButtonEntityDescription, ) -> None: """Initiate Sensibo Device Button.""" super().__init__( @@ -60,8 +76,18 @@ class SensiboDeviceButton(SensiboDeviceBaseEntity, ButtonEntity): async def async_press(self) -> None: """Press the button.""" - result = await self.async_send_command("reset_filter") - if result["status"] == "success": - await self.coordinator.async_request_refresh() - return - raise HomeAssistantError(f"Could not set calibration for device {self.name}") + await self.async_send_api_call( + device_data=self.device_data, + key=self.entity_description.data_key, + value=False, + ) + + @async_handle_api_call + async def async_send_api_call( + self, device_data: SensiboDevice, key: Any, value: Any + ) -> bool: + """Make service call to api.""" + result = await self._client.async_reset_filter( + self._device_id, + ) + return bool(result.get("status") == "success") diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 25ac73bfd4f..eda60458d4f 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -4,6 +4,7 @@ from __future__ import annotations from bisect import bisect_left from typing import TYPE_CHECKING, Any +from pysensibo.model import SensiboDevice import voluptuous as vol from homeassistant.components.climate import ClimateEntity @@ -24,7 +25,7 @@ from homeassistant.util.temperature import convert as convert_temperature from .const import DOMAIN from .coordinator import SensiboDataUpdateCoordinator -from .entity import SensiboDeviceBaseEntity +from .entity import SensiboDeviceBaseEntity, async_handle_api_call SERVICE_ASSUME_STATE = "assume_state" SERVICE_ENABLE_TIMER = "enable_timer" @@ -123,7 +124,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): def __init__( self, coordinator: SensiboDataUpdateCoordinator, device_id: str ) -> None: - """Initiate SensiboClimate.""" + """Initiate Sensibo Climate.""" super().__init__(coordinator, device_id) self._attr_unique_id = device_id self._attr_temperature_unit = ( @@ -173,6 +174,11 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): ) return None + @property + def temperature_unit(self) -> str: + """Return temperature unit.""" + return TEMP_CELSIUS if self.device_data.temp_unit == "C" else TEMP_FAHRENHEIT + @property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" @@ -242,69 +248,99 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): return new_temp = _find_valid_target_temp(temperature, self.device_data.temp_list) - await self._async_set_ac_state_property("targetTemperature", new_temp) + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["targetTemperature"], + value=new_temp, + name="targetTemperature", + assumed_state=False, + ) async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" if "fanLevel" not in self.device_data.active_features: raise HomeAssistantError("Current mode doesn't support setting Fanlevel") - await self._async_set_ac_state_property("fanLevel", fan_mode) + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["fanLevel"], + value=fan_mode, + name="fanLevel", + assumed_state=False, + ) async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" if hvac_mode == HVACMode.OFF: - await self._async_set_ac_state_property("on", False) + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["on"], + value=False, + name="on", + assumed_state=False, + ) return # Turn on if not currently on. if not self.device_data.device_on: - await self._async_set_ac_state_property("on", True) + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["on"], + value=True, + name="on", + assumed_state=False, + ) - await self._async_set_ac_state_property("mode", HA_TO_SENSIBO[hvac_mode]) - await self.coordinator.async_request_refresh() + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["mode"], + value=HA_TO_SENSIBO[hvac_mode], + name="mode", + assumed_state=False, + ) async def async_set_swing_mode(self, swing_mode: str) -> None: """Set new target swing operation.""" if "swing" not in self.device_data.active_features: raise HomeAssistantError("Current mode doesn't support setting Swing") - await self._async_set_ac_state_property("swing", swing_mode) + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["swing"], + value=swing_mode, + name="swing", + assumed_state=False, + ) async def async_turn_on(self) -> None: """Turn Sensibo unit on.""" - await self._async_set_ac_state_property("on", True) + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["on"], + value=True, + name="on", + assumed_state=False, + ) async def async_turn_off(self) -> None: """Turn Sensibo unit on.""" - await self._async_set_ac_state_property("on", False) - - async def _async_set_ac_state_property( - self, name: str, value: str | int | bool, assumed_state: bool = False - ) -> None: - """Set AC state.""" - params = { - "name": name, - "value": value, - "ac_states": self.device_data.ac_states, - "assumed_state": assumed_state, - } - result = await self.async_send_command("set_ac_state", params) - - if result["result"]["status"] == "Success": - setattr(self.device_data, AC_STATE_TO_DATA[name], value) - self.async_write_ha_state() - return - - failure = result["result"]["failureReason"] - raise HomeAssistantError( - f"Could not set state for device {self.name} due to reason {failure}" + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["on"], + value=False, + name="on", + assumed_state=False, ) async def async_assume_state(self, state: str) -> None: """Sync state with api.""" - await self._async_set_ac_state_property("on", state != HVACMode.OFF, True) - await self.coordinator.async_refresh() + await self.async_send_api_call( + device_data=self.device_data, + key=AC_STATE_TO_DATA["on"], + value=state != HVACMode.OFF, + name="on", + assumed_state=True, + ) async def async_enable_timer(self, minutes: int) -> None: """Enable the timer.""" @@ -313,11 +349,13 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): "minutesFromNow": minutes, "acState": {**self.device_data.ac_states, "on": new_state}, } - result = await self.async_send_command("set_timer", params) - - if result["status"] == "success": - return await self.coordinator.async_request_refresh() - raise HomeAssistantError(f"Could not enable timer for device {self.name}") + await self.api_call_custom_service_timer( + device_data=self.device_data, + key="timer_on", + value=True, + command="set_timer", + data=params, + ) async def async_enable_pure_boost( self, @@ -343,5 +381,57 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): if outdoor_integration is not None: params["primeIntegration"] = outdoor_integration - await self.async_send_command("set_pure_boost", params) - await self.coordinator.async_refresh() + await self.api_call_custom_service_pure_boost( + device_data=self.device_data, + key="pure_boost_enabled", + value=True, + command="set_pure_boost", + data=params, + ) + + @async_handle_api_call + async def async_send_api_call( + self, + device_data: SensiboDevice, + key: Any, + value: Any, + name: str, + assumed_state: bool = False, + ) -> bool: + """Make service call to api.""" + result = await self._client.async_set_ac_state_property( + self._device_id, + name, + value, + self.device_data.ac_states, + assumed_state, + ) + return bool(result.get("result", {}).get("status") == "Success") + + @async_handle_api_call + async def api_call_custom_service_timer( + self, + device_data: SensiboDevice, + key: Any, + value: Any, + command: str, + data: dict, + ) -> bool: + """Make service call to api.""" + result = {} + result = await self._client.async_set_timer(self._device_id, data) + return bool(result.get("status") == "success") + + @async_handle_api_call + async def api_call_custom_service_pure_boost( + self, + device_data: SensiboDevice, + key: Any, + value: Any, + command: str, + data: dict, + ) -> bool: + """Make service call to api.""" + result = {} + result = await self._client.async_set_pureboost(self._device_id, data) + return bool(result.get("status") == "success") diff --git a/homeassistant/components/sensibo/config_flow.py b/homeassistant/components/sensibo/config_flow.py index c7aaa30b3db..f3b413071e8 100644 --- a/homeassistant/components/sensibo/config_flow.py +++ b/homeassistant/components/sensibo/config_flow.py @@ -10,14 +10,14 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_API_KEY from homeassistant.data_entry_flow import FlowResult -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.selector import TextSelector from .const import DEFAULT_NAME, DOMAIN from .util import NoDevicesError, NoUsernameError, async_validate_api DATA_SCHEMA = vol.Schema( { - vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_API_KEY): TextSelector(), } ) diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index a0321bf611e..6c5a993e34a 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -12,10 +12,13 @@ from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, TIMEOUT +REQUEST_REFRESH_DELAY = 0.35 + class SensiboDataUpdateCoordinator(DataUpdateCoordinator): """A Sensibo Data Update Coordinator.""" @@ -34,6 +37,11 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): LOGGER, name=DOMAIN, update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL), + # We don't want an immediate refresh since the device + # takes a moment to reflect the state change + request_refresh_debouncer=Debouncer( + hass, LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False + ), ) async def _async_update_data(self) -> SensiboData: diff --git a/homeassistant/components/sensibo/diagnostics.py b/homeassistant/components/sensibo/diagnostics.py index e4a4672bf64..72029acc2f1 100644 --- a/homeassistant/components/sensibo/diagnostics.py +++ b/homeassistant/components/sensibo/diagnostics.py @@ -31,6 +31,6 @@ TO_REDACT = { async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any]: - """Return diagnostics for a config entry.""" + """Return diagnostics for Sensibo config entry.""" coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] return async_redact_data(coordinator.data.raw, TO_REDACT) diff --git a/homeassistant/components/sensibo/entity.py b/homeassistant/components/sensibo/entity.py index 41d8b8b5070..56a7c820739 100644 --- a/homeassistant/components/sensibo/entity.py +++ b/homeassistant/components/sensibo/entity.py @@ -1,10 +1,12 @@ """Base entity for Sensibo integration.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any +from collections.abc import Callable, Coroutine +from typing import TYPE_CHECKING, Any, TypeVar import async_timeout from pysensibo.model import MotionSensor, SensiboDevice +from typing_extensions import Concatenate, ParamSpec from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC @@ -14,9 +16,39 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, LOGGER, SENSIBO_ERRORS, TIMEOUT from .coordinator import SensiboDataUpdateCoordinator +_T = TypeVar("_T", bound="SensiboDeviceBaseEntity") +_P = ParamSpec("_P") + + +def async_handle_api_call( + function: Callable[Concatenate[_T, _P], Coroutine[Any, Any, Any]] +) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, Any]]: + """Decorate api calls.""" + + async def wrap_api_call(*args: Any, **kwargs: Any) -> None: + """Wrap services for api calls.""" + res: bool = False + try: + async with async_timeout.timeout(TIMEOUT): + res = await function(*args, **kwargs) + except SENSIBO_ERRORS as err: + raise HomeAssistantError from err + + LOGGER.debug("Result %s for entity %s with arguments %s", res, args[0], kwargs) + entity: SensiboDeviceBaseEntity = args[0] + if res is not True: + raise HomeAssistantError(f"Could not execute service for {entity.name}") + if kwargs.get("key") is not None and kwargs.get("value") is not None: + setattr(entity.device_data, kwargs["key"], kwargs["value"]) + LOGGER.debug("Debug check key %s is now %s", kwargs["key"], kwargs["value"]) + entity.async_write_ha_state() + await entity.coordinator.async_request_refresh() + + return wrap_api_call + class SensiboBaseEntity(CoordinatorEntity[SensiboDataUpdateCoordinator]): - """Representation of a Sensibo entity.""" + """Representation of a Sensibo Base Entity.""" def __init__( self, @@ -35,7 +67,7 @@ class SensiboBaseEntity(CoordinatorEntity[SensiboDataUpdateCoordinator]): class SensiboDeviceBaseEntity(SensiboBaseEntity): - """Representation of a Sensibo device.""" + """Representation of a Sensibo Device.""" _attr_has_entity_name = True @@ -44,7 +76,7 @@ class SensiboDeviceBaseEntity(SensiboBaseEntity): coordinator: SensiboDataUpdateCoordinator, device_id: str, ) -> None: - """Initiate Sensibo Number.""" + """Initiate Sensibo Device.""" super().__init__(coordinator, device_id) self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self.device_data.id)}, @@ -58,63 +90,9 @@ class SensiboDeviceBaseEntity(SensiboBaseEntity): suggested_area=self.device_data.name, ) - async def async_send_command( - self, command: str, params: dict[str, Any] | None = None - ) -> 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] | None = None - ) -> dict[str, Any]: - """Send api call.""" - result: dict[str, Any] = {"status": None} - if command == "set_calibration": - if TYPE_CHECKING: - assert params is not None - result = await self._client.async_set_calibration( - self._device_id, - params["data"], - ) - if command == "set_ac_state": - if TYPE_CHECKING: - assert params is not None - result = await self._client.async_set_ac_state_property( - self._device_id, - params["name"], - params["value"], - params["ac_states"], - params["assumed_state"], - ) - if command == "set_timer": - if TYPE_CHECKING: - assert params is not None - result = await self._client.async_set_timer(self._device_id, params) - if command == "del_timer": - result = await self._client.async_del_timer(self._device_id) - if command == "set_pure_boost": - if TYPE_CHECKING: - assert params is not None - result = await self._client.async_set_pureboost( - self._device_id, - params, - ) - if command == "reset_filter": - result = await self._client.async_reset_filter(self._device_id) - return result - class SensiboMotionBaseEntity(SensiboBaseEntity): - """Representation of a Sensibo motion entity.""" + """Representation of a Sensibo Motion Entity.""" _attr_has_entity_name = True @@ -141,7 +119,7 @@ class SensiboMotionBaseEntity(SensiboBaseEntity): @property def sensor_data(self) -> MotionSensor | None: - """Return data for device.""" + """Return data for Motion Sensor.""" if TYPE_CHECKING: assert self.device_data.motion_sensors return self.device_data.motion_sensors[self._sensor_id] diff --git a/homeassistant/components/sensibo/number.py b/homeassistant/components/sensibo/number.py index bcad658c700..6550d7382d3 100644 --- a/homeassistant/components/sensibo/number.py +++ b/homeassistant/components/sensibo/number.py @@ -1,18 +1,21 @@ """Number platform for Sensibo integration.""" from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass +from typing import Any + +from pysensibo.model import SensiboDevice 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.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import SensiboDataUpdateCoordinator -from .entity import SensiboDeviceBaseEntity +from .entity import SensiboDeviceBaseEntity, async_handle_api_call PARALLEL_UPDATES = 0 @@ -22,6 +25,7 @@ class SensiboEntityDescriptionMixin: """Mixin values for Sensibo entities.""" remote_key: str + value_fn: Callable[[SensiboDevice], float | None] @dataclass @@ -42,6 +46,7 @@ DEVICE_NUMBER_TYPES = ( native_min_value=-10, native_max_value=10, native_step=0.1, + value_fn=lambda data: data.calibration_temp, ), SensiboNumberEntityDescription( key="calibration_hum", @@ -53,6 +58,7 @@ DEVICE_NUMBER_TYPES = ( native_min_value=-10, native_max_value=10, native_step=0.1, + value_fn=lambda data: data.calibration_hum, ), ) @@ -90,15 +96,22 @@ class SensiboNumber(SensiboDeviceBaseEntity, NumberEntity): @property def native_value(self) -> float | None: """Return the value from coordinator data.""" - value: float | None = getattr(self.device_data, self.entity_description.key) - return value + return self.entity_description.value_fn(self.device_data) async def async_set_native_value(self, value: float) -> None: """Set value for calibration.""" + await self.async_send_api_call( + device_data=self.device_data, key=self.entity_description.key, value=value + ) + + @async_handle_api_call + async def async_send_api_call( + self, device_data: SensiboDevice, key: Any, value: Any + ) -> bool: + """Make service call to api.""" data = {self.entity_description.remote_key: value} - result = await self.async_send_command("set_calibration", {"data": data}) - if result["status"] == "success": - setattr(self.device_data, self.entity_description.key, value) - self.async_write_ha_state() - return - raise HomeAssistantError(f"Could not set calibration for device {self.name}") + result = await self._client.async_set_calibration( + self._device_id, + data, + ) + return bool(result.get("status") == "success") diff --git a/homeassistant/components/sensibo/select.py b/homeassistant/components/sensibo/select.py index a8cfc527704..ab95377d016 100644 --- a/homeassistant/components/sensibo/select.py +++ b/homeassistant/components/sensibo/select.py @@ -1,7 +1,11 @@ -"""Number platform for Sensibo integration.""" +"""Select platform for Sensibo integration.""" from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +from pysensibo.model import SensiboDevice from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry @@ -11,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import SensiboDataUpdateCoordinator -from .entity import SensiboDeviceBaseEntity +from .entity import SensiboDeviceBaseEntity, async_handle_api_call PARALLEL_UPDATES = 0 @@ -20,31 +24,34 @@ PARALLEL_UPDATES = 0 class SensiboSelectDescriptionMixin: """Mixin values for Sensibo entities.""" - remote_key: str - remote_options: str + data_key: str + value_fn: Callable[[SensiboDevice], str | None] + options_fn: Callable[[SensiboDevice], list[str] | None] @dataclass class SensiboSelectEntityDescription( SelectEntityDescription, SensiboSelectDescriptionMixin ): - """Class describing Sensibo Number entities.""" + """Class describing Sensibo Select entities.""" DEVICE_SELECT_TYPES = ( SensiboSelectEntityDescription( key="horizontalSwing", - remote_key="horizontal_swing_mode", - remote_options="horizontal_swing_modes", + data_key="horizontal_swing_mode", name="Horizontal swing", icon="mdi:air-conditioner", + value_fn=lambda data: data.horizontal_swing_mode, + options_fn=lambda data: data.horizontal_swing_modes, ), SensiboSelectEntityDescription( key="light", - remote_key="light_mode", - remote_options="light_modes", + data_key="light_mode", name="Light", icon="mdi:flashlight", + value_fn=lambda data: data.light_mode, + options_fn=lambda data: data.light_modes, ), ) @@ -83,15 +90,15 @@ class SensiboSelect(SensiboDeviceBaseEntity, SelectEntity): @property def current_option(self) -> str | None: """Return the current selected option.""" - option: str | None = getattr( - self.device_data, self.entity_description.remote_key - ) - return option + return self.entity_description.value_fn(self.device_data) @property def options(self) -> list[str]: """Return possible options.""" - return getattr(self.device_data, self.entity_description.remote_options) or [] + options = self.entity_description.options_fn(self.device_data) + if TYPE_CHECKING: + assert options is not None + return options async def async_select_option(self, option: str) -> None: """Set state to the selected option.""" @@ -100,20 +107,28 @@ class SensiboSelect(SensiboDeviceBaseEntity, SelectEntity): f"Current mode {self.device_data.hvac_mode} doesn't support setting {self.entity_description.name}" ) - params = { + await self.async_send_api_call( + device_data=self.device_data, + key=self.entity_description.data_key, + value=option, + ) + + @async_handle_api_call + async def async_send_api_call( + self, device_data: SensiboDevice, key: Any, value: Any + ) -> bool: + """Make service call to api.""" + data = { "name": self.entity_description.key, - "value": option, + "value": value, "ac_states": self.device_data.ac_states, "assumed_state": False, } - result = await self.async_send_command("set_ac_state", params) - - if result["result"]["status"] == "Success": - setattr(self.device_data, self.entity_description.remote_key, option) - self.async_write_ha_state() - return - - failure = result["result"]["failureReason"] - raise HomeAssistantError( - f"Could not set state for device {self.name} due to reason {failure}" + result = await self._client.async_set_ac_state_property( + self._device_id, + data["name"], + data["value"], + data["ac_states"], + data["assumed_state"], ) + return bool(result.get("result", {}).get("status") == "Success") diff --git a/homeassistant/components/sensibo/sensor.py b/homeassistant/components/sensibo/sensor.py index f21366c7aa6..e9d9447ad81 100644 --- a/homeassistant/components/sensibo/sensor.py +++ b/homeassistant/components/sensibo/sensor.py @@ -63,7 +63,7 @@ class SensiboMotionSensorEntityDescription( class SensiboDeviceSensorEntityDescription( SensorEntityDescription, DeviceBaseEntityDescriptionMixin ): - """Describes Sensibo Motion sensor entity.""" + """Describes Sensibo Device sensor entity.""" FILTER_LAST_RESET_DESCRIPTION = SensiboDeviceSensorEntityDescription( @@ -178,6 +178,8 @@ AIRQ_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( ), ) +DESCRIPTION_BY_MODELS = {"pure": PURE_SENSOR_TYPES, "airq": AIRQ_SENSOR_TYPES} + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -200,20 +202,9 @@ async def async_setup_entry( entities.extend( SensiboDeviceSensor(coordinator, device_id, description) for device_id, device_data in coordinator.data.parsed.items() - for description in PURE_SENSOR_TYPES - if device_data.model == "pure" - ) - entities.extend( - SensiboDeviceSensor(coordinator, device_id, description) - for device_id, device_data in coordinator.data.parsed.items() - for description in DEVICE_SENSOR_TYPES - if device_data.model != "pure" - ) - entities.extend( - SensiboDeviceSensor(coordinator, device_id, description) - for device_id, device_data in coordinator.data.parsed.items() - for description in AIRQ_SENSOR_TYPES - if device_data.model == "airq" + for description in DESCRIPTION_BY_MODELS.get( + device_data.model, DEVICE_SENSOR_TYPES + ) ) async_add_entities(entities) diff --git a/homeassistant/components/sensibo/strings.json b/homeassistant/components/sensibo/strings.json index dc27644b3e1..19c6a7e594a 100644 --- a/homeassistant/components/sensibo/strings.json +++ b/homeassistant/components/sensibo/strings.json @@ -15,11 +15,17 @@ "user": { "data": { "api_key": "[%key:common::config_flow::data::api_key%]" + }, + "data_description": { + "api_key": "Follow the documentation to get your api key." } }, "reauth_confirm": { "data": { "api_key": "[%key:common::config_flow::data::api_key%]" + }, + "data_description": { + "api_key": "Follow the documentation to get a new api key." } } } diff --git a/homeassistant/components/sensibo/switch.py b/homeassistant/components/sensibo/switch.py index 14cfaac73ae..c06bf4d1ac6 100644 --- a/homeassistant/components/sensibo/switch.py +++ b/homeassistant/components/sensibo/switch.py @@ -14,25 +14,24 @@ from homeassistant.components.switch import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import SensiboDataUpdateCoordinator -from .entity import SensiboDeviceBaseEntity +from .entity import SensiboDeviceBaseEntity, async_handle_api_call PARALLEL_UPDATES = 0 @dataclass class DeviceBaseEntityDescriptionMixin: - """Mixin for required Sensibo base description keys.""" + """Mixin for required Sensibo Device description keys.""" value_fn: Callable[[SensiboDevice], bool | None] extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None]] | None command_on: str command_off: str - remote_key: str + data_key: str @dataclass @@ -52,7 +51,7 @@ DEVICE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = ( extra_fn=lambda data: {"id": data.timer_id, "turn_on": data.timer_state_on}, command_on="set_timer", command_off="del_timer", - remote_key="timer_on", + data_key="timer_on", ), ) @@ -65,56 +64,27 @@ PURE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = ( extra_fn=None, command_on="set_pure_boost", command_off="set_pure_boost", - remote_key="pure_boost_enabled", + data_key="pure_boost_enabled", ), ) - -def build_params(command: str, device_data: SensiboDevice) -> dict[str, Any] | None: - """Build params for turning on switch.""" - if command == "set_timer": - new_state = bool(device_data.ac_states["on"] is False) - params = { - "minutesFromNow": 60, - "acState": {**device_data.ac_states, "on": new_state}, - } - return params - if command == "set_pure_boost": - new_state = bool(device_data.pure_boost_enabled is False) - params = {"enabled": new_state} - if device_data.pure_measure_integration is None: - params["sensitivity"] = "N" - params["measurementsIntegration"] = True - params["acIntegration"] = False - params["geoIntegration"] = False - params["primeIntegration"] = False - return params - return None +DESCRIPTION_BY_MODELS = {"pure": PURE_SWITCH_TYPES} async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: - """Set up Sensibo binary sensor platform.""" + """Set up Sensibo Switch platform.""" coordinator: SensiboDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - entities: list[SensiboDeviceSwitch] = [] - - entities.extend( + async_add_entities( SensiboDeviceSwitch(coordinator, device_id, description) - for description in DEVICE_SWITCH_TYPES for device_id, device_data in coordinator.data.parsed.items() - if device_data.model != "pure" + for description in DESCRIPTION_BY_MODELS.get( + device_data.model, DEVICE_SWITCH_TYPES + ) ) - entities.extend( - SensiboDeviceSwitch(coordinator, device_id, description) - for description in PURE_SWITCH_TYPES - for device_id, device_data in coordinator.data.parsed.items() - if device_data.model == "pure" - ) - - async_add_entities(entities) class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity): @@ -143,33 +113,33 @@ class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" - params = build_params(self.entity_description.command_on, self.device_data) - result = await self.async_send_command( - self.entity_description.command_on, params - ) - - if result["status"] == "success": - setattr(self.device_data, self.entity_description.remote_key, True) - self.async_write_ha_state() - return await self.coordinator.async_request_refresh() - raise HomeAssistantError( - f"Could not execute {self.entity_description.command_on} for device {self.name}" - ) + if self.entity_description.key == "timer_on_switch": + await self.async_turn_on_timer( + device_data=self.device_data, + key=self.entity_description.data_key, + value=True, + ) + if self.entity_description.key == "pure_boost_switch": + await self.async_turn_on_off_pure_boost( + device_data=self.device_data, + key=self.entity_description.data_key, + value=True, + ) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" - params = build_params(self.entity_description.command_on, self.device_data) - result = await self.async_send_command( - self.entity_description.command_off, params - ) - - if result["status"] == "success": - setattr(self.device_data, self.entity_description.remote_key, False) - self.async_write_ha_state() - return await self.coordinator.async_request_refresh() - raise HomeAssistantError( - f"Could not execute {self.entity_description.command_off} for device {self.name}" - ) + if self.entity_description.key == "timer_on_switch": + await self.async_turn_off_timer( + device_data=self.device_data, + key=self.entity_description.data_key, + value=False, + ) + if self.entity_description.key == "pure_boost_switch": + await self.async_turn_on_off_pure_boost( + device_data=self.device_data, + key=self.entity_description.data_key, + value=False, + ) @property def extra_state_attributes(self) -> Mapping[str, Any] | None: @@ -177,3 +147,43 @@ class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity): if self.entity_description.extra_fn: return self.entity_description.extra_fn(self.device_data) return None + + @async_handle_api_call + async def async_turn_on_timer( + self, device_data: SensiboDevice, key: Any, value: Any + ) -> bool: + """Make service call to api for setting timer.""" + result = {} + new_state = bool(device_data.ac_states["on"] is False) + data = { + "minutesFromNow": 60, + "acState": {**device_data.ac_states, "on": new_state}, + } + result = await self._client.async_set_timer(self._device_id, data) + return bool(result.get("status") == "success") + + @async_handle_api_call + async def async_turn_off_timer( + self, device_data: SensiboDevice, key: Any, value: Any + ) -> bool: + """Make service call to api for deleting timer.""" + result = {} + result = await self._client.async_del_timer(self._device_id) + return bool(result.get("status") == "success") + + @async_handle_api_call + async def async_turn_on_off_pure_boost( + self, device_data: SensiboDevice, key: Any, value: Any + ) -> bool: + """Make service call to api for setting Pure Boost.""" + result = {} + new_state = bool(device_data.pure_boost_enabled is False) + data: dict[str, Any] = {"enabled": new_state} + if device_data.pure_measure_integration is None: + data["sensitivity"] = "N" + data["measurementsIntegration"] = True + data["acIntegration"] = False + data["geoIntegration"] = False + data["primeIntegration"] = False + result = await self._client.async_set_pureboost(self._device_id, data) + return bool(result.get("status") == "success") diff --git a/homeassistant/components/sensibo/translations/en.json b/homeassistant/components/sensibo/translations/en.json index 7b7c8aab7f1..3f78e3be98d 100644 --- a/homeassistant/components/sensibo/translations/en.json +++ b/homeassistant/components/sensibo/translations/en.json @@ -15,11 +15,17 @@ "reauth_confirm": { "data": { "api_key": "API Key" + }, + "data_description": { + "api_key": "Follow the documentation to get a new api key." } }, "user": { "data": { "api_key": "API Key" + }, + "data_description": { + "api_key": "Follow the documentation to get your api key." } } } diff --git a/homeassistant/components/sensibo/util.py b/homeassistant/components/sensibo/util.py index 8a181cbe568..9070be3412a 100644 --- a/homeassistant/components/sensibo/util.py +++ b/homeassistant/components/sensibo/util.py @@ -12,7 +12,7 @@ from .const import LOGGER, SENSIBO_ERRORS, TIMEOUT async def async_validate_api(hass: HomeAssistant, api_key: str) -> str: - """Get data from API.""" + """Validate the api and return username.""" client = SensiboClient( api_key, session=async_get_clientsession(hass), diff --git a/tests/components/sensibo/test_button.py b/tests/components/sensibo/test_button.py index 66b7a1258b1..c0602525931 100644 --- a/tests/components/sensibo/test_button.py +++ b/tests/components/sensibo/test_button.py @@ -36,7 +36,9 @@ async def test_button( assert state_filter_clean.state is STATE_ON assert state_filter_last_reset.state == "2022-03-12T15:24:26+00:00" - freezer.move_to(datetime(2022, 6, 19, 20, 0, 0)) + today = datetime(datetime.now().year + 1, 6, 19, 20, 0, 0).replace(tzinfo=dt.UTC) + today_str = today.isoformat() + freezer.move_to(today) with patch( "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", @@ -53,13 +55,13 @@ async def test_button( }, blocking=True, ) - await hass.async_block_till_done() + await hass.async_block_till_done() monkeypatch.setattr(get_data.parsed["ABC999111"], "filter_clean", False) monkeypatch.setattr( get_data.parsed["ABC999111"], "filter_last_reset", - datetime(2022, 6, 19, 20, 0, 0, tzinfo=dt.UTC), + today, ) with patch( @@ -75,11 +77,9 @@ async def test_button( state_button = hass.states.get("button.hallway_reset_filter") state_filter_clean = hass.states.get("binary_sensor.hallway_filter_clean_required") state_filter_last_reset = hass.states.get("sensor.hallway_filter_last_reset") - assert ( - state_button.state == datetime(2022, 6, 19, 20, 0, 0, tzinfo=dt.UTC).isoformat() - ) + assert state_button.state == today_str assert state_filter_clean.state is STATE_OFF - assert state_filter_last_reset.state == "2022-06-19T20:00:00+00:00" + assert state_filter_last_reset.state == today_str async def test_button_failure( diff --git a/tests/components/sensibo/test_climate.py b/tests/components/sensibo/test_climate.py index e7a3c465f76..b66bbd14afb 100644 --- a/tests/components/sensibo/test_climate.py +++ b/tests/components/sensibo/test_climate.py @@ -115,6 +115,9 @@ async def test_climate_fan( assert state1.attributes["fan_mode"] == "high" with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): @@ -180,6 +183,9 @@ async def test_climate_swing( assert state1.attributes["swing_mode"] == "stopped" with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): @@ -189,7 +195,7 @@ async def test_climate_swing( {ATTR_ENTITY_ID: state1.entity_id, ATTR_SWING_MODE: "fixedTop"}, blocking=True, ) - await hass.async_block_till_done() + await hass.async_block_till_done() state2 = hass.states.get("climate.hallway") assert state2.attributes["swing_mode"] == "fixedTop" @@ -244,6 +250,9 @@ async def test_climate_temperatures( assert state1.attributes["temperature"] == 25 with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): @@ -259,6 +268,9 @@ async def test_climate_temperatures( assert state2.attributes["temperature"] == 20 with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): @@ -274,6 +286,9 @@ async def test_climate_temperatures( assert state2.attributes["temperature"] == 16 with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): @@ -289,6 +304,9 @@ async def test_climate_temperatures( assert state2.attributes["temperature"] == 19 with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): @@ -304,6 +322,9 @@ async def test_climate_temperatures( assert state2.attributes["temperature"] == 20 with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): @@ -481,7 +502,7 @@ async def test_climate_hvac_mode( {ATTR_ENTITY_ID: state1.entity_id, ATTR_HVAC_MODE: "off"}, blocking=True, ) - await hass.async_block_till_done() + await hass.async_block_till_done() state2 = hass.states.get("climate.hallway") assert state2.state == "off" @@ -540,6 +561,9 @@ async def test_climate_on_off( assert state1.state == "heat" with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): @@ -549,12 +573,15 @@ async def test_climate_on_off( {ATTR_ENTITY_ID: state1.entity_id}, blocking=True, ) - await hass.async_block_till_done() + await hass.async_block_till_done() state2 = hass.states.get("climate.hallway") assert state2.state == "off" with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", return_value={"result": {"status": "Success"}}, ): diff --git a/tests/components/sensibo/test_entity.py b/tests/components/sensibo/test_entity.py index bf512f9f220..818d9ddb924 100644 --- a/tests/components/sensibo/test_entity.py +++ b/tests/components/sensibo/test_entity.py @@ -1,7 +1,7 @@ """The test for the sensibo entity.""" from __future__ import annotations -from unittest.mock import AsyncMock, patch +from unittest.mock import patch from pysensibo.model import SensiboData import pytest @@ -11,11 +11,6 @@ from homeassistant.components.climate.const import ( DOMAIN as CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, ) -from homeassistant.components.number.const import ( - ATTR_VALUE, - DOMAIN as NUMBER_DOMAIN, - SERVICE_SET_VALUE, -) from homeassistant.components.sensibo.const import SENSIBO_ERRORS from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID @@ -51,7 +46,7 @@ async def test_entity( @pytest.mark.parametrize("p_error", SENSIBO_ERRORS) -async def test_entity_send_command( +async def test_entity_failed_service_calls( hass: HomeAssistant, p_error: Exception, load_int: ConfigEntry, @@ -91,29 +86,3 @@ async def test_entity_send_command( state = hass.states.get("climate.hallway") assert state.attributes["fan_mode"] == "low" - - -async def test_entity_send_command_calibration( - hass: HomeAssistant, - entity_registry_enabled_by_default: AsyncMock, - load_int: ConfigEntry, - get_data: SensiboData, -) -> None: - """Test the Sensibo send command for calibration.""" - - state = hass.states.get("number.hallway_temperature_calibration") - assert state.state == "0.1" - - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_calibration", - return_value={"status": "success"}, - ): - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - {ATTR_ENTITY_ID: state.entity_id, ATTR_VALUE: 0.2}, - blocking=True, - ) - - state = hass.states.get("number.hallway_temperature_calibration") - assert state.state == "0.2" diff --git a/tests/components/sensibo/test_switch.py b/tests/components/sensibo/test_switch.py index 2a24751d70b..7b7b35cdd4e 100644 --- a/tests/components/sensibo/test_switch.py +++ b/tests/components/sensibo/test_switch.py @@ -8,7 +8,6 @@ from pysensibo.model import SensiboData import pytest from pytest import MonkeyPatch -from homeassistant.components.sensibo.switch import build_params from homeassistant.components.switch.const import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -134,6 +133,7 @@ async def test_switch_pure_boost( await hass.async_block_till_done() monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pure_boost_enabled", True) + monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pure_measure_integration", None) with patch( "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", @@ -223,28 +223,3 @@ async def test_switch_command_failure( }, blocking=True, ) - - -async def test_build_params( - hass: HomeAssistant, - load_int: ConfigEntry, - monkeypatch: MonkeyPatch, - get_data: SensiboData, -) -> None: - """Test the build params method.""" - - assert build_params("set_timer", get_data.parsed["ABC999111"]) == { - "minutesFromNow": 60, - "acState": {**get_data.parsed["ABC999111"].ac_states, "on": False}, - } - - monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pure_measure_integration", None) - assert build_params("set_pure_boost", get_data.parsed["AAZZAAZZ"]) == { - "enabled": True, - "sensitivity": "N", - "measurementsIntegration": True, - "acIntegration": False, - "geoIntegration": False, - "primeIntegration": False, - } - assert build_params("incorrect_command", get_data.parsed["ABC999111"]) is None