Add overlay options to Tado (#65886)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
north3221 2022-03-30 06:59:03 +01:00 committed by GitHub
parent 09f6785956
commit e76170fbfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 108 additions and 51 deletions

View File

@ -1003,8 +1003,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/syslog/ @fabaff
/homeassistant/components/system_bridge/ @timmo001
/tests/components/system_bridge/ @timmo001
/homeassistant/components/tado/ @michaelarnauts
/tests/components/tado/ @michaelarnauts
/homeassistant/components/tado/ @michaelarnauts @north3221
/tests/components/tado/ @michaelarnauts @north3221
/homeassistant/components/tag/ @balloob @dmulcahey
/tests/components/tag/ @balloob @dmulcahey
/homeassistant/components/tailscale/ @frenck

View File

@ -3,7 +3,6 @@ from datetime import timedelta
import logging
from PyTado.interface import Tado
from PyTado.zone import TadoZone
from requests import RequestException
import requests.exceptions
@ -19,6 +18,7 @@ from homeassistant.util import Throttle
from .const import (
CONF_FALLBACK,
CONST_OVERLAY_TADO_MODE,
DATA,
DOMAIN,
INSIDE_TEMPERATURE_MEASUREMENT,
@ -51,7 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
fallback = entry.options.get(CONF_FALLBACK, True)
fallback = entry.options.get(CONF_FALLBACK, CONST_OVERLAY_TADO_MODE)
tadoconnector = TadoConnector(hass, username, password, fallback)
@ -99,7 +99,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: ConfigEntry):
options = dict(entry.options)
if CONF_FALLBACK not in options:
options[CONF_FALLBACK] = entry.data.get(CONF_FALLBACK, True)
options[CONF_FALLBACK] = entry.data.get(CONF_FALLBACK, CONST_OVERLAY_TADO_MODE)
hass.config_entries.async_update_entry(entry, options=options)
@ -213,23 +213,8 @@ class TadoConnector:
_LOGGER.error("Unable to connect to Tado while updating zones")
return
for zone in self.zones:
zone_id = zone["id"]
_LOGGER.debug("Updating zone %s", zone_id)
zone_state = TadoZone(zone_states[str(zone_id)], zone_id)
self.data["zone"][zone_id] = zone_state
_LOGGER.debug(
"Dispatching update to %s zone %s: %s",
self.home_id,
zone_id,
zone_state,
)
dispatcher_send(
self.hass,
SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_id, "zone", zone["id"]),
)
for zone in zone_states:
self.update_zone(int(zone))
def update_zone(self, zone_id):
"""Update the internal data from Tado."""

View File

@ -24,6 +24,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
CONST_EXCLUSIVE_OVERLAY_GROUP,
CONST_FAN_AUTO,
CONST_FAN_OFF,
CONST_MODE_AUTO,
@ -32,10 +33,14 @@ from .const import (
CONST_MODE_OFF,
CONST_MODE_SMART_SCHEDULE,
CONST_OVERLAY_MANUAL,
CONST_OVERLAY_TADO_DEFAULT,
CONST_OVERLAY_TADO_MODE,
CONST_OVERLAY_TADO_OPTIONS,
CONST_OVERLAY_TIMER,
DATA,
DOMAIN,
HA_TERMINATION_DURATION,
HA_TERMINATION_TYPE,
HA_TO_TADO_FAN_MODE_MAP,
HA_TO_TADO_HVAC_MODE_MAP,
ORDERED_KNOWN_TADO_MODES,
@ -58,12 +63,16 @@ _LOGGER = logging.getLogger(__name__)
SERVICE_CLIMATE_TIMER = "set_climate_timer"
ATTR_TIME_PERIOD = "time_period"
ATTR_REQUESTED_OVERLAY = "requested_overlay"
CLIMATE_TIMER_SCHEMA = {
vol.Required(ATTR_TIME_PERIOD, default="01:00:00"): vol.All(
vol.Required(ATTR_TEMPERATURE): vol.Coerce(float),
vol.Exclusive(ATTR_TIME_PERIOD, CONST_EXCLUSIVE_OVERLAY_GROUP): vol.All(
cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds()
),
vol.Required(ATTR_TEMPERATURE): vol.Coerce(float),
vol.Exclusive(ATTR_REQUESTED_OVERLAY, CONST_EXCLUSIVE_OVERLAY_GROUP): vol.In(
CONST_OVERLAY_TADO_OPTIONS
),
}
SERVICE_TEMP_OFFSET = "set_climate_temperature_offset"
@ -379,11 +388,14 @@ class TadoClimate(TadoZoneEntity, ClimateEntity):
# the device is switching states
return self._tado_zone_data.target_temp or self._tado_zone_data.current_temp
def set_timer(self, time_period, temperature=None):
def set_timer(self, temperature=None, time_period=None, requested_overlay=None):
"""Set the timer on the entity, and temperature if supported."""
self._control_hvac(
hvac_mode=CONST_MODE_HEAT, target_temp=temperature, duration=time_period
hvac_mode=CONST_MODE_HEAT,
target_temp=temperature,
duration=time_period,
overlay_mode=requested_overlay,
)
def set_temp_offset(self, offset):
@ -464,7 +476,14 @@ class TadoClimate(TadoZoneEntity, ClimateEntity):
@property
def extra_state_attributes(self):
"""Return temperature offset."""
return self._tado_zone_temp_offset
state_attr = self._tado_zone_temp_offset
state_attr[
HA_TERMINATION_TYPE
] = self._tado_zone_data.default_overlay_termination_type
state_attr[
HA_TERMINATION_DURATION
] = self._tado_zone_data.default_overlay_termination_duration
return state_attr
def set_swing_mode(self, swing_mode):
"""Set swing modes for the device."""
@ -474,6 +493,7 @@ class TadoClimate(TadoZoneEntity, ClimateEntity):
def _async_update_zone_data(self):
"""Load tado data into zone."""
self._tado_zone_data = self._tado.data["zone"][self.zone_id]
# Assign offset values to mapped attributes
for offset_key, attr in TADO_TO_HA_OFFSET_MAP.items():
if (
@ -518,6 +538,7 @@ class TadoClimate(TadoZoneEntity, ClimateEntity):
fan_mode=None,
swing_mode=None,
duration=None,
overlay_mode=None,
):
"""Send new target temperature to Tado."""
@ -559,22 +580,41 @@ class TadoClimate(TadoZoneEntity, ClimateEntity):
self._tado.reset_zone_overlay(self.zone_id)
return
# If user gave duration then overlay mode needs to be timer
if duration:
overlay_mode = CONST_OVERLAY_TIMER
# If no duration or timer set to fallback setting
if overlay_mode is None:
overlay_mode = (
self._tado.fallback
if self._tado.fallback is not None
else CONST_OVERLAY_TADO_MODE
)
# If default is Tado default then look it up
if overlay_mode == CONST_OVERLAY_TADO_DEFAULT:
overlay_mode = (
self._tado_zone_data.default_overlay_termination_type
if self._tado_zone_data.default_overlay_termination_type is not None
else CONST_OVERLAY_TADO_MODE
)
# If we ended up with a timer but no duration, set a default duration
if overlay_mode == CONST_OVERLAY_TIMER and duration is None:
duration = (
self._tado_zone_data.default_overlay_termination_duration
if self._tado_zone_data.default_overlay_termination_duration is not None
else "3600"
)
_LOGGER.debug(
"Switching to %s for zone %s (%d) with temperature %s °C and duration %s",
"Switching to %s for zone %s (%d) with temperature %s °C and duration %s using overlay %s",
self._current_tado_hvac_mode,
self.zone_name,
self.zone_id,
self._target_temp,
duration,
overlay_mode,
)
overlay_mode = CONST_OVERLAY_MANUAL
if duration:
overlay_mode = CONST_OVERLAY_TIMER
elif self._tado.fallback:
# Fallback to Smart Schedule at next Schedule switch if we have fallback enabled
overlay_mode = CONST_OVERLAY_TADO_MODE
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

View File

@ -11,12 +11,15 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from .const import CONF_FALLBACK, DOMAIN, UNIQUE_ID
from .const import CONF_FALLBACK, CONST_OVERLAY_TADO_OPTIONS, DOMAIN, UNIQUE_ID
_LOGGER = logging.getLogger(__name__)
DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
@ -122,9 +125,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
data_schema = vol.Schema(
{
vol.Required(
vol.Optional(
CONF_FALLBACK, default=self.config_entry.options.get(CONF_FALLBACK)
): bool,
): vol.In(CONST_OVERLAY_TADO_OPTIONS),
}
)
return self.async_show_form(step_id="init", data_schema=data_schema)

View File

@ -95,6 +95,17 @@ CONST_OVERLAY_TADO_MODE = (
)
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
CONST_OVERLAY_TADO_DEFAULT = (
"TADO_DEFAULT" # use the setting from tado zone itself (set in Tado app or webapp)
)
CONST_OVERLAY_TADO_OPTIONS = [
CONST_OVERLAY_TADO_MODE,
CONST_OVERLAY_MANUAL,
CONST_OVERLAY_TADO_DEFAULT,
]
CONST_EXCLUSIVE_OVERLAY_GROUP = (
"overlay_group" # Overlay group for set_climate_timer service
)
# Heat always comes first since we get the
@ -180,3 +191,7 @@ TADO_TO_HA_OFFSET_MAP = {
TADO_OFFSET_CELSIUS: HA_OFFSET_CELSIUS,
TADO_OFFSET_FAHRENHEIT: HA_OFFSET_FAHRENHEIT,
}
# Constants for Overlay Default settings
HA_TERMINATION_TYPE = "default_overlay_type"
HA_TERMINATION_DURATION = "default_overlay_seconds"

View File

@ -3,7 +3,7 @@
"name": "Tado",
"documentation": "https://www.home-assistant.io/integrations/tado",
"requirements": ["python-tado==0.12.0"],
"codeowners": ["@michaelarnauts"],
"codeowners": ["@michaelarnauts", "@north3221"],
"config_flow": true,
"homekit": {
"models": ["tado", "AC02"]

View File

@ -6,14 +6,6 @@ set_climate_timer:
integration: tado
domain: climate
fields:
time_period:
name: Time period
description: Set the time period for the boost.
required: true
example: "01:30:00"
default: "01:00:00"
selector:
text:
temperature:
name: Temperature
description: Temperature to set climate entity to
@ -24,6 +16,26 @@ set_climate_timer:
max: 100
step: 0.5
unit_of_measurement: '°'
time_period:
name: Time period
description: Choose this or Overlay. Set the time period for the change if you want to be specific. Alternatively use Overlay
required: false
example: "01:30:00"
default: "01:00:00"
selector:
text:
requested_overlay:
name: Overlay
description: Choose this or Time Period. Allows you to choose an overlay. MANUAL:=Overlay until user removes; NEXT_TIME_BLOCK:=Overlay until next timeblock; TADO_DEFAULT:=Overlay based on tado app setting
required: false
example: "MANUAL"
default: "TADO_DEFAULT"
selector:
select:
options:
- "NEXT_TIME_BLOCK"
- "MANUAL"
- "TADO_DEFAULT"
set_water_heater_timer:
name: Set water heater timer

View File

@ -22,9 +22,9 @@
"options": {
"step": {
"init": {
"description": "Fallback mode will switch to Smart Schedule at next schedule switch after manually adjusting a zone.",
"description": "Fallback mode lets you choose when to fallback to Smart Schedule from your manual zone overlay. (NEXT_TIME_BLOCK:= Change at next Smart Schedule change; MANUAL:= Dont change until you cancel; TADO_DEFAULT:= Change based on your setting in Tado App).",
"data": {
"fallback": "Enable fallback mode."
"fallback": "Choose fallback mode."
},
"title": "Adjust Tado options."
}

View File

@ -146,7 +146,9 @@ async def async_init_integration(
text=load_fixture(zone_1_state_fixture),
)
entry = MockConfigEntry(
domain=DOMAIN, data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"}
domain=DOMAIN,
data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"},
options={"fallback": "NEXT_TIME_BLOCK"},
)
entry.add_to_hass(hass)