diff --git a/CODEOWNERS b/CODEOWNERS index 89417c4ca56..f59eb6322f2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -350,7 +350,7 @@ homeassistant/components/switchmate/* @danielhiversen homeassistant/components/syncthru/* @nielstron homeassistant/components/synology_srm/* @aerialls homeassistant/components/syslog/* @fabaff -homeassistant/components/tado/* @michaelarnauts +homeassistant/components/tado/* @michaelarnauts @bdraco homeassistant/components/tahoma/* @philklei homeassistant/components/tankerkoenig/* @guillempages homeassistant/components/tautulli/* @ludeeus diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index 727fb868a33..5442493cbaa 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -1,12 +1,13 @@ """Support for the (unofficial) Tado API.""" from datetime import timedelta import logging -import urllib from PyTado.interface import Tado +from requests import RequestException import voluptuous as vol from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.dispatcher import dispatcher_send @@ -109,7 +110,7 @@ class TadoConnector: """Connect to Tado and fetch the zones.""" try: self.tado = Tado(self._username, self._password) - except (RuntimeError, urllib.error.HTTPError) as exc: + except (RuntimeError, RequestException) as exc: _LOGGER.error("Unable to connect: %s", exc) return False @@ -136,7 +137,12 @@ class TadoConnector: if sensor_type == "zone": data = self.tado.getState(sensor) elif sensor_type == "device": - data = self.tado.getDevices()[0] + devices_data = self.tado.getDevices() + if not devices_data: + _LOGGER.info("There are no devices to setup on this tado account.") + return + + data = devices_data[0] else: _LOGGER.debug("Unknown sensor: %s", sensor_type) return @@ -162,31 +168,62 @@ class TadoConnector: self.tado.resetZoneOverlay(zone_id) self.update_sensor("zone", zone_id) + def set_home(self): + """Put tado in home mode.""" + response_json = None + try: + response_json = self.tado.setHome() + except RequestException as exc: + _LOGGER.error("Could not set home: %s", exc) + + _raise_home_away_errors(response_json) + + def set_away(self): + """Put tado in away mode.""" + response_json = None + try: + response_json = self.tado.setAway() + except RequestException as exc: + _LOGGER.error("Could not set away: %s", exc) + + _raise_home_away_errors(response_json) + def set_zone_overlay( self, - zone_id, - overlay_mode, + zone_id=None, + overlay_mode=None, temperature=None, duration=None, device_type="HEATING", mode=None, + fan_speed=None, ): """Set a zone overlay.""" _LOGGER.debug( - "Set overlay for zone %s: mode=%s, temp=%s, duration=%s, type=%s, mode=%s", + "Set overlay for zone %s: overlay_mode=%s, temp=%s, duration=%s, type=%s, mode=%s fan_speed=%s", zone_id, overlay_mode, temperature, duration, device_type, mode, + fan_speed, ) + try: self.tado.setZoneOverlay( - zone_id, overlay_mode, temperature, duration, device_type, "ON", mode + zone_id, + overlay_mode, + temperature, + duration, + device_type, + "ON", + mode, + fan_speed, ) - except urllib.error.HTTPError as exc: - _LOGGER.error("Could not set zone overlay: %s", exc.read()) + + except RequestException as exc: + _LOGGER.error("Could not set zone overlay: %s", exc) self.update_sensor("zone", zone_id) @@ -196,7 +233,18 @@ class TadoConnector: self.tado.setZoneOverlay( zone_id, overlay_mode, None, None, device_type, "OFF" ) - except urllib.error.HTTPError as exc: - _LOGGER.error("Could not set zone overlay: %s", exc.read()) + except RequestException as exc: + _LOGGER.error("Could not set zone overlay: %s", exc) self.update_sensor("zone", zone_id) + + +def _raise_home_away_errors(response_json): + if response_json is None: + return + + # Likely we are displaying to the user: + # Tried to update to HOME though all mobile devices are detected outside the home fence + if "errors" in response_json and len(response_json["errors"]) > 0: + error_list = response_json["errors"] + raise HomeAssistantError(error_list[0]["title"]) diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index b92a54edd5e..52b26738373 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -3,21 +3,12 @@ import logging from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate.const import ( - CURRENT_HVAC_COOL, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_IDLE, CURRENT_HVAC_OFF, - FAN_HIGH, - FAN_LOW, - FAN_MIDDLE, - FAN_OFF, - HVAC_MODE_AUTO, - HVAC_MODE_COOL, + FAN_AUTO, HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, PRESET_AWAY, PRESET_HOME, + SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) @@ -27,49 +18,29 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED from .const import ( + CONST_FAN_AUTO, + CONST_FAN_OFF, + CONST_MODE_COOL, + CONST_MODE_HEAT, CONST_MODE_OFF, CONST_MODE_SMART_SCHEDULE, CONST_OVERLAY_MANUAL, CONST_OVERLAY_TADO_MODE, - CONST_OVERLAY_TIMER, DATA, + HA_TO_TADO_FAN_MODE_MAP, + HA_TO_TADO_HVAC_MODE_MAP, + ORDERED_KNOWN_TADO_MODES, + SUPPORT_PRESET, + TADO_MODES_WITH_NO_TEMP_SETTING, + TADO_TO_HA_FAN_MODE_MAP, + TADO_TO_HA_HVAC_MODE_MAP, TYPE_AIR_CONDITIONING, TYPE_HEATING, ) +from .tado_adapter import TadoZoneData _LOGGER = logging.getLogger(__name__) -FAN_MAP_TADO = {"HIGH": FAN_HIGH, "MIDDLE": FAN_MIDDLE, "LOW": FAN_LOW} - -HVAC_MAP_TADO_HEAT = { - CONST_OVERLAY_MANUAL: HVAC_MODE_HEAT, - CONST_OVERLAY_TIMER: HVAC_MODE_HEAT, - CONST_OVERLAY_TADO_MODE: HVAC_MODE_HEAT, - CONST_MODE_SMART_SCHEDULE: HVAC_MODE_AUTO, - CONST_MODE_OFF: HVAC_MODE_OFF, -} -HVAC_MAP_TADO_COOL = { - CONST_OVERLAY_MANUAL: HVAC_MODE_COOL, - CONST_OVERLAY_TIMER: HVAC_MODE_COOL, - CONST_OVERLAY_TADO_MODE: HVAC_MODE_COOL, - CONST_MODE_SMART_SCHEDULE: HVAC_MODE_AUTO, - CONST_MODE_OFF: HVAC_MODE_OFF, -} -HVAC_MAP_TADO_HEAT_COOL = { - CONST_OVERLAY_MANUAL: HVAC_MODE_HEAT_COOL, - CONST_OVERLAY_TIMER: HVAC_MODE_HEAT_COOL, - CONST_OVERLAY_TADO_MODE: HVAC_MODE_HEAT_COOL, - CONST_MODE_SMART_SCHEDULE: HVAC_MODE_AUTO, - CONST_MODE_OFF: HVAC_MODE_OFF, -} - -SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE -SUPPORT_HVAC_HEAT = [HVAC_MODE_HEAT, HVAC_MODE_AUTO, HVAC_MODE_OFF] -SUPPORT_HVAC_COOL = [HVAC_MODE_COOL, HVAC_MODE_AUTO, HVAC_MODE_OFF] -SUPPORT_HVAC_HEAT_COOL = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO, HVAC_MODE_OFF] -SUPPORT_FAN = [FAN_HIGH, FAN_MIDDLE, FAN_LOW, FAN_OFF] -SUPPORT_PRESET = [PRESET_AWAY, PRESET_HOME] - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Tado climate platform.""" @@ -96,29 +67,80 @@ def create_climate_entity(tado, name: str, zone_id: int): _LOGGER.debug("Capabilities for zone %s: %s", zone_id, capabilities) zone_type = capabilities["type"] + support_flags = SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + supported_hvac_modes = [ + TADO_TO_HA_HVAC_MODE_MAP[CONST_MODE_OFF], + TADO_TO_HA_HVAC_MODE_MAP[CONST_MODE_SMART_SCHEDULE], + ] + supported_fan_modes = None + heat_temperatures = None + cool_temperatures = None - ac_support_heat = False if zone_type == TYPE_AIR_CONDITIONING: - # Only use heat if available - # (you don't have to setup a heat mode, but cool is required) # Heat is preferred as it generally has a lower minimum temperature - if "HEAT" in capabilities: - temperatures = capabilities["HEAT"]["temperatures"] - ac_support_heat = True - else: - temperatures = capabilities["COOL"]["temperatures"] - elif "temperatures" in capabilities: - temperatures = capabilities["temperatures"] + for mode in ORDERED_KNOWN_TADO_MODES: + if mode not in capabilities: + continue + + supported_hvac_modes.append(TADO_TO_HA_HVAC_MODE_MAP[mode]) + if not capabilities[mode].get("fanSpeeds"): + continue + + support_flags |= SUPPORT_FAN_MODE + + if supported_fan_modes: + continue + + supported_fan_modes = [ + TADO_TO_HA_FAN_MODE_MAP[speed] + for speed in capabilities[mode]["fanSpeeds"] + ] + + cool_temperatures = capabilities[CONST_MODE_COOL]["temperatures"] else: - _LOGGER.debug("Not adding zone %s since it has no temperature", name) + supported_hvac_modes.append(HVAC_MODE_HEAT) + + if CONST_MODE_HEAT in capabilities: + heat_temperatures = capabilities[CONST_MODE_HEAT]["temperatures"] + + if heat_temperatures is None and "temperatures" in capabilities: + heat_temperatures = capabilities["temperatures"] + + if cool_temperatures is None and heat_temperatures is None: + _LOGGER.debug("Not adding zone %s since it has no temperatures", name) return None - min_temp = float(temperatures["celsius"]["min"]) - max_temp = float(temperatures["celsius"]["max"]) - step = temperatures["celsius"].get("step", PRECISION_TENTHS) + heat_min_temp = None + heat_max_temp = None + heat_step = None + cool_min_temp = None + cool_max_temp = None + cool_step = None + + if heat_temperatures is not None: + heat_min_temp = float(heat_temperatures["celsius"]["min"]) + heat_max_temp = float(heat_temperatures["celsius"]["max"]) + heat_step = heat_temperatures["celsius"].get("step", PRECISION_TENTHS) + + if cool_temperatures is not None: + cool_min_temp = float(cool_temperatures["celsius"]["min"]) + cool_max_temp = float(cool_temperatures["celsius"]["max"]) + cool_step = cool_temperatures["celsius"].get("step", PRECISION_TENTHS) entity = TadoClimate( - tado, name, zone_id, zone_type, min_temp, max_temp, step, ac_support_heat, + tado, + name, + zone_id, + zone_type, + heat_min_temp, + heat_max_temp, + heat_step, + cool_min_temp, + cool_max_temp, + cool_step, + supported_hvac_modes, + supported_fan_modes, + support_flags, ) return entity @@ -132,10 +154,15 @@ class TadoClimate(ClimateDevice): zone_name, zone_id, zone_type, - min_temp, - max_temp, - step, - ac_support_heat, + heat_min_temp, + heat_max_temp, + heat_step, + cool_min_temp, + cool_max_temp, + cool_step, + supported_hvac_modes, + supported_fan_modes, + support_flags, ): """Initialize of Tado climate entity.""" self._tado = tado @@ -146,49 +173,45 @@ class TadoClimate(ClimateDevice): self._unique_id = f"{zone_type} {zone_id} {tado.device_id}" self._ac_device = zone_type == TYPE_AIR_CONDITIONING - self._ac_support_heat = ac_support_heat - self._cooling = False + self._supported_hvac_modes = supported_hvac_modes + self._supported_fan_modes = supported_fan_modes + self._support_flags = support_flags - self._active = False - self._device_is_active = False + self._available = False self._cur_temp = None self._cur_humidity = None - self._is_away = False - self._min_temp = min_temp - self._max_temp = max_temp - self._step = step + + self._heat_min_temp = heat_min_temp + self._heat_max_temp = heat_max_temp + self._heat_step = heat_step + + self._cool_min_temp = cool_min_temp + self._cool_max_temp = cool_max_temp + self._cool_step = cool_step + self._target_temp = None - if tado.fallback: - # Fallback to Smart Schedule at next Schedule switch - self._default_overlay = CONST_OVERLAY_TADO_MODE - else: - # Don't fallback to Smart Schedule, but keep in manual mode - self._default_overlay = CONST_OVERLAY_MANUAL + self._current_tado_fan_speed = CONST_FAN_OFF + self._current_tado_hvac_mode = CONST_MODE_OFF + self._current_hvac_action = CURRENT_HVAC_OFF - self._current_fan = CONST_MODE_OFF - self._current_operation = CONST_MODE_SMART_SCHEDULE - self._overlay_mode = CONST_MODE_SMART_SCHEDULE + self._tado_zone_data = None + self._async_update_zone_data() async def async_added_to_hass(self): """Register for sensor updates.""" - @callback - def async_update_callback(): - """Schedule an entity update.""" - self.async_schedule_update_ha_state(True) - async_dispatcher_connect( self.hass, SIGNAL_TADO_UPDATE_RECEIVED.format("zone", self.zone_id), - async_update_callback, + self._async_update_callback, ) @property def supported_features(self): """Return the list of supported features.""" - return SUPPORT_FLAGS + return self._support_flags @property def name(self): @@ -208,12 +231,12 @@ class TadoClimate(ClimateDevice): @property def current_humidity(self): """Return the current humidity.""" - return self._cur_humidity + return self._tado_zone_data.current_humidity @property def current_temperature(self): """Return the sensor temperature.""" - return self._cur_temp + return self._tado_zone_data.current_temp @property def hvac_mode(self): @@ -221,11 +244,9 @@ class TadoClimate(ClimateDevice): Need to be one of HVAC_MODE_*. """ - if self._ac_device and self._ac_support_heat: - return HVAC_MAP_TADO_HEAT_COOL.get(self._current_operation) - if self._ac_device and not self._ac_support_heat: - return HVAC_MAP_TADO_COOL.get(self._current_operation) - return HVAC_MAP_TADO_HEAT.get(self._current_operation) + return TADO_TO_HA_HVAC_MODE_MAP.get( + self._tado_zone_data.current_tado_hvac_mode, CURRENT_HVAC_OFF + ) @property def hvac_modes(self): @@ -233,11 +254,7 @@ class TadoClimate(ClimateDevice): Need to be a subset of HVAC_MODES. """ - if self._ac_device: - if self._ac_support_heat: - return SUPPORT_HVAC_HEAT_COOL - return SUPPORT_HVAC_COOL - return SUPPORT_HVAC_HEAT + return self._supported_hvac_modes @property def hvac_action(self): @@ -245,40 +262,28 @@ class TadoClimate(ClimateDevice): Need to be one of CURRENT_HVAC_*. """ - if not self._device_is_active: - return CURRENT_HVAC_OFF - if self._ac_device: - if self._active: - if self._ac_support_heat and not self._cooling: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_COOL - return CURRENT_HVAC_IDLE - if self._active: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + return self._tado_zone_data.current_hvac_action @property def fan_mode(self): """Return the fan setting.""" if self._ac_device: - return FAN_MAP_TADO.get(self._current_fan) + return TADO_TO_HA_FAN_MODE_MAP.get(self._current_tado_fan_speed, FAN_AUTO) return None @property def fan_modes(self): """List of available fan modes.""" - if self._ac_device: - return SUPPORT_FAN - return None + return self._supported_fan_modes def set_fan_mode(self, fan_mode: str): """Turn fan on/off.""" - pass + self._control_hvac(fan_mode=HA_TO_TADO_FAN_MODE_MAP[fan_mode]) @property def preset_mode(self): """Return the current preset mode (home, away).""" - if self._is_away: + if self._tado_zone_data.is_away: return PRESET_AWAY return PRESET_HOME @@ -289,7 +294,10 @@ class TadoClimate(ClimateDevice): def set_preset_mode(self, preset_mode): """Set new preset mode.""" - pass + if preset_mode == PRESET_HOME: + self._tado.set_home() + else: + self._tado.set_away() @property def temperature_unit(self): @@ -299,12 +307,14 @@ class TadoClimate(ClimateDevice): @property def target_temperature_step(self): """Return the supported step of target temperature.""" - return self._step + if self._tado_zone_data.current_tado_hvac_mode == CONST_MODE_COOL: + return self._cool_step or self._heat_step + return self._heat_step or self._cool_step @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._target_temp + return self._tado_zone_data.target_temp def set_temperature(self, **kwargs): """Set new target temperature.""" @@ -312,174 +322,142 @@ class TadoClimate(ClimateDevice): if temperature is None: return - self._current_operation = self._default_overlay - self._overlay_mode = None - self._target_temp = temperature - self._control_heating() + self._control_hvac(target_temp=temperature) def set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" - mode = None - if hvac_mode == HVAC_MODE_OFF: - mode = CONST_MODE_OFF - elif hvac_mode == HVAC_MODE_AUTO: - mode = CONST_MODE_SMART_SCHEDULE - elif hvac_mode == HVAC_MODE_HEAT: - mode = self._default_overlay - elif hvac_mode == HVAC_MODE_COOL: - mode = self._default_overlay - elif hvac_mode == HVAC_MODE_HEAT_COOL: - mode = self._default_overlay + self._control_hvac(hvac_mode=HA_TO_TADO_HVAC_MODE_MAP[hvac_mode]) - self._current_operation = mode - self._overlay_mode = None - - # Set a target temperature if we don't have any - # This can happen when we switch from Off to On - if self._target_temp is None: - if self._ac_device: - self._target_temp = self.max_temp - else: - self._target_temp = self.min_temp - self.schedule_update_ha_state() - - self._control_heating() + @property + def available(self): + """Return if the device is available.""" + return self._tado_zone_data.available @property def min_temp(self): """Return the minimum temperature.""" - return self._min_temp + if ( + self._current_tado_hvac_mode == CONST_MODE_COOL + and self._cool_min_temp is not None + ): + return self._cool_min_temp + if self._heat_min_temp is not None: + return self._heat_min_temp + + return self._cool_min_temp @property def max_temp(self): """Return the maximum temperature.""" - return self._max_temp - - def update(self): - """Handle update callbacks.""" - _LOGGER.debug("Updating climate platform for zone %d", self.zone_id) - data = self._tado.data["zone"][self.zone_id] - - if "sensorDataPoints" in data: - sensor_data = data["sensorDataPoints"] - - if "insideTemperature" in sensor_data: - temperature = float(sensor_data["insideTemperature"]["celsius"]) - self._cur_temp = temperature - - if "humidity" in sensor_data: - humidity = float(sensor_data["humidity"]["percentage"]) - self._cur_humidity = humidity - - # temperature setting will not exist when device is off if ( - "temperature" in data["setting"] - and data["setting"]["temperature"] is not None + self._current_tado_hvac_mode == CONST_MODE_HEAT + and self._heat_max_temp is not None ): - setting = float(data["setting"]["temperature"]["celsius"]) - self._target_temp = setting + return self._heat_max_temp + if self._heat_max_temp is not None: + return self._heat_max_temp - if "tadoMode" in data: - mode = data["tadoMode"] - self._is_away = mode == "AWAY" + return self._heat_max_temp - if "setting" in data: - power = data["setting"]["power"] - if power == "OFF": - self._current_operation = CONST_MODE_OFF - self._current_fan = CONST_MODE_OFF - # There is no overlay, the mode will always be - # "SMART_SCHEDULE" - self._overlay_mode = CONST_MODE_SMART_SCHEDULE - self._device_is_active = False + @callback + def _async_update_zone_data(self): + """Load tado data into zone.""" + self._tado_zone_data = TadoZoneData( + self._tado.data["zone"][self.zone_id], self.zone_id + ) + + @callback + def _async_update_callback(self): + """Load tado data and update state.""" + self._async_update_zone_data() + self.async_write_ha_state() + + def _normalize_target_temp_for_hvac_mode(self): + # Set a target temperature if we don't have any + # This can happen when we switch from Off to On + if self._target_temp is None: + if self._current_tado_hvac_mode == CONST_MODE_COOL: + self._target_temp = self._cool_max_temp else: - self._device_is_active = True + self._target_temp = self._heat_min_temp + elif self._current_tado_hvac_mode == CONST_MODE_COOL: + if self._target_temp > self._cool_max_temp: + self._target_temp = self._cool_max_temp + elif self._target_temp < self._cool_min_temp: + self._target_temp = self._cool_min_temp + elif self._current_tado_hvac_mode == CONST_MODE_HEAT: + if self._target_temp > self._heat_max_temp: + self._target_temp = self._heat_max_temp + elif self._target_temp < self._heat_min_temp: + self._target_temp = self._heat_min_temp - active = False - if "activityDataPoints" in data: - activity_data = data["activityDataPoints"] - if self._ac_device: - if "acPower" in activity_data and activity_data["acPower"] is not None: - if not activity_data["acPower"]["value"] == "OFF": - active = True - else: - if ( - "heatingPower" in activity_data - and activity_data["heatingPower"] is not None - ): - if float(activity_data["heatingPower"]["percentage"]) > 0.0: - active = True - self._active = active - - overlay = False - overlay_data = None - termination = CONST_MODE_SMART_SCHEDULE - cooling = False - fan_speed = CONST_MODE_OFF - - if "overlay" in data: - overlay_data = data["overlay"] - overlay = overlay_data is not None - - if overlay: - termination = overlay_data["termination"]["type"] - setting = False - setting_data = None - - if "setting" in overlay_data: - setting_data = overlay_data["setting"] - setting = setting_data is not None - - if setting: - if "mode" in setting_data: - cooling = setting_data["mode"] == "COOL" - - if "fanSpeed" in setting_data: - fan_speed = setting_data["fanSpeed"] - - if self._device_is_active: - # If you set mode manually to off, there will be an overlay - # and a termination, but we want to see the mode "OFF" - self._overlay_mode = termination - self._current_operation = termination - - self._cooling = cooling - self._current_fan = fan_speed - - def _control_heating(self): + def _control_hvac(self, hvac_mode=None, target_temp=None, fan_mode=None): """Send new target temperature to Tado.""" - if self._current_operation == CONST_MODE_SMART_SCHEDULE: + + if hvac_mode: + self._current_tado_hvac_mode = hvac_mode + + if target_temp: + self._target_temp = target_temp + + if fan_mode: + self._current_tado_fan_speed = fan_mode + + self._normalize_target_temp_for_hvac_mode() + + # tado does not permit setting the fan speed to + # off, you must turn off the device + if ( + self._current_tado_fan_speed == CONST_FAN_OFF + and self._current_tado_hvac_mode != CONST_MODE_OFF + ): + self._current_tado_fan_speed = CONST_FAN_AUTO + + if self._current_tado_hvac_mode == CONST_MODE_OFF: + _LOGGER.debug( + "Switching to OFF for zone %s (%d)", self.zone_name, self.zone_id + ) + self._tado.set_zone_off(self.zone_id, CONST_OVERLAY_MANUAL, self.zone_type) + return + + if self._current_tado_hvac_mode == CONST_MODE_SMART_SCHEDULE: _LOGGER.debug( "Switching to SMART_SCHEDULE for zone %s (%d)", self.zone_name, self.zone_id, ) self._tado.reset_zone_overlay(self.zone_id) - self._overlay_mode = self._current_operation - return - - if self._current_operation == CONST_MODE_OFF: - _LOGGER.debug( - "Switching to OFF for zone %s (%d)", self.zone_name, self.zone_id - ) - self._tado.set_zone_off(self.zone_id, CONST_OVERLAY_MANUAL, self.zone_type) - self._overlay_mode = self._current_operation return _LOGGER.debug( "Switching to %s for zone %s (%d) with temperature %s °C", - self._current_operation, + self._current_tado_hvac_mode, self.zone_name, self.zone_id, self._target_temp, ) - self._tado.set_zone_overlay( - self.zone_id, - self._current_operation, - self._target_temp, - None, - self.zone_type, - "COOL" if self._ac_device else None, + + # Fallback to Smart Schedule at next Schedule switch if we have fallback enabled + overlay_mode = ( + CONST_OVERLAY_TADO_MODE if self._tado.fallback else CONST_OVERLAY_MANUAL + ) + + temperature_to_send = self._target_temp + if self._current_tado_hvac_mode in TADO_MODES_WITH_NO_TEMP_SETTING: + # A temperature cannot be passed with these modes + temperature_to_send = None + + self._tado.set_zone_overlay( + zone_id=self.zone_id, + overlay_mode=overlay_mode, # What to do when the period ends + temperature=temperature_to_send, + duration=None, + device_type=self.zone_type, + mode=self._current_tado_hvac_mode, + fan_speed=( + self._current_tado_fan_speed + if (self._support_flags & SUPPORT_FAN_MODE) + else None + ), # api defaults to not sending fanSpeed if not specified ) - self._overlay_mode = self._current_operation diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index 8d67e3bf9f8..a2630a8f9c2 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -1,5 +1,26 @@ """Constant values for the Tado component.""" +from homeassistant.components.climate.const import ( + CURRENT_HVAC_COOL, + CURRENT_HVAC_DRY, + CURRENT_HVAC_FAN, + CURRENT_HVAC_HEAT, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + FAN_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_HOME, +) + # Configuration CONF_FALLBACK = "fallback" DATA = "data" @@ -10,10 +31,81 @@ TYPE_HEATING = "HEATING" TYPE_HOT_WATER = "HOT_WATER" # Base modes +CONST_MODE_OFF = "OFF" CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Use the schedule -CONST_MODE_OFF = "OFF" # Switch off heating in a zone +CONST_MODE_AUTO = "AUTO" +CONST_MODE_COOL = "COOL" +CONST_MODE_HEAT = "HEAT" +CONST_MODE_DRY = "DRY" +CONST_MODE_FAN = "FAN" + +CONST_LINK_OFFLINE = "OFFLINE" + +CONST_FAN_OFF = "OFF" +CONST_FAN_AUTO = "AUTO" +CONST_FAN_LOW = "LOW" +CONST_FAN_MIDDLE = "MIDDLE" +CONST_FAN_HIGH = "HIGH" + # When we change the temperature setting, we need an overlay mode CONST_OVERLAY_TADO_MODE = "TADO_MODE" # wait until tado changes the mode automatic CONST_OVERLAY_MANUAL = "MANUAL" # the user has change the temperature or mode manually CONST_OVERLAY_TIMER = "TIMER" # the temperature will be reset after a timespan + + +# Heat always comes first since we get the +# min and max tempatures for the zone from +# it. +# Heat is preferred as it generally has a lower minimum temperature +ORDERED_KNOWN_TADO_MODES = [ + CONST_MODE_HEAT, + CONST_MODE_COOL, + CONST_MODE_AUTO, + CONST_MODE_DRY, + CONST_MODE_FAN, +] + +TADO_MODES_TO_HA_CURRENT_HVAC_ACTION = { + CONST_MODE_HEAT: CURRENT_HVAC_HEAT, + CONST_MODE_DRY: CURRENT_HVAC_DRY, + CONST_MODE_FAN: CURRENT_HVAC_FAN, + CONST_MODE_COOL: CURRENT_HVAC_COOL, +} + +# These modes will not allow a temp to be set +TADO_MODES_WITH_NO_TEMP_SETTING = [CONST_MODE_AUTO, CONST_MODE_DRY, CONST_MODE_FAN] +# +# HVAC_MODE_HEAT_COOL is mapped to CONST_MODE_AUTO +# This lets tado decide on a temp +# +# HVAC_MODE_AUTO is mapped to CONST_MODE_SMART_SCHEDULE +# This runs the smart schedule +# +HA_TO_TADO_HVAC_MODE_MAP = { + HVAC_MODE_OFF: CONST_MODE_OFF, + HVAC_MODE_HEAT_COOL: CONST_MODE_AUTO, + HVAC_MODE_AUTO: CONST_MODE_SMART_SCHEDULE, + HVAC_MODE_HEAT: CONST_MODE_HEAT, + HVAC_MODE_COOL: CONST_MODE_COOL, + HVAC_MODE_DRY: CONST_MODE_DRY, + HVAC_MODE_FAN_ONLY: CONST_MODE_FAN, +} + +HA_TO_TADO_FAN_MODE_MAP = { + FAN_AUTO: CONST_FAN_AUTO, + FAN_OFF: CONST_FAN_OFF, + FAN_LOW: CONST_FAN_LOW, + FAN_MEDIUM: CONST_FAN_MIDDLE, + FAN_HIGH: CONST_FAN_HIGH, +} + +TADO_TO_HA_HVAC_MODE_MAP = { + value: key for key, value in HA_TO_TADO_HVAC_MODE_MAP.items() +} + +TADO_TO_HA_FAN_MODE_MAP = {value: key for key, value in HA_TO_TADO_FAN_MODE_MAP.items()} + +DEFAULT_TADO_PRECISION = 0.1 + +SUPPORT_PRESET = [PRESET_AWAY, PRESET_HOME] diff --git a/homeassistant/components/tado/manifest.json b/homeassistant/components/tado/manifest.json index e51cc53caa5..2589388a4da 100644 --- a/homeassistant/components/tado/manifest.json +++ b/homeassistant/components/tado/manifest.json @@ -3,10 +3,10 @@ "name": "Tado", "documentation": "https://www.home-assistant.io/integrations/tado", "requirements": [ - "python-tado==0.3.0" + "python-tado==0.4.0" ], "dependencies": [], "codeowners": [ - "@michaelarnauts" + "@michaelarnauts", "@bdraco" ] } diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 2cd40bee3fa..70014380512 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -8,6 +8,7 @@ from homeassistant.helpers.entity import Entity from . import DATA, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED from .const import TYPE_AIR_CONDITIONING, TYPE_HEATING, TYPE_HOT_WATER +from .tado_adapter import TadoZoneData _LOGGER = logging.getLogger(__name__) @@ -50,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for zone in tado.zones: entities.extend( [ - create_zone_sensor(tado, zone["name"], zone["id"], variable) + create_zone_sensor(hass, tado, zone["name"], zone["id"], variable) for variable in ZONE_SENSORS.get(zone["type"]) ] ) @@ -59,7 +60,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for home in tado.devices: entities.extend( [ - create_device_sensor(tado, home["name"], home["id"], variable) + create_device_sensor(hass, tado, home["name"], home["id"], variable) for variable in DEVICE_SENSORS ] ) @@ -67,21 +68,22 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -def create_zone_sensor(tado, name, zone_id, variable): +def create_zone_sensor(hass, tado, name, zone_id, variable): """Create a zone sensor.""" - return TadoSensor(tado, name, "zone", zone_id, variable) + return TadoSensor(hass, tado, name, "zone", zone_id, variable) -def create_device_sensor(tado, name, device_id, variable): +def create_device_sensor(hass, tado, name, device_id, variable): """Create a device sensor.""" - return TadoSensor(tado, name, "device", device_id, variable) + return TadoSensor(hass, tado, name, "device", device_id, variable) class TadoSensor(Entity): """Representation of a tado Sensor.""" - def __init__(self, tado, zone_name, sensor_type, zone_id, zone_variable): + def __init__(self, hass, tado, zone_name, sensor_type, zone_id, zone_variable): """Initialize of the Tado Sensor.""" + self.hass = hass self._tado = tado self.zone_name = zone_name @@ -93,19 +95,16 @@ class TadoSensor(Entity): self._state = None self._state_attributes = None + self._tado_zone_data = None + self._async_update_zone_data() async def async_added_to_hass(self): """Register for sensor updates.""" - @callback - def async_update_callback(): - """Schedule an entity update.""" - self.async_schedule_update_ha_state(True) - async_dispatcher_connect( self.hass, SIGNAL_TADO_UPDATE_RECEIVED.format(self.sensor_type, self.zone_id), - async_update_callback, + self._async_update_callback, ) @property @@ -149,97 +148,74 @@ class TadoSensor(Entity): return "mdi:water-percent" @property - def should_poll(self) -> bool: + def should_poll(self): """Do not poll.""" return False - def update(self): + @callback + def _async_update_callback(self): + """Update and write state.""" + self._async_update_zone_data() + self.async_write_ha_state() + + @callback + def _async_update_zone_data(self): """Handle update callbacks.""" try: data = self._tado.data[self.sensor_type][self.zone_id] except KeyError: return - unit = TEMP_CELSIUS + self._tado_zone_data = TadoZoneData(data, self.zone_id) if self.zone_variable == "temperature": - if "sensorDataPoints" in data: - sensor_data = data["sensorDataPoints"] - temperature = float(sensor_data["insideTemperature"]["celsius"]) - - self._state = self.hass.config.units.temperature(temperature, unit) - self._state_attributes = { - "time": sensor_data["insideTemperature"]["timestamp"], - "setting": 0, # setting is used in climate device - } - - # temperature setting will not exist when device is off - if ( - "temperature" in data["setting"] - and data["setting"]["temperature"] is not None - ): - temperature = float(data["setting"]["temperature"]["celsius"]) - - self._state_attributes[ - "setting" - ] = self.hass.config.units.temperature(temperature, unit) + self._state = self.hass.config.units.temperature( + self._tado_zone_data.current_temp, TEMP_CELSIUS + ) + self._state_attributes = { + "time": self._tado_zone_data.current_temp_timestamp, + "setting": 0, # setting is used in climate device + } elif self.zone_variable == "humidity": - if "sensorDataPoints" in data: - sensor_data = data["sensorDataPoints"] - self._state = float(sensor_data["humidity"]["percentage"]) - self._state_attributes = {"time": sensor_data["humidity"]["timestamp"]} + self._state = self._tado_zone_data.current_humidity + self._state_attributes = { + "time": self._tado_zone_data.current_humidity_timestamp + } elif self.zone_variable == "power": - if "setting" in data: - self._state = data["setting"]["power"] + self._state = self._tado_zone_data.power elif self.zone_variable == "link": - if "link" in data: - self._state = data["link"]["state"] + self._state = self._tado_zone_data.link elif self.zone_variable == "heating": - if "activityDataPoints" in data: - activity_data = data["activityDataPoints"] - - if ( - "heatingPower" in activity_data - and activity_data["heatingPower"] is not None - ): - self._state = float(activity_data["heatingPower"]["percentage"]) - self._state_attributes = { - "time": activity_data["heatingPower"]["timestamp"] - } + self._state = self._tado_zone_data.heating_power_percentage + self._state_attributes = { + "time": self._tado_zone_data.heating_power_timestamp + } elif self.zone_variable == "ac": - if "activityDataPoints" in data: - activity_data = data["activityDataPoints"] - - if "acPower" in activity_data and activity_data["acPower"] is not None: - self._state = activity_data["acPower"]["value"] - self._state_attributes = { - "time": activity_data["acPower"]["timestamp"] - } + self._state = self._tado_zone_data.ac_power + self._state_attributes = {"time": self._tado_zone_data.ac_power_timestamp} elif self.zone_variable == "tado bridge status": - if "connectionState" in data: - self._state = data["connectionState"]["value"] + self._state = self._tado_zone_data.connection elif self.zone_variable == "tado mode": - if "tadoMode" in data: - self._state = data["tadoMode"] + self._state = self._tado_zone_data.tado_mode elif self.zone_variable == "overlay": - self._state = "overlay" in data and data["overlay"] is not None + self._state = self._tado_zone_data.overlay_active self._state_attributes = ( - {"termination": data["overlay"]["termination"]["type"]} - if self._state + {"termination": self._tado_zone_data.overlay_termination_type} + if self._tado_zone_data.overlay_active else {} ) elif self.zone_variable == "early start": - self._state = "preparation" in data and data["preparation"] is not None + self._state = self._tado_zone_data.preparation is not None elif self.zone_variable == "open window": - self._state = "openWindow" in data and data["openWindow"] is not None - self._state_attributes = data["openWindow"] if self._state else {} + self._state = self._tado_zone_data.open_window is not None + self._state_attributes = self._tado_zone_data.open_window_attr diff --git a/homeassistant/components/tado/tado_adapter.py b/homeassistant/components/tado/tado_adapter.py new file mode 100644 index 00000000000..211ff9baf84 --- /dev/null +++ b/homeassistant/components/tado/tado_adapter.py @@ -0,0 +1,285 @@ +"""Adapter to represent a tado zones and state.""" +import logging + +from homeassistant.components.climate.const import ( + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, +) + +from .const import ( + CONST_FAN_AUTO, + CONST_FAN_OFF, + CONST_LINK_OFFLINE, + CONST_MODE_OFF, + CONST_MODE_SMART_SCHEDULE, + DEFAULT_TADO_PRECISION, + TADO_MODES_TO_HA_CURRENT_HVAC_ACTION, +) + +_LOGGER = logging.getLogger(__name__) + + +class TadoZoneData: + """Represent a tado zone.""" + + def __init__(self, data, zone_id): + """Create a tado zone.""" + self._data = data + self._zone_id = zone_id + self._current_temp = None + self._connection = None + self._current_temp_timestamp = None + self._current_humidity = None + self._is_away = False + self._current_hvac_action = None + self._current_tado_fan_speed = None + self._current_tado_hvac_mode = None + self._target_temp = None + self._available = False + self._power = None + self._link = None + self._ac_power_timestamp = None + self._heating_power_timestamp = None + self._ac_power = None + self._heating_power = None + self._heating_power_percentage = None + self._tado_mode = None + self._overlay_active = None + self._overlay_termination_type = None + self._preparation = None + self._open_window = None + self._open_window_attr = None + self._precision = DEFAULT_TADO_PRECISION + self.update_data(data) + + @property + def preparation(self): + """Zone is preparing to heat.""" + return self._preparation + + @property + def open_window(self): + """Window is open.""" + return self._open_window + + @property + def open_window_attr(self): + """Window open attributes.""" + return self._open_window_attr + + @property + def current_temp(self): + """Temperature of the zone.""" + return self._current_temp + + @property + def current_temp_timestamp(self): + """Temperature of the zone timestamp.""" + return self._current_temp_timestamp + + @property + def connection(self): + """Up or down internet connection.""" + return self._connection + + @property + def tado_mode(self): + """Tado mode.""" + return self._tado_mode + + @property + def overlay_active(self): + """Overlay acitive.""" + return self._current_tado_hvac_mode != CONST_MODE_SMART_SCHEDULE + + @property + def overlay_termination_type(self): + """Overlay termination type (what happens when period ends).""" + return self._overlay_termination_type + + @property + def current_humidity(self): + """Humidity of the zone.""" + return self._current_humidity + + @property + def current_humidity_timestamp(self): + """Humidity of the zone timestamp.""" + return self._current_humidity_timestamp + + @property + def ac_power_timestamp(self): + """AC power timestamp.""" + return self._ac_power_timestamp + + @property + def heating_power_timestamp(self): + """Heating power timestamp.""" + return self._heating_power_timestamp + + @property + def ac_power(self): + """AC power.""" + return self._ac_power + + @property + def heating_power(self): + """Heating power.""" + return self._heating_power + + @property + def heating_power_percentage(self): + """Heating power percentage.""" + return self._heating_power_percentage + + @property + def is_away(self): + """Is Away (not home).""" + return self._is_away + + @property + def power(self): + """Power is on.""" + return self._power + + @property + def current_hvac_action(self): + """HVAC Action (home assistant const).""" + return self._current_hvac_action + + @property + def current_tado_fan_speed(self): + """TADO Fan speed (tado const).""" + return self._current_tado_fan_speed + + @property + def link(self): + """Link (internet connection state).""" + return self._link + + @property + def precision(self): + """Precision of temp units.""" + return self._precision + + @property + def current_tado_hvac_mode(self): + """TADO HVAC Mode (tado const).""" + return self._current_tado_hvac_mode + + @property + def target_temp(self): + """Target temperature (C).""" + return self._target_temp + + @property + def available(self): + """Device is available and link is up.""" + return self._available + + def update_data(self, data): + """Handle update callbacks.""" + _LOGGER.debug("Updating climate platform for zone %d", self._zone_id) + if "sensorDataPoints" in data: + sensor_data = data["sensorDataPoints"] + + if "insideTemperature" in sensor_data: + temperature = float(sensor_data["insideTemperature"]["celsius"]) + self._current_temp = temperature + self._current_temp_timestamp = sensor_data["insideTemperature"][ + "timestamp" + ] + if "precision" in sensor_data["insideTemperature"]: + self._precision = sensor_data["insideTemperature"]["precision"][ + "celsius" + ] + + if "humidity" in sensor_data: + humidity = float(sensor_data["humidity"]["percentage"]) + self._current_humidity = humidity + self._current_humidity_timestamp = sensor_data["humidity"]["timestamp"] + + self._is_away = None + self._tado_mode = None + if "tadoMode" in data: + self._is_away = data["tadoMode"] == "AWAY" + self._tado_mode = data["tadoMode"] + + self._link = None + if "link" in data: + self._link = data["link"]["state"] + + self._current_hvac_action = CURRENT_HVAC_OFF + + if "setting" in data: + # temperature setting will not exist when device is off + if ( + "temperature" in data["setting"] + and data["setting"]["temperature"] is not None + ): + setting = float(data["setting"]["temperature"]["celsius"]) + self._target_temp = setting + + setting = data["setting"] + + self._current_tado_fan_speed = CONST_FAN_OFF + # If there is no overlay, the mode will always be + # "SMART_SCHEDULE" + if "mode" in setting: + self._current_tado_hvac_mode = setting["mode"] + else: + self._current_tado_hvac_mode = CONST_MODE_OFF + + self._power = setting["power"] + if self._power == "ON": + # Not all devices have fans + self._current_tado_fan_speed = setting.get("fanSpeed", CONST_FAN_AUTO) + self._current_hvac_action = CURRENT_HVAC_IDLE + + self._preparation = "preparation" in data and data["preparation"] is not None + self._open_window = "openWindow" in data and data["openWindow"] is not None + self._open_window_attr = data["openWindow"] if self._open_window else {} + + if "activityDataPoints" in data: + activity_data = data["activityDataPoints"] + if "acPower" in activity_data and activity_data["acPower"] is not None: + self._ac_power = activity_data["acPower"]["value"] + self._ac_power_timestamp = activity_data["acPower"]["timestamp"] + if activity_data["acPower"]["value"] == "ON" and self._power == "ON": + # acPower means the unit has power so we need to map the mode + self._current_hvac_action = TADO_MODES_TO_HA_CURRENT_HVAC_ACTION.get( + self._current_tado_hvac_mode, CURRENT_HVAC_COOL + ) + if ( + "heatingPower" in activity_data + and activity_data["heatingPower"] is not None + ): + self._heating_power = activity_data["heatingPower"].get("value", None) + self._heating_power_timestamp = activity_data["heatingPower"][ + "timestamp" + ] + self._heating_power_percentage = float( + activity_data["heatingPower"].get("percentage", 0) + ) + + if self._heating_power_percentage > 0.0 and self._power == "ON": + self._current_hvac_action = CURRENT_HVAC_HEAT + + # If there is no overlay + # then we are running the smart schedule + self._overlay_termination_type = None + if "overlay" in data and data["overlay"] is not None: + if ( + "termination" in data["overlay"] + and "type" in data["overlay"]["termination"] + ): + self._overlay_termination_type = data["overlay"]["termination"]["type"] + else: + self._current_tado_hvac_mode = CONST_MODE_SMART_SCHEDULE + + self._connection = ( + data["connectionState"]["value"] if "connectionState" in data else None + ) + self._available = self._link != CONST_LINK_OFFLINE diff --git a/homeassistant/components/tado/water_heater.py b/homeassistant/components/tado/water_heater.py index fc3a9ce9cf4..52c085d8ec3 100644 --- a/homeassistant/components/tado/water_heater.py +++ b/homeassistant/components/tado/water_heater.py @@ -12,6 +12,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED from .const import ( + CONST_MODE_HEAT, CONST_MODE_OFF, CONST_MODE_SMART_SCHEDULE, CONST_OVERLAY_MANUAL, @@ -51,14 +52,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for tado in api_list: for zone in tado.zones: if zone["type"] in [TYPE_HOT_WATER]: - entity = create_water_heater_entity(tado, zone["name"], zone["id"]) + entity = create_water_heater_entity( + hass, tado, zone["name"], zone["id"] + ) entities.append(entity) if entities: add_entities(entities, True) -def create_water_heater_entity(tado, name: str, zone_id: int): +def create_water_heater_entity(hass, tado, name: str, zone_id: int): """Create a Tado water heater device.""" capabilities = tado.get_capabilities(zone_id) supports_temperature_control = capabilities["canSetTemperature"] @@ -72,7 +75,7 @@ def create_water_heater_entity(tado, name: str, zone_id: int): max_temp = None entity = TadoWaterHeater( - tado, name, zone_id, supports_temperature_control, min_temp, max_temp + hass, tado, name, zone_id, supports_temperature_control, min_temp, max_temp ) return entity @@ -83,6 +86,7 @@ class TadoWaterHeater(WaterHeaterDevice): def __init__( self, + hass, tado, zone_name, zone_id, @@ -91,6 +95,7 @@ class TadoWaterHeater(WaterHeaterDevice): max_temp, ): """Initialize of Tado water heater entity.""" + self.hass = hass self._tado = tado self.zone_name = zone_name @@ -110,28 +115,17 @@ class TadoWaterHeater(WaterHeaterDevice): if self._supports_temperature_control: self._supported_features |= SUPPORT_TARGET_TEMPERATURE - if tado.fallback: - # Fallback to Smart Schedule at next Schedule switch - self._default_overlay = CONST_OVERLAY_TADO_MODE - else: - # Don't fallback to Smart Schedule, but keep in manual mode - self._default_overlay = CONST_OVERLAY_MANUAL - - self._current_operation = CONST_MODE_SMART_SCHEDULE + self._current_tado_heat_mode = CONST_MODE_SMART_SCHEDULE self._overlay_mode = CONST_MODE_SMART_SCHEDULE + self._async_update_data() async def async_added_to_hass(self): """Register for sensor updates.""" - @callback - def async_update_callback(): - """Schedule an entity update.""" - self.async_schedule_update_ha_state(True) - async_dispatcher_connect( self.hass, SIGNAL_TADO_UPDATE_RECEIVED.format("zone", self.zone_id), - async_update_callback, + self._async_update_callback, ) @property @@ -157,7 +151,7 @@ class TadoWaterHeater(WaterHeaterDevice): @property def current_operation(self): """Return current readable operation mode.""" - return WATER_HEATER_MAP_TADO.get(self._current_operation) + return WATER_HEATER_MAP_TADO.get(self._current_tado_heat_mode) @property def target_temperature(self): @@ -198,16 +192,9 @@ class TadoWaterHeater(WaterHeaterDevice): elif operation_mode == MODE_AUTO: mode = CONST_MODE_SMART_SCHEDULE elif operation_mode == MODE_HEAT: - mode = self._default_overlay + mode = CONST_MODE_HEAT - self._current_operation = mode - self._overlay_mode = None - - # Set a target temperature if we don't have any - if mode == CONST_OVERLAY_TADO_MODE and self._target_temp is None: - self._target_temp = self.min_temp - - self._control_heater() + self._control_heater(heat_mode=mode) def set_temperature(self, **kwargs): """Set new target temperature.""" @@ -215,13 +202,17 @@ class TadoWaterHeater(WaterHeaterDevice): if not self._supports_temperature_control or temperature is None: return - self._current_operation = self._default_overlay - self._overlay_mode = None - self._target_temp = temperature - self._control_heater() + self._control_heater(target_temp=temperature) - def update(self): - """Handle update callbacks.""" + @callback + def _async_update_callback(self): + """Load tado data and update state.""" + self._async_update_data() + self.async_write_ha_state() + + @callback + def _async_update_data(self): + """Load tado data.""" _LOGGER.debug("Updating water_heater platform for zone %d", self.zone_id) data = self._tado.data["zone"][self.zone_id] @@ -232,71 +223,70 @@ class TadoWaterHeater(WaterHeaterDevice): if "setting" in data: power = data["setting"]["power"] if power == "OFF": - self._current_operation = CONST_MODE_OFF - # There is no overlay, the mode will always be - # "SMART_SCHEDULE" - self._overlay_mode = CONST_MODE_SMART_SCHEDULE - self._device_is_active = False + self._current_tado_heat_mode = CONST_MODE_OFF else: - self._device_is_active = True + self._current_tado_heat_mode = CONST_MODE_HEAT # temperature setting will not exist when device is off if ( "temperature" in data["setting"] and data["setting"]["temperature"] is not None ): - setting = float(data["setting"]["temperature"]["celsius"]) - self._target_temp = setting + self._target_temp = float(data["setting"]["temperature"]["celsius"]) - overlay = False - overlay_data = None - termination = CONST_MODE_SMART_SCHEDULE + # If there is no overlay + # then we are running the smart schedule + if "overlay" in data and data["overlay"] is None: + self._current_tado_heat_mode = CONST_MODE_SMART_SCHEDULE - if "overlay" in data: - overlay_data = data["overlay"] - overlay = overlay_data is not None + self.async_write_ha_state() - if overlay: - termination = overlay_data["termination"]["type"] - - if self._device_is_active: - # If you set mode manually to off, there will be an overlay - # and a termination, but we want to see the mode "OFF" - self._overlay_mode = termination - self._current_operation = termination - - def _control_heater(self): + def _control_heater(self, heat_mode=None, target_temp=None): """Send new target temperature.""" - if self._current_operation == CONST_MODE_SMART_SCHEDULE: + + if heat_mode: + self._current_tado_heat_mode = heat_mode + + if target_temp: + self._target_temp = target_temp + + # Set a target temperature if we don't have any + if self._target_temp is None: + self._target_temp = self.min_temp + + if self._current_tado_heat_mode == CONST_MODE_SMART_SCHEDULE: _LOGGER.debug( "Switching to SMART_SCHEDULE for zone %s (%d)", self.zone_name, self.zone_id, ) self._tado.reset_zone_overlay(self.zone_id) - self._overlay_mode = self._current_operation return - if self._current_operation == CONST_MODE_OFF: + if self._current_tado_heat_mode == CONST_MODE_OFF: _LOGGER.debug( "Switching to OFF for zone %s (%d)", self.zone_name, self.zone_id ) self._tado.set_zone_off(self.zone_id, CONST_OVERLAY_MANUAL, TYPE_HOT_WATER) - self._overlay_mode = self._current_operation return + # Fallback to Smart Schedule at next Schedule switch if we have fallback enabled + overlay_mode = ( + CONST_OVERLAY_TADO_MODE if self._tado.fallback else CONST_OVERLAY_MANUAL + ) + _LOGGER.debug( "Switching to %s for zone %s (%d) with temperature %s", - self._current_operation, + self._current_tado_heat_mode, self.zone_name, self.zone_id, self._target_temp, ) self._tado.set_zone_overlay( - self.zone_id, - self._current_operation, - self._target_temp, - None, - TYPE_HOT_WATER, + zone_id=self.zone_id, + overlay_mode=overlay_mode, + temperature=self._target_temp, + duration=None, + device_type=TYPE_HOT_WATER, ) - self._overlay_mode = self._current_operation + self._overlay_mode = self._current_tado_heat_mode diff --git a/requirements_all.txt b/requirements_all.txt index acfebe8c4d0..5a8199f2c14 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1650,7 +1650,7 @@ python-songpal==0.11.2 python-synology==0.4.0 # homeassistant.components.tado -python-tado==0.3.0 +python-tado==0.4.0 # homeassistant.components.telegram_bot python-telegram-bot==11.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 54ecf61d031..54732560fe9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -580,6 +580,9 @@ python-miio==0.4.8 # homeassistant.components.nest python-nest==4.1.0 +# homeassistant.components.tado +python-tado==0.4.0 + # homeassistant.components.twitch python-twitch-client==0.6.0 diff --git a/tests/components/tado/mocks.py b/tests/components/tado/mocks.py new file mode 100644 index 00000000000..149bcbc24c6 --- /dev/null +++ b/tests/components/tado/mocks.py @@ -0,0 +1,18 @@ +"""Mocks for the tado component.""" +import json +import os + +from homeassistant.components.tado.tado_adapter import TadoZoneData + +from tests.common import load_fixture + + +async def _mock_tado_climate_zone_from_fixture(hass, file): + return TadoZoneData(await _load_json_fixture(hass, file), 1) + + +async def _load_json_fixture(hass, path): + fixture = await hass.async_add_executor_job( + load_fixture, os.path.join("tado", path) + ) + return json.loads(fixture) diff --git a/tests/components/tado/test_tado_adapter.py b/tests/components/tado/test_tado_adapter.py new file mode 100644 index 00000000000..ccbf2291b7f --- /dev/null +++ b/tests/components/tado/test_tado_adapter.py @@ -0,0 +1,423 @@ +"""The tado_adapter tests for the tado platform.""" + + +from tests.components.tado.mocks import _mock_tado_climate_zone_from_fixture + + +async def test_ac_issue_32294_heat_mode(hass): + """Test smart ac cool mode.""" + ac_issue_32294_heat_mode = await _mock_tado_climate_zone_from_fixture( + hass, "ac_issue_32294.heat_mode.json" + ) + assert ac_issue_32294_heat_mode.preparation is False + assert ac_issue_32294_heat_mode.open_window is False + assert ac_issue_32294_heat_mode.open_window_attr == {} + assert ac_issue_32294_heat_mode.current_temp == 21.82 + assert ac_issue_32294_heat_mode.current_temp_timestamp == "2020-02-29T22:51:05.016Z" + assert ac_issue_32294_heat_mode.connection is None + assert ac_issue_32294_heat_mode.tado_mode == "HOME" + assert ac_issue_32294_heat_mode.overlay_active is False + assert ac_issue_32294_heat_mode.overlay_termination_type is None + assert ac_issue_32294_heat_mode.current_humidity == 40.4 + assert ( + ac_issue_32294_heat_mode.current_humidity_timestamp + == "2020-02-29T22:51:05.016Z" + ) + assert ac_issue_32294_heat_mode.ac_power_timestamp == "2020-02-29T22:50:34.850Z" + assert ac_issue_32294_heat_mode.heating_power_timestamp is None + assert ac_issue_32294_heat_mode.ac_power == "ON" + assert ac_issue_32294_heat_mode.heating_power is None + assert ac_issue_32294_heat_mode.heating_power_percentage is None + assert ac_issue_32294_heat_mode.is_away is False + assert ac_issue_32294_heat_mode.power == "ON" + assert ac_issue_32294_heat_mode.current_hvac_action == "heating" + assert ac_issue_32294_heat_mode.current_tado_fan_speed == "AUTO" + assert ac_issue_32294_heat_mode.link == "ONLINE" + assert ac_issue_32294_heat_mode.current_tado_hvac_mode == "SMART_SCHEDULE" + assert ac_issue_32294_heat_mode.target_temp == 25.0 + assert ac_issue_32294_heat_mode.available is True + assert ac_issue_32294_heat_mode.precision == 0.1 + + +async def test_smartac3_smart_mode(hass): + """Test smart ac smart mode.""" + smartac3_smart_mode = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.smart_mode.json" + ) + assert smartac3_smart_mode.preparation is False + assert smartac3_smart_mode.open_window is False + assert smartac3_smart_mode.open_window_attr == {} + assert smartac3_smart_mode.current_temp == 24.43 + assert smartac3_smart_mode.current_temp_timestamp == "2020-03-05T03:50:24.769Z" + assert smartac3_smart_mode.connection is None + assert smartac3_smart_mode.tado_mode == "HOME" + assert smartac3_smart_mode.overlay_active is False + assert smartac3_smart_mode.overlay_termination_type is None + assert smartac3_smart_mode.current_humidity == 60.0 + assert smartac3_smart_mode.current_humidity_timestamp == "2020-03-05T03:50:24.769Z" + assert smartac3_smart_mode.ac_power_timestamp == "2020-03-05T03:52:22.253Z" + assert smartac3_smart_mode.heating_power_timestamp is None + assert smartac3_smart_mode.ac_power == "OFF" + assert smartac3_smart_mode.heating_power is None + assert smartac3_smart_mode.heating_power_percentage is None + assert smartac3_smart_mode.is_away is False + assert smartac3_smart_mode.power == "ON" + assert smartac3_smart_mode.current_hvac_action == "idle" + assert smartac3_smart_mode.current_tado_fan_speed == "MIDDLE" + assert smartac3_smart_mode.link == "ONLINE" + assert smartac3_smart_mode.current_tado_hvac_mode == "SMART_SCHEDULE" + assert smartac3_smart_mode.target_temp == 20.0 + assert smartac3_smart_mode.available is True + assert smartac3_smart_mode.precision == 0.1 + + +async def test_smartac3_cool_mode(hass): + """Test smart ac cool mode.""" + smartac3_cool_mode = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.cool_mode.json" + ) + assert smartac3_cool_mode.preparation is False + assert smartac3_cool_mode.open_window is False + assert smartac3_cool_mode.open_window_attr == {} + assert smartac3_cool_mode.current_temp == 24.76 + assert smartac3_cool_mode.current_temp_timestamp == "2020-03-05T03:57:38.850Z" + assert smartac3_cool_mode.connection is None + assert smartac3_cool_mode.tado_mode == "HOME" + assert smartac3_cool_mode.overlay_active is True + assert smartac3_cool_mode.overlay_termination_type == "TADO_MODE" + assert smartac3_cool_mode.current_humidity == 60.9 + assert smartac3_cool_mode.current_humidity_timestamp == "2020-03-05T03:57:38.850Z" + assert smartac3_cool_mode.ac_power_timestamp == "2020-03-05T04:01:07.162Z" + assert smartac3_cool_mode.heating_power_timestamp is None + assert smartac3_cool_mode.ac_power == "ON" + assert smartac3_cool_mode.heating_power is None + assert smartac3_cool_mode.heating_power_percentage is None + assert smartac3_cool_mode.is_away is False + assert smartac3_cool_mode.power == "ON" + assert smartac3_cool_mode.current_hvac_action == "cooling" + assert smartac3_cool_mode.current_tado_fan_speed == "AUTO" + assert smartac3_cool_mode.link == "ONLINE" + assert smartac3_cool_mode.current_tado_hvac_mode == "COOL" + assert smartac3_cool_mode.target_temp == 17.78 + assert smartac3_cool_mode.available is True + assert smartac3_cool_mode.precision == 0.1 + + +async def test_smartac3_auto_mode(hass): + """Test smart ac cool mode.""" + smartac3_auto_mode = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.auto_mode.json" + ) + assert smartac3_auto_mode.preparation is False + assert smartac3_auto_mode.open_window is False + assert smartac3_auto_mode.open_window_attr == {} + assert smartac3_auto_mode.current_temp == 24.8 + assert smartac3_auto_mode.current_temp_timestamp == "2020-03-05T03:55:38.160Z" + assert smartac3_auto_mode.connection is None + assert smartac3_auto_mode.tado_mode == "HOME" + assert smartac3_auto_mode.overlay_active is True + assert smartac3_auto_mode.overlay_termination_type == "TADO_MODE" + assert smartac3_auto_mode.current_humidity == 62.5 + assert smartac3_auto_mode.current_humidity_timestamp == "2020-03-05T03:55:38.160Z" + assert smartac3_auto_mode.ac_power_timestamp == "2020-03-05T03:56:38.627Z" + assert smartac3_auto_mode.heating_power_timestamp is None + assert smartac3_auto_mode.ac_power == "ON" + assert smartac3_auto_mode.heating_power is None + assert smartac3_auto_mode.heating_power_percentage is None + assert smartac3_auto_mode.is_away is False + assert smartac3_auto_mode.power == "ON" + assert smartac3_auto_mode.current_hvac_action == "cooling" + assert smartac3_auto_mode.current_tado_fan_speed == "AUTO" + assert smartac3_auto_mode.link == "ONLINE" + assert smartac3_auto_mode.current_tado_hvac_mode == "AUTO" + assert smartac3_auto_mode.target_temp is None + assert smartac3_auto_mode.available is True + assert smartac3_auto_mode.precision == 0.1 + + +async def test_smartac3_dry_mode(hass): + """Test smart ac cool mode.""" + smartac3_dry_mode = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.dry_mode.json" + ) + assert smartac3_dry_mode.preparation is False + assert smartac3_dry_mode.open_window is False + assert smartac3_dry_mode.open_window_attr == {} + assert smartac3_dry_mode.current_temp == 25.01 + assert smartac3_dry_mode.current_temp_timestamp == "2020-03-05T04:02:07.396Z" + assert smartac3_dry_mode.connection is None + assert smartac3_dry_mode.tado_mode == "HOME" + assert smartac3_dry_mode.overlay_active is True + assert smartac3_dry_mode.overlay_termination_type == "TADO_MODE" + assert smartac3_dry_mode.current_humidity == 62.0 + assert smartac3_dry_mode.current_humidity_timestamp == "2020-03-05T04:02:07.396Z" + assert smartac3_dry_mode.ac_power_timestamp == "2020-03-05T04:02:40.867Z" + assert smartac3_dry_mode.heating_power_timestamp is None + assert smartac3_dry_mode.ac_power == "ON" + assert smartac3_dry_mode.heating_power is None + assert smartac3_dry_mode.heating_power_percentage is None + assert smartac3_dry_mode.is_away is False + assert smartac3_dry_mode.power == "ON" + assert smartac3_dry_mode.current_hvac_action == "drying" + assert smartac3_dry_mode.current_tado_fan_speed == "AUTO" + assert smartac3_dry_mode.link == "ONLINE" + assert smartac3_dry_mode.current_tado_hvac_mode == "DRY" + assert smartac3_dry_mode.target_temp is None + assert smartac3_dry_mode.available is True + assert smartac3_dry_mode.precision == 0.1 + + +async def test_smartac3_fan_mode(hass): + """Test smart ac cool mode.""" + smartac3_fan_mode = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.fan_mode.json" + ) + assert smartac3_fan_mode.preparation is False + assert smartac3_fan_mode.open_window is False + assert smartac3_fan_mode.open_window_attr == {} + assert smartac3_fan_mode.current_temp == 25.01 + assert smartac3_fan_mode.current_temp_timestamp == "2020-03-05T04:02:07.396Z" + assert smartac3_fan_mode.connection is None + assert smartac3_fan_mode.tado_mode == "HOME" + assert smartac3_fan_mode.overlay_active is True + assert smartac3_fan_mode.overlay_termination_type == "TADO_MODE" + assert smartac3_fan_mode.current_humidity == 62.0 + assert smartac3_fan_mode.current_humidity_timestamp == "2020-03-05T04:02:07.396Z" + assert smartac3_fan_mode.ac_power_timestamp == "2020-03-05T04:03:44.328Z" + assert smartac3_fan_mode.heating_power_timestamp is None + assert smartac3_fan_mode.ac_power == "ON" + assert smartac3_fan_mode.heating_power is None + assert smartac3_fan_mode.heating_power_percentage is None + assert smartac3_fan_mode.is_away is False + assert smartac3_fan_mode.power == "ON" + assert smartac3_fan_mode.current_hvac_action == "fan" + assert smartac3_fan_mode.current_tado_fan_speed == "AUTO" + assert smartac3_fan_mode.link == "ONLINE" + assert smartac3_fan_mode.current_tado_hvac_mode == "FAN" + assert smartac3_fan_mode.target_temp is None + assert smartac3_fan_mode.available is True + assert smartac3_fan_mode.precision == 0.1 + + +async def test_smartac3_heat_mode(hass): + """Test smart ac cool mode.""" + smartac3_heat_mode = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.heat_mode.json" + ) + assert smartac3_heat_mode.preparation is False + assert smartac3_heat_mode.open_window is False + assert smartac3_heat_mode.open_window_attr == {} + assert smartac3_heat_mode.current_temp == 24.76 + assert smartac3_heat_mode.current_temp_timestamp == "2020-03-05T03:57:38.850Z" + assert smartac3_heat_mode.connection is None + assert smartac3_heat_mode.tado_mode == "HOME" + assert smartac3_heat_mode.overlay_active is True + assert smartac3_heat_mode.overlay_termination_type == "TADO_MODE" + assert smartac3_heat_mode.current_humidity == 60.9 + assert smartac3_heat_mode.current_humidity_timestamp == "2020-03-05T03:57:38.850Z" + assert smartac3_heat_mode.ac_power_timestamp == "2020-03-05T03:59:36.390Z" + assert smartac3_heat_mode.heating_power_timestamp is None + assert smartac3_heat_mode.ac_power == "ON" + assert smartac3_heat_mode.heating_power is None + assert smartac3_heat_mode.heating_power_percentage is None + assert smartac3_heat_mode.is_away is False + assert smartac3_heat_mode.power == "ON" + assert smartac3_heat_mode.current_hvac_action == "heating" + assert smartac3_heat_mode.current_tado_fan_speed == "AUTO" + assert smartac3_heat_mode.link == "ONLINE" + assert smartac3_heat_mode.current_tado_hvac_mode == "HEAT" + assert smartac3_heat_mode.target_temp == 16.11 + assert smartac3_heat_mode.available is True + assert smartac3_heat_mode.precision == 0.1 + + +async def test_smartac3_hvac_off(hass): + """Test smart ac cool mode.""" + smartac3_hvac_off = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.hvac_off.json" + ) + assert smartac3_hvac_off.preparation is False + assert smartac3_hvac_off.open_window is False + assert smartac3_hvac_off.open_window_attr == {} + assert smartac3_hvac_off.current_temp == 21.44 + assert smartac3_hvac_off.current_temp_timestamp == "2020-03-05T01:21:44.089Z" + assert smartac3_hvac_off.connection is None + assert smartac3_hvac_off.tado_mode == "AWAY" + assert smartac3_hvac_off.overlay_active is True + assert smartac3_hvac_off.overlay_termination_type == "MANUAL" + assert smartac3_hvac_off.current_humidity == 48.2 + assert smartac3_hvac_off.current_humidity_timestamp == "2020-03-05T01:21:44.089Z" + assert smartac3_hvac_off.ac_power_timestamp == "2020-02-29T05:34:10.318Z" + assert smartac3_hvac_off.heating_power_timestamp is None + assert smartac3_hvac_off.ac_power == "OFF" + assert smartac3_hvac_off.heating_power is None + assert smartac3_hvac_off.heating_power_percentage is None + assert smartac3_hvac_off.is_away is True + assert smartac3_hvac_off.power == "OFF" + assert smartac3_hvac_off.current_hvac_action == "off" + assert smartac3_hvac_off.current_tado_fan_speed == "OFF" + assert smartac3_hvac_off.link == "ONLINE" + assert smartac3_hvac_off.current_tado_hvac_mode == "OFF" + assert smartac3_hvac_off.target_temp is None + assert smartac3_hvac_off.available is True + assert smartac3_hvac_off.precision == 0.1 + + +async def test_smartac3_manual_off(hass): + """Test smart ac cool mode.""" + smartac3_manual_off = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.manual_off.json" + ) + assert smartac3_manual_off.preparation is False + assert smartac3_manual_off.open_window is False + assert smartac3_manual_off.open_window_attr == {} + assert smartac3_manual_off.current_temp == 25.01 + assert smartac3_manual_off.current_temp_timestamp == "2020-03-05T04:02:07.396Z" + assert smartac3_manual_off.connection is None + assert smartac3_manual_off.tado_mode == "HOME" + assert smartac3_manual_off.overlay_active is True + assert smartac3_manual_off.overlay_termination_type == "MANUAL" + assert smartac3_manual_off.current_humidity == 62.0 + assert smartac3_manual_off.current_humidity_timestamp == "2020-03-05T04:02:07.396Z" + assert smartac3_manual_off.ac_power_timestamp == "2020-03-05T04:05:08.804Z" + assert smartac3_manual_off.heating_power_timestamp is None + assert smartac3_manual_off.ac_power == "OFF" + assert smartac3_manual_off.heating_power is None + assert smartac3_manual_off.heating_power_percentage is None + assert smartac3_manual_off.is_away is False + assert smartac3_manual_off.power == "OFF" + assert smartac3_manual_off.current_hvac_action == "off" + assert smartac3_manual_off.current_tado_fan_speed == "OFF" + assert smartac3_manual_off.link == "ONLINE" + assert smartac3_manual_off.current_tado_hvac_mode == "OFF" + assert smartac3_manual_off.target_temp is None + assert smartac3_manual_off.available is True + assert smartac3_manual_off.precision == 0.1 + + +async def test_smartac3_offline(hass): + """Test smart ac cool mode.""" + smartac3_offline = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.offline.json" + ) + assert smartac3_offline.preparation is False + assert smartac3_offline.open_window is False + assert smartac3_offline.open_window_attr == {} + assert smartac3_offline.current_temp == 25.05 + assert smartac3_offline.current_temp_timestamp == "2020-03-03T21:23:57.846Z" + assert smartac3_offline.connection is None + assert smartac3_offline.tado_mode == "HOME" + assert smartac3_offline.overlay_active is True + assert smartac3_offline.overlay_termination_type == "TADO_MODE" + assert smartac3_offline.current_humidity == 61.6 + assert smartac3_offline.current_humidity_timestamp == "2020-03-03T21:23:57.846Z" + assert smartac3_offline.ac_power_timestamp == "2020-02-29T18:42:26.683Z" + assert smartac3_offline.heating_power_timestamp is None + assert smartac3_offline.ac_power == "OFF" + assert smartac3_offline.heating_power is None + assert smartac3_offline.heating_power_percentage is None + assert smartac3_offline.is_away is False + assert smartac3_offline.power == "ON" + assert smartac3_offline.current_hvac_action == "idle" + assert smartac3_offline.current_tado_fan_speed == "AUTO" + assert smartac3_offline.link == "OFFLINE" + assert smartac3_offline.current_tado_hvac_mode == "COOL" + assert smartac3_offline.target_temp == 17.78 + assert smartac3_offline.available is False + assert smartac3_offline.precision == 0.1 + + +async def test_hvac_action_heat(hass): + """Test smart ac cool mode.""" + hvac_action_heat = await _mock_tado_climate_zone_from_fixture( + hass, "hvac_action_heat.json" + ) + assert hvac_action_heat.preparation is False + assert hvac_action_heat.open_window is False + assert hvac_action_heat.open_window_attr == {} + assert hvac_action_heat.current_temp == 21.4 + assert hvac_action_heat.current_temp_timestamp == "2020-03-06T18:06:09.546Z" + assert hvac_action_heat.connection is None + assert hvac_action_heat.tado_mode == "HOME" + assert hvac_action_heat.overlay_active is True + assert hvac_action_heat.overlay_termination_type == "TADO_MODE" + assert hvac_action_heat.current_humidity == 50.4 + assert hvac_action_heat.current_humidity_timestamp == "2020-03-06T18:06:09.546Z" + assert hvac_action_heat.ac_power_timestamp == "2020-03-06T17:38:30.302Z" + assert hvac_action_heat.heating_power_timestamp is None + assert hvac_action_heat.ac_power == "OFF" + assert hvac_action_heat.heating_power is None + assert hvac_action_heat.heating_power_percentage is None + assert hvac_action_heat.is_away is False + assert hvac_action_heat.power == "ON" + assert hvac_action_heat.current_hvac_action == "idle" + assert hvac_action_heat.current_tado_fan_speed == "AUTO" + assert hvac_action_heat.link == "ONLINE" + assert hvac_action_heat.current_tado_hvac_mode == "HEAT" + assert hvac_action_heat.target_temp == 16.11 + assert hvac_action_heat.available is True + assert hvac_action_heat.precision == 0.1 + + +async def test_smartac3_turning_off(hass): + """Test smart ac cool mode.""" + smartac3_turning_off = await _mock_tado_climate_zone_from_fixture( + hass, "smartac3.turning_off.json" + ) + assert smartac3_turning_off.preparation is False + assert smartac3_turning_off.open_window is False + assert smartac3_turning_off.open_window_attr == {} + assert smartac3_turning_off.current_temp == 21.4 + assert smartac3_turning_off.current_temp_timestamp == "2020-03-06T19:06:13.185Z" + assert smartac3_turning_off.connection is None + assert smartac3_turning_off.tado_mode == "HOME" + assert smartac3_turning_off.overlay_active is True + assert smartac3_turning_off.overlay_termination_type == "MANUAL" + assert smartac3_turning_off.current_humidity == 49.2 + assert smartac3_turning_off.current_humidity_timestamp == "2020-03-06T19:06:13.185Z" + assert smartac3_turning_off.ac_power_timestamp == "2020-03-06T19:05:21.835Z" + assert smartac3_turning_off.heating_power_timestamp is None + assert smartac3_turning_off.ac_power == "ON" + assert smartac3_turning_off.heating_power is None + assert smartac3_turning_off.heating_power_percentage is None + assert smartac3_turning_off.is_away is False + assert smartac3_turning_off.power == "OFF" + assert smartac3_turning_off.current_hvac_action == "off" + assert smartac3_turning_off.current_tado_fan_speed == "OFF" + assert smartac3_turning_off.link == "ONLINE" + assert smartac3_turning_off.current_tado_hvac_mode == "OFF" + assert smartac3_turning_off.target_temp is None + assert smartac3_turning_off.available is True + assert smartac3_turning_off.precision == 0.1 + + +async def test_michael_heat_mode(hass): + """Test michael's tado.""" + michael_heat_mode = await _mock_tado_climate_zone_from_fixture( + hass, "michael_heat_mode.json" + ) + assert michael_heat_mode.preparation is False + assert michael_heat_mode.open_window is False + assert michael_heat_mode.open_window_attr == {} + assert michael_heat_mode.current_temp == 20.06 + assert michael_heat_mode.current_temp_timestamp == "2020-03-09T08:16:49.271Z" + assert michael_heat_mode.connection is None + assert michael_heat_mode.tado_mode == "HOME" + assert michael_heat_mode.overlay_active is False + assert michael_heat_mode.overlay_termination_type is None + assert michael_heat_mode.current_humidity == 41.8 + assert michael_heat_mode.current_humidity_timestamp == "2020-03-09T08:16:49.271Z" + assert michael_heat_mode.ac_power_timestamp is None + assert michael_heat_mode.heating_power_timestamp == "2020-03-09T08:20:47.299Z" + assert michael_heat_mode.ac_power is None + assert michael_heat_mode.heating_power is None + assert michael_heat_mode.heating_power_percentage == 0.0 + assert michael_heat_mode.is_away is False + assert michael_heat_mode.power == "ON" + assert michael_heat_mode.current_hvac_action == "idle" + assert michael_heat_mode.current_tado_fan_speed == "AUTO" + assert michael_heat_mode.link == "ONLINE" + assert michael_heat_mode.current_tado_hvac_mode == "SMART_SCHEDULE" + assert michael_heat_mode.target_temp == 20.0 + assert michael_heat_mode.available is True + assert michael_heat_mode.precision == 0.1 diff --git a/tests/fixtures/tado/ac_issue_32294.heat_mode.json b/tests/fixtures/tado/ac_issue_32294.heat_mode.json new file mode 100644 index 00000000000..098afd018aa --- /dev/null +++ b/tests/fixtures/tado/ac_issue_32294.heat_mode.json @@ -0,0 +1,60 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 71.28, + "timestamp": "2020-02-29T22:51:05.016Z", + "celsius": 21.82, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-02-29T22:51:05.016Z", + "percentage": 40.4, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": null, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-02-29T22:50:34.850Z", + "type": "POWER", + "value": "ON" + } + }, + "nextTimeBlock": { + "start": "2020-03-01T00:00:00.000Z" + }, + "preparation": null, + "overlayType": null, + "nextScheduleChange": { + "start": "2020-03-01T00:00:00Z", + "setting": { + "type": "AIR_CONDITIONING", + "mode": "HEAT", + "power": "ON", + "temperature": { + "fahrenheit": 59.0, + "celsius": 15.0 + } + } + }, + "setting": { + "type": "AIR_CONDITIONING", + "mode": "HEAT", + "power": "ON", + "temperature": { + "fahrenheit": 77.0, + "celsius": 25.0 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/tado/hvac_action_heat.json b/tests/fixtures/tado/hvac_action_heat.json new file mode 100644 index 00000000000..9cbf1fd5f82 --- /dev/null +++ b/tests/fixtures/tado/hvac_action_heat.json @@ -0,0 +1,67 @@ +{ + "tadoMode": "HOME", + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "preparation": null, + "setting": { + "type": "AIR_CONDITIONING", + "power": "ON", + "mode": "HEAT", + "temperature": { + "celsius": 16.11, + "fahrenheit": 61.00 + }, + "fanSpeed": "AUTO" + }, + "overlayType": "MANUAL", + "overlay": { + "type": "MANUAL", + "setting": { + "type": "AIR_CONDITIONING", + "power": "ON", + "mode": "HEAT", + "temperature": { + "celsius": 16.11, + "fahrenheit": 61.00 + }, + "fanSpeed": "AUTO" + }, + "termination": { + "type": "TADO_MODE", + "typeSkillBasedApp": "TADO_MODE", + "projectedExpiry": null + } + }, + "openWindow": null, + "nextScheduleChange": null, + "nextTimeBlock": { + "start": "2020-03-07T04:00:00.000Z" + }, + "link": { + "state": "ONLINE" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-06T17:38:30.302Z", + "type": "POWER", + "value": "OFF" + } + }, + "sensorDataPoints": { + "insideTemperature": { + "celsius": 21.40, + "fahrenheit": 70.52, + "timestamp": "2020-03-06T18:06:09.546Z", + "type": "TEMPERATURE", + "precision": { + "celsius": 0.1, + "fahrenheit": 0.1 + } + }, + "humidity": { + "type": "PERCENTAGE", + "percentage": 50.40, + "timestamp": "2020-03-06T18:06:09.546Z" + } + } +} diff --git a/tests/fixtures/tado/michael_heat_mode.json b/tests/fixtures/tado/michael_heat_mode.json new file mode 100644 index 00000000000..958ead7635a --- /dev/null +++ b/tests/fixtures/tado/michael_heat_mode.json @@ -0,0 +1,58 @@ +{ + "tadoMode": "HOME", + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "preparation": null, + "setting": { + "type": "HEATING", + "power": "ON", + "temperature": { + "celsius": 20.0, + "fahrenheit": 68.0 + } + }, + "overlayType": null, + "overlay": null, + "openWindow": null, + "nextScheduleChange": { + "start": "2020-03-09T17:00:00Z", + "setting": { + "type": "HEATING", + "power": "ON", + "temperature": { + "celsius": 21.0, + "fahrenheit": 69.8 + } + } + }, + "nextTimeBlock": { + "start": "2020-03-09T17:00:00.000Z" + }, + "link": { + "state": "ONLINE" + }, + "activityDataPoints": { + "heatingPower": { + "type": "PERCENTAGE", + "percentage": 0.0, + "timestamp": "2020-03-09T08:20:47.299Z" + } + }, + "sensorDataPoints": { + "insideTemperature": { + "celsius": 20.06, + "fahrenheit": 68.11, + "timestamp": "2020-03-09T08:16:49.271Z", + "type": "TEMPERATURE", + "precision": { + "celsius": 0.1, + "fahrenheit": 0.1 + } + }, + "humidity": { + "type": "PERCENTAGE", + "percentage": 41.8, + "timestamp": "2020-03-09T08:16:49.271Z" + } + } +} diff --git a/tests/fixtures/tado/smartac3.auto_mode.json b/tests/fixtures/tado/smartac3.auto_mode.json new file mode 100644 index 00000000000..254b409ddd9 --- /dev/null +++ b/tests/fixtures/tado/smartac3.auto_mode.json @@ -0,0 +1,57 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 76.64, + "timestamp": "2020-03-05T03:55:38.160Z", + "celsius": 24.8, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-05T03:55:38.160Z", + "percentage": 62.5, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": { + "termination": { + "typeSkillBasedApp": "TADO_MODE", + "projectedExpiry": null, + "type": "TADO_MODE" + }, + "setting": { + "type": "AIR_CONDITIONING", + "mode": "AUTO", + "power": "ON" + }, + "type": "MANUAL" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-05T03:56:38.627Z", + "type": "POWER", + "value": "ON" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T08:00:00.000Z" + }, + "preparation": null, + "overlayType": "MANUAL", + "nextScheduleChange": null, + "setting": { + "type": "AIR_CONDITIONING", + "mode": "AUTO", + "power": "ON" + } +} \ No newline at end of file diff --git a/tests/fixtures/tado/smartac3.cool_mode.json b/tests/fixtures/tado/smartac3.cool_mode.json new file mode 100644 index 00000000000..a7db2cc75bc --- /dev/null +++ b/tests/fixtures/tado/smartac3.cool_mode.json @@ -0,0 +1,67 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 76.57, + "timestamp": "2020-03-05T03:57:38.850Z", + "celsius": 24.76, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-05T03:57:38.850Z", + "percentage": 60.9, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": { + "termination": { + "typeSkillBasedApp": "TADO_MODE", + "projectedExpiry": null, + "type": "TADO_MODE" + }, + "setting": { + "fanSpeed": "AUTO", + "type": "AIR_CONDITIONING", + "mode": "COOL", + "power": "ON", + "temperature": { + "fahrenheit": 64.0, + "celsius": 17.78 + } + }, + "type": "MANUAL" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-05T04:01:07.162Z", + "type": "POWER", + "value": "ON" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T08:00:00.000Z" + }, + "preparation": null, + "overlayType": "MANUAL", + "nextScheduleChange": null, + "setting": { + "fanSpeed": "AUTO", + "type": "AIR_CONDITIONING", + "mode": "COOL", + "power": "ON", + "temperature": { + "fahrenheit": 64.0, + "celsius": 17.78 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/tado/smartac3.dry_mode.json b/tests/fixtures/tado/smartac3.dry_mode.json new file mode 100644 index 00000000000..d04612d1105 --- /dev/null +++ b/tests/fixtures/tado/smartac3.dry_mode.json @@ -0,0 +1,57 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 77.02, + "timestamp": "2020-03-05T04:02:07.396Z", + "celsius": 25.01, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-05T04:02:07.396Z", + "percentage": 62.0, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": { + "termination": { + "typeSkillBasedApp": "TADO_MODE", + "projectedExpiry": null, + "type": "TADO_MODE" + }, + "setting": { + "type": "AIR_CONDITIONING", + "mode": "DRY", + "power": "ON" + }, + "type": "MANUAL" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-05T04:02:40.867Z", + "type": "POWER", + "value": "ON" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T08:00:00.000Z" + }, + "preparation": null, + "overlayType": "MANUAL", + "nextScheduleChange": null, + "setting": { + "type": "AIR_CONDITIONING", + "mode": "DRY", + "power": "ON" + } +} \ No newline at end of file diff --git a/tests/fixtures/tado/smartac3.fan_mode.json b/tests/fixtures/tado/smartac3.fan_mode.json new file mode 100644 index 00000000000..6907c31c517 --- /dev/null +++ b/tests/fixtures/tado/smartac3.fan_mode.json @@ -0,0 +1,57 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 77.02, + "timestamp": "2020-03-05T04:02:07.396Z", + "celsius": 25.01, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-05T04:02:07.396Z", + "percentage": 62.0, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": { + "termination": { + "typeSkillBasedApp": "TADO_MODE", + "projectedExpiry": null, + "type": "TADO_MODE" + }, + "setting": { + "type": "AIR_CONDITIONING", + "mode": "FAN", + "power": "ON" + }, + "type": "MANUAL" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-05T04:03:44.328Z", + "type": "POWER", + "value": "ON" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T08:00:00.000Z" + }, + "preparation": null, + "overlayType": "MANUAL", + "nextScheduleChange": null, + "setting": { + "type": "AIR_CONDITIONING", + "mode": "FAN", + "power": "ON" + } +} \ No newline at end of file diff --git a/tests/fixtures/tado/smartac3.heat_mode.json b/tests/fixtures/tado/smartac3.heat_mode.json new file mode 100644 index 00000000000..06b5a350d83 --- /dev/null +++ b/tests/fixtures/tado/smartac3.heat_mode.json @@ -0,0 +1,67 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 76.57, + "timestamp": "2020-03-05T03:57:38.850Z", + "celsius": 24.76, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-05T03:57:38.850Z", + "percentage": 60.9, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": { + "termination": { + "typeSkillBasedApp": "TADO_MODE", + "projectedExpiry": null, + "type": "TADO_MODE" + }, + "setting": { + "fanSpeed": "AUTO", + "type": "AIR_CONDITIONING", + "mode": "HEAT", + "power": "ON", + "temperature": { + "fahrenheit": 61.0, + "celsius": 16.11 + } + }, + "type": "MANUAL" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-05T03:59:36.390Z", + "type": "POWER", + "value": "ON" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T08:00:00.000Z" + }, + "preparation": null, + "overlayType": "MANUAL", + "nextScheduleChange": null, + "setting": { + "fanSpeed": "AUTO", + "type": "AIR_CONDITIONING", + "mode": "HEAT", + "power": "ON", + "temperature": { + "fahrenheit": 61.0, + "celsius": 16.11 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/tado/smartac3.hvac_off.json b/tests/fixtures/tado/smartac3.hvac_off.json new file mode 100644 index 00000000000..83e9d1a83d5 --- /dev/null +++ b/tests/fixtures/tado/smartac3.hvac_off.json @@ -0,0 +1,55 @@ +{ + "tadoMode": "AWAY", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 70.59, + "timestamp": "2020-03-05T01:21:44.089Z", + "celsius": 21.44, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-05T01:21:44.089Z", + "percentage": 48.2, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": { + "termination": { + "typeSkillBasedApp": "MANUAL", + "projectedExpiry": null, + "type": "MANUAL" + }, + "setting": { + "type": "AIR_CONDITIONING", + "power": "OFF" + }, + "type": "MANUAL" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-02-29T05:34:10.318Z", + "type": "POWER", + "value": "OFF" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T04:00:00.000Z" + }, + "preparation": null, + "overlayType": "MANUAL", + "nextScheduleChange": null, + "setting": { + "type": "AIR_CONDITIONING", + "power": "OFF" + } +} diff --git a/tests/fixtures/tado/smartac3.manual_off.json b/tests/fixtures/tado/smartac3.manual_off.json new file mode 100644 index 00000000000..a9538f30dbe --- /dev/null +++ b/tests/fixtures/tado/smartac3.manual_off.json @@ -0,0 +1,55 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 77.02, + "timestamp": "2020-03-05T04:02:07.396Z", + "celsius": 25.01, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-05T04:02:07.396Z", + "percentage": 62.0, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": { + "termination": { + "typeSkillBasedApp": "MANUAL", + "projectedExpiry": null, + "type": "MANUAL" + }, + "setting": { + "type": "AIR_CONDITIONING", + "power": "OFF" + }, + "type": "MANUAL" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-05T04:05:08.804Z", + "type": "POWER", + "value": "OFF" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T08:00:00.000Z" + }, + "preparation": null, + "overlayType": "MANUAL", + "nextScheduleChange": null, + "setting": { + "type": "AIR_CONDITIONING", + "power": "OFF" + } +} \ No newline at end of file diff --git a/tests/fixtures/tado/smartac3.offline.json b/tests/fixtures/tado/smartac3.offline.json new file mode 100644 index 00000000000..fda1e6468eb --- /dev/null +++ b/tests/fixtures/tado/smartac3.offline.json @@ -0,0 +1,71 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 77.09, + "timestamp": "2020-03-03T21:23:57.846Z", + "celsius": 25.05, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-03T21:23:57.846Z", + "percentage": 61.6, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "OFFLINE", + "reason": { + "code": "disconnectedDevice", + "title": "There is a disconnected device." + } + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": { + "termination": { + "typeSkillBasedApp": "TADO_MODE", + "projectedExpiry": null, + "type": "TADO_MODE" + }, + "setting": { + "fanSpeed": "AUTO", + "type": "AIR_CONDITIONING", + "mode": "COOL", + "power": "ON", + "temperature": { + "fahrenheit": 64.0, + "celsius": 17.78 + } + }, + "type": "MANUAL" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-02-29T18:42:26.683Z", + "type": "POWER", + "value": "OFF" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T08:00:00.000Z" + }, + "preparation": null, + "overlayType": "MANUAL", + "nextScheduleChange": null, + "setting": { + "fanSpeed": "AUTO", + "type": "AIR_CONDITIONING", + "mode": "COOL", + "power": "ON", + "temperature": { + "fahrenheit": 64.0, + "celsius": 17.78 + } + } +} diff --git a/tests/fixtures/tado/smartac3.smart_mode.json b/tests/fixtures/tado/smartac3.smart_mode.json new file mode 100644 index 00000000000..357a1a96658 --- /dev/null +++ b/tests/fixtures/tado/smartac3.smart_mode.json @@ -0,0 +1,50 @@ +{ + "tadoMode": "HOME", + "sensorDataPoints": { + "insideTemperature": { + "fahrenheit": 75.97, + "timestamp": "2020-03-05T03:50:24.769Z", + "celsius": 24.43, + "type": "TEMPERATURE", + "precision": { + "fahrenheit": 0.1, + "celsius": 0.1 + } + }, + "humidity": { + "timestamp": "2020-03-05T03:50:24.769Z", + "percentage": 60.0, + "type": "PERCENTAGE" + } + }, + "link": { + "state": "ONLINE" + }, + "openWindow": null, + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "overlay": null, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-05T03:52:22.253Z", + "type": "POWER", + "value": "OFF" + } + }, + "nextTimeBlock": { + "start": "2020-03-05T08:00:00.000Z" + }, + "preparation": null, + "overlayType": null, + "nextScheduleChange": null, + "setting": { + "fanSpeed": "MIDDLE", + "type": "AIR_CONDITIONING", + "mode": "COOL", + "power": "ON", + "temperature": { + "fahrenheit": 68.0, + "celsius": 20.0 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/tado/smartac3.turning_off.json b/tests/fixtures/tado/smartac3.turning_off.json new file mode 100644 index 00000000000..0c16f85811a --- /dev/null +++ b/tests/fixtures/tado/smartac3.turning_off.json @@ -0,0 +1,55 @@ +{ + "tadoMode": "HOME", + "geolocationOverride": false, + "geolocationOverrideDisableTime": null, + "preparation": null, + "setting": { + "type": "AIR_CONDITIONING", + "power": "OFF" + }, + "overlayType": "MANUAL", + "overlay": { + "type": "MANUAL", + "setting": { + "type": "AIR_CONDITIONING", + "power": "OFF" + }, + "termination": { + "type": "MANUAL", + "typeSkillBasedApp": "MANUAL", + "projectedExpiry": null + } + }, + "openWindow": null, + "nextScheduleChange": null, + "nextTimeBlock": { + "start": "2020-03-07T04:00:00.000Z" + }, + "link": { + "state": "ONLINE" + }, + "activityDataPoints": { + "acPower": { + "timestamp": "2020-03-06T19:05:21.835Z", + "type": "POWER", + "value": "ON" + } + }, + "sensorDataPoints": { + "insideTemperature": { + "celsius": 21.40, + "fahrenheit": 70.52, + "timestamp": "2020-03-06T19:06:13.185Z", + "type": "TEMPERATURE", + "precision": { + "celsius": 0.1, + "fahrenheit": 0.1 + } + }, + "humidity": { + "type": "PERCENTAGE", + "percentage": 49.20, + "timestamp": "2020-03-06T19:06:13.185Z" + } + } +}