From b04165b495ecdd0b1309169ec2712ff726fe4a2a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 23 Oct 2022 22:22:14 +0200 Subject: [PATCH] Add Sensibo Climate React (#78221) --- homeassistant/components/sensibo/climate.py | 67 ++++ homeassistant/components/sensibo/sensor.py | 31 +- .../components/sensibo/services.yaml | 53 +++ .../components/sensibo/strings.sensor.json | 5 + homeassistant/components/sensibo/switch.py | 24 ++ .../sensibo/translations/sensor.en.json | 5 + tests/components/sensibo/test_climate.py | 311 ++++++++++++++++++ tests/components/sensibo/test_sensor.py | 19 +- tests/components/sensibo/test_switch.py | 110 +++++++ 9 files changed, 623 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index d4891d7cdf9..70d4a12406a 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -38,6 +38,12 @@ ATTR_MINUTES = "minutes" SERVICE_ENABLE_PURE_BOOST = "enable_pure_boost" SERVICE_DISABLE_PURE_BOOST = "disable_pure_boost" SERVICE_FULL_STATE = "full_state" +SERVICE_ENABLE_CLIMATE_REACT = "enable_climate_react" +ATTR_HIGH_TEMPERATURE_THRESHOLD = "high_temperature_threshold" +ATTR_HIGH_TEMPERATURE_STATE = "high_temperature_state" +ATTR_LOW_TEMPERATURE_THRESHOLD = "low_temperature_threshold" +ATTR_LOW_TEMPERATURE_STATE = "low_temperature_state" +ATTR_SMART_TYPE = "smart_type" ATTR_AC_INTEGRATION = "ac_integration" ATTR_GEO_INTEGRATION = "geo_integration" @@ -140,6 +146,20 @@ async def async_setup_entry( "async_full_ac_state", ) + platform.async_register_entity_service( + SERVICE_ENABLE_CLIMATE_REACT, + { + vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): float, + vol.Required(ATTR_HIGH_TEMPERATURE_STATE): dict, + vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): float, + vol.Required(ATTR_LOW_TEMPERATURE_STATE): dict, + vol.Required(ATTR_SMART_TYPE): vol.In( + ["temperature", "feelsLike", "humidity"] + ), + }, + "async_enable_climate_react", + ) + class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): """Representation of a Sensibo device.""" @@ -430,6 +450,42 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): data=params, ) + async def async_enable_climate_react( + self, + high_temperature_threshold: float, + high_temperature_state: dict[str, Any], + low_temperature_threshold: float, + low_temperature_state: dict[str, Any], + smart_type: str, + ) -> None: + """Enable Climate React Configuration.""" + high_temp = high_temperature_threshold + low_temp = low_temperature_threshold + + if high_temperature_state.get("temperatureUnit") == "F": + high_temp = TemperatureConverter.convert( + high_temperature_threshold, TEMP_FAHRENHEIT, TEMP_CELSIUS + ) + low_temp = TemperatureConverter.convert( + low_temperature_threshold, TEMP_FAHRENHEIT, TEMP_CELSIUS + ) + + params: dict[str, str | bool | float | dict] = { + "enabled": True, + "deviceUid": self._device_id, + "highTemperatureState": high_temperature_state, + "highTemperatureThreshold": high_temp, + "lowTemperatureState": low_temperature_state, + "lowTemperatureThreshold": low_temp, + "type": smart_type, + } + + await self.api_call_custom_service_climate_react( + key="smart_on", + value=True, + data=params, + ) + @async_handle_api_call async def async_send_api_call( self, @@ -470,6 +526,17 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): result = await self._client.async_set_pureboost(self._device_id, data) return bool(result.get("status") == "success") + @async_handle_api_call + async def api_call_custom_service_climate_react( + self, + key: str, + value: Any, + data: dict, + ) -> bool: + """Make service call to api.""" + result = await self._client.async_set_climate_react(self._device_id, data) + return bool(result.get("status") == "success") + @async_handle_api_call async def api_call_custom_service_full_ac_state( self, diff --git a/homeassistant/components/sensibo/sensor.py b/homeassistant/components/sensibo/sensor.py index 3fa764a8757..25d83089a52 100644 --- a/homeassistant/components/sensibo/sensor.py +++ b/homeassistant/components/sensibo/sensor.py @@ -155,6 +155,32 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( extra_fn=None, entity_registry_enabled_default=False, ), + SensiboDeviceSensorEntityDescription( + key="climate_react_low", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + name="Climate React low temperature threshold", + value_fn=lambda data: data.smart_low_temp_threshold, + extra_fn=lambda data: data.smart_low_state, + entity_registry_enabled_default=False, + ), + SensiboDeviceSensorEntityDescription( + key="climate_react_high", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + name="Climate React high temperature threshold", + value_fn=lambda data: data.smart_high_temp_threshold, + extra_fn=lambda data: data.smart_high_state, + entity_registry_enabled_default=False, + ), + SensiboDeviceSensorEntityDescription( + key="climate_react_type", + device_class="sensibo__smart_type", + name="Climate React type", + value_fn=lambda data: data.smart_type, + extra_fn=None, + entity_registry_enabled_default=False, + ), FILTER_LAST_RESET_DESCRIPTION, ) @@ -281,7 +307,10 @@ class SensiboDeviceSensor(SensiboDeviceBaseEntity, SensorEntity): @property def native_value(self) -> StateType | datetime: """Return value of sensor.""" - return self.entity_description.value_fn(self.device_data) + state = self.entity_description.value_fn(self.device_data) + if isinstance(state, str): + return state.lower() + return state @property def extra_state_attributes(self) -> Mapping[str, Any] | None: diff --git a/homeassistant/components/sensibo/services.yaml b/homeassistant/components/sensibo/services.yaml index 0f7e97976c6..f9f9365eb8e 100644 --- a/homeassistant/components/sensibo/services.yaml +++ b/homeassistant/components/sensibo/services.yaml @@ -146,3 +146,56 @@ full_state: options: - "on" - "off" +enable_climate_react: + name: Enable Climate React + description: Enable and configure Climate React + target: + entity: + integration: sensibo + domain: climate + fields: + high_temperature_threshold: + name: Threshold high + description: When temp/humidity goes above + required: true + example: 24 + selector: + number: + min: 0 + max: 150 + step: 0.1 + mode: box + high_temperature_state: + name: State high threshold + description: What should happen at high threshold. Requires full state + required: true + selector: + object: + low_temperature_threshold: + name: Threshold low + description: When temp/humidity goes below + required: true + example: 19 + selector: + number: + min: 0 + max: 150 + step: 0.1 + mode: box + low_temperature_state: + name: State low threshold + description: What should happen at low threshold. Requires full state + required: true + selector: + object: + smart_type: + name: Trigger type + description: Choose between temperature/feels like/humidity + required: true + example: "temperature" + selector: + select: + options: + - "temperature" + - "feelsLike" + - "humidity" diff --git a/homeassistant/components/sensibo/strings.sensor.json b/homeassistant/components/sensibo/strings.sensor.json index 2e4e05fba5b..6226fd26a0f 100644 --- a/homeassistant/components/sensibo/strings.sensor.json +++ b/homeassistant/components/sensibo/strings.sensor.json @@ -3,6 +3,11 @@ "sensibo__sensitivity": { "n": "Normal", "s": "Sensitive" + }, + "sensibo__smart_type": { + "temperature": "Temperature", + "feelslike": "Feels like", + "humidity": "Humidity" } } } diff --git a/homeassistant/components/sensibo/switch.py b/homeassistant/components/sensibo/switch.py index 3b75268fa9c..f57d72e5fb3 100644 --- a/homeassistant/components/sensibo/switch.py +++ b/homeassistant/components/sensibo/switch.py @@ -14,6 +14,7 @@ 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 @@ -53,6 +54,17 @@ DEVICE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = ( command_off="async_turn_off_timer", data_key="timer_on", ), + SensiboDeviceSwitchEntityDescription( + key="climate_react_switch", + device_class=SwitchDeviceClass.SWITCH, + name="Climate React", + icon="mdi:wizard-hat", + value_fn=lambda data: data.smart_on, + extra_fn=lambda data: {"type": data.smart_type}, + command_on="async_turn_on_off_smart", + command_off="async_turn_on_off_smart", + data_key="smart_on", + ), ) PURE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = ( @@ -166,3 +178,15 @@ class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity): data["primeIntegration"] = False result = await self._client.async_set_pureboost(self._device_id, data) return bool(result.get("status") == "success") + + @async_handle_api_call + async def async_turn_on_off_smart(self, key: str, value: Any) -> bool: + """Make service call to api for setting Climate React.""" + if self.device_data.smart_type is None: + raise HomeAssistantError( + "Use Sensibo Enable Climate React Service once to enable switch or the Sensibo app" + ) + new_state = bool(self.device_data.smart_on is False) + data: dict[str, Any] = {"enabled": new_state} + result = await self._client.async_enable_climate_react(self._device_id, data) + return bool(result.get("status") == "success") diff --git a/homeassistant/components/sensibo/translations/sensor.en.json b/homeassistant/components/sensibo/translations/sensor.en.json index 9ea1818b37c..b846d6a2074 100644 --- a/homeassistant/components/sensibo/translations/sensor.en.json +++ b/homeassistant/components/sensibo/translations/sensor.en.json @@ -3,6 +3,11 @@ "sensibo__sensitivity": { "n": "Normal", "s": "Sensitive" + }, + "sensibo__smart_type": { + "feelslike": "Feels like", + "humidity": "Humidity", + "temperature": "Temperature" } } } \ No newline at end of file diff --git a/tests/components/sensibo/test_climate.py b/tests/components/sensibo/test_climate.py index 555fc7f2ea5..224248c2d16 100644 --- a/tests/components/sensibo/test_climate.py +++ b/tests/components/sensibo/test_climate.py @@ -23,14 +23,20 @@ from homeassistant.components.climate import ( from homeassistant.components.sensibo.climate import ( ATTR_AC_INTEGRATION, ATTR_GEO_INTEGRATION, + ATTR_HIGH_TEMPERATURE_STATE, + ATTR_HIGH_TEMPERATURE_THRESHOLD, ATTR_HORIZONTAL_SWING_MODE, ATTR_INDOOR_INTEGRATION, ATTR_LIGHT, + ATTR_LOW_TEMPERATURE_STATE, + ATTR_LOW_TEMPERATURE_THRESHOLD, ATTR_MINUTES, ATTR_OUTDOOR_INTEGRATION, ATTR_SENSITIVITY, + ATTR_SMART_TYPE, ATTR_TARGET_TEMPERATURE, SERVICE_ASSUME_STATE, + SERVICE_ENABLE_CLIMATE_REACT, SERVICE_ENABLE_PURE_BOOST, SERVICE_ENABLE_TIMER, SERVICE_FULL_STATE, @@ -923,6 +929,311 @@ async def test_climate_pure_boost( assert state4.state == "s" +async def test_climate_climate_react( + hass: HomeAssistant, + entity_registry_enabled_by_default: AsyncMock, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate react custom service.""" + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state_climate = hass.states.get("climate.hallway") + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", + ): + with pytest.raises(MultipleInvalid): + await hass.services.async_call( + DOMAIN, + SERVICE_ENABLE_PURE_BOOST, + { + ATTR_ENTITY_ID: state_climate.entity_id, + ATTR_LOW_TEMPERATURE_THRESHOLD: 0.2, + ATTR_HIGH_TEMPERATURE_THRESHOLD: 30.3, + ATTR_SMART_TYPE: "temperature", + }, + blocking=True, + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", + return_value={ + "status": "success", + "result": { + "enabled": True, + "deviceUid": "ABC999111", + "highTemperatureState": { + "on": True, + "targetTemperature": 15, + "temperatureUnit": "C", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + "highTemperatureThreshold": 30.5, + "lowTemperatureState": { + "on": True, + "targetTemperature": 25, + "temperatureUnit": "C", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + "lowTemperatureThreshold": 5.5, + "type": "temperature", + }, + }, + ): + await hass.services.async_call( + DOMAIN, + SERVICE_ENABLE_CLIMATE_REACT, + { + ATTR_ENTITY_ID: state_climate.entity_id, + ATTR_LOW_TEMPERATURE_THRESHOLD: 5.5, + ATTR_HIGH_TEMPERATURE_THRESHOLD: 30.5, + ATTR_LOW_TEMPERATURE_STATE: { + "on": True, + "targetTemperature": 25, + "temperatureUnit": "C", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + ATTR_HIGH_TEMPERATURE_STATE: { + "on": True, + "targetTemperature": 15, + "temperatureUnit": "C", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + ATTR_SMART_TYPE: "temperature", + }, + blocking=True, + ) + await hass.async_block_till_done() + + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_on", True) + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_type", "temperature") + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_low_temp_threshold", 5.5) + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_high_temp_threshold", 30.5) + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "smart_low_state", + { + "on": True, + "targetTemperature": 25, + "temperatureUnit": "C", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + ) + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "smart_high_state", + { + "on": True, + "targetTemperature": 15, + "temperatureUnit": "C", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + ) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("switch.hallway_climate_react") + state2 = hass.states.get("sensor.hallway_climate_react_low_temperature_threshold") + state3 = hass.states.get("sensor.hallway_climate_react_high_temperature_threshold") + state4 = hass.states.get("sensor.hallway_climate_react_type") + assert state1.state == "on" + assert state2.state == "5.5" + assert state3.state == "30.5" + assert state4.state == "temperature" + + +async def test_climate_climate_react_fahrenheit( + hass: HomeAssistant, + entity_registry_enabled_by_default: AsyncMock, + load_int: ConfigEntry, + monkeypatch: pytest.MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo climate react custom service with fahrenheit.""" + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state_climate = hass.states.get("climate.hallway") + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", + return_value={ + "status": "success", + "result": { + "enabled": True, + "deviceUid": "ABC999111", + "highTemperatureState": { + "on": True, + "targetTemperature": 65, + "temperatureUnit": "F", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + "highTemperatureThreshold": 77, + "lowTemperatureState": { + "on": True, + "targetTemperature": 85, + "temperatureUnit": "F", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + "lowTemperatureThreshold": 32, + "type": "temperature", + }, + }, + ): + await hass.services.async_call( + DOMAIN, + SERVICE_ENABLE_CLIMATE_REACT, + { + ATTR_ENTITY_ID: state_climate.entity_id, + ATTR_LOW_TEMPERATURE_THRESHOLD: 32.0, + ATTR_HIGH_TEMPERATURE_THRESHOLD: 77.0, + ATTR_LOW_TEMPERATURE_STATE: { + "on": True, + "targetTemperature": 85, + "temperatureUnit": "F", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + ATTR_HIGH_TEMPERATURE_STATE: { + "on": True, + "targetTemperature": 65, + "temperatureUnit": "F", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + ATTR_SMART_TYPE: "temperature", + }, + blocking=True, + ) + await hass.async_block_till_done() + + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_on", True) + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_type", "temperature") + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_low_temp_threshold", 0) + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_high_temp_threshold", 25) + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "smart_low_state", + { + "on": True, + "targetTemperature": 85, + "temperatureUnit": "F", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + ) + monkeypatch.setattr( + get_data.parsed["ABC999111"], + "smart_high_state", + { + "on": True, + "targetTemperature": 65, + "temperatureUnit": "F", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + ) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("switch.hallway_climate_react") + state2 = hass.states.get("sensor.hallway_climate_react_low_temperature_threshold") + state3 = hass.states.get("sensor.hallway_climate_react_high_temperature_threshold") + state4 = hass.states.get("sensor.hallway_climate_react_type") + assert state1.state == "on" + assert state2.state == "0" + assert state3.state == "25" + assert state4.state == "temperature" + + async def test_climate_full_ac_state( hass: HomeAssistant, entity_registry_enabled_by_default: AsyncMock, diff --git a/tests/components/sensibo/test_sensor.py b/tests/components/sensibo/test_sensor.py index 426416ae2b9..676e9f1f73d 100644 --- a/tests/components/sensibo/test_sensor.py +++ b/tests/components/sensibo/test_sensor.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import timedelta -from unittest.mock import patch +from unittest.mock import AsyncMock, patch from pysensibo.model import SensiboData from pytest import MonkeyPatch @@ -16,6 +16,7 @@ from tests.common import async_fire_time_changed async def test_sensor( hass: HomeAssistant, + entity_registry_enabled_by_default: AsyncMock, load_int: ConfigEntry, monkeypatch: MonkeyPatch, get_data: SensiboData, @@ -25,9 +26,11 @@ async def test_sensor( state1 = hass.states.get("sensor.hallway_motion_sensor_battery_voltage") state2 = hass.states.get("sensor.kitchen_pm2_5") state3 = hass.states.get("sensor.kitchen_pure_sensitivity") + state4 = hass.states.get("sensor.hallway_climate_react_low_temperature_threshold") assert state1.state == "3000" assert state2.state == "1" assert state3.state == "n" + assert state4.state == "0.0" assert state2.attributes == { "state_class": "measurement", "unit_of_measurement": "µg/m³", @@ -35,6 +38,20 @@ async def test_sensor( "icon": "mdi:air-filter", "friendly_name": "Kitchen PM2.5", } + assert state4.attributes == { + "device_class": "temperature", + "friendly_name": "Hallway Climate React low temperature threshold", + "state_class": "measurement", + "unit_of_measurement": "°C", + "on": True, + "targetTemperature": 21, + "temperatureUnit": "C", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + } monkeypatch.setattr(get_data.parsed["AAZZAAZZ"], "pm25", 2) diff --git a/tests/components/sensibo/test_switch.py b/tests/components/sensibo/test_switch.py index 2b99fa2e227..3ff0e52a0f8 100644 --- a/tests/components/sensibo/test_switch.py +++ b/tests/components/sensibo/test_switch.py @@ -223,3 +223,113 @@ async def test_switch_command_failure( }, blocking=True, ) + + +async def test_switch_climate_react( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo switch for climate react.""" + + state1 = hass.states.get("switch.hallway_climate_react") + assert state1.state == STATE_OFF + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_enable_climate_react", + return_value={"status": "success"}, + ): + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: state1.entity_id, + }, + blocking=True, + ) + await hass.async_block_till_done() + + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_on", True) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + state1 = hass.states.get("switch.hallway_climate_react") + assert state1.state == STATE_ON + + with patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), patch( + "homeassistant.components.sensibo.util.SensiboClient.async_enable_climate_react", + return_value={"status": "success"}, + ): + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: state1.entity_id, + }, + blocking=True, + ) + await hass.async_block_till_done() + + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_on", False) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("switch.hallway_climate_react") + assert state1.state == STATE_OFF + + +async def test_switch_climate_react_no_data( + hass: HomeAssistant, + load_int: ConfigEntry, + monkeypatch: MonkeyPatch, + get_data: SensiboData, +) -> None: + """Test the Sensibo switch for climate react.""" + + monkeypatch.setattr(get_data.parsed["ABC999111"], "smart_type", None) + + with patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ): + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(minutes=5), + ) + await hass.async_block_till_done() + + state1 = hass.states.get("switch.hallway_climate_react") + assert state1.state == STATE_OFF + + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: state1.entity_id, + }, + blocking=True, + ) + await hass.async_block_till_done()