From 3df2cb6b7836bd8a93af5ed69210d64db9f51117 Mon Sep 17 00:00:00 2001 From: Victor Vostrikov <1998617+gorynychzmey@users.noreply.github.com> Date: Tue, 11 Feb 2020 17:46:02 +0100 Subject: [PATCH] Add support of multiple Tado accounts (#31527) * Added support of multiple Tado accounts Changed geberation of sensor unique id (breaking change) * Fixed lints * Fixed error detecting opened window * Changed gereration of unique id of climate * Removed commented code and added comments --- homeassistant/components/tado/__init__.py | 71 ++++++++++++++--------- homeassistant/components/tado/climate.py | 38 ++++++------ homeassistant/components/tado/const.py | 1 + homeassistant/components/tado/sensor.py | 66 ++++++++++----------- 4 files changed, 93 insertions(+), 83 deletions(-) diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index ebf605bdc75..dbc4e87b650 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -12,7 +12,7 @@ from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.util import Throttle -from .const import CONF_FALLBACK +from .const import CONF_FALLBACK, DATA _LOGGER = logging.getLogger(__name__) @@ -27,12 +27,15 @@ SCAN_INTERVAL = timedelta(seconds=15) CONFIG_SCHEMA = vol.Schema( { - DOMAIN: vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_FALLBACK, default=True): cv.boolean, - } + DOMAIN: vol.All( + cv.ensure_list, + [ + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_FALLBACK, default=True): cv.boolean, + } + ], ) }, extra=vol.ALLOW_EXTRA, @@ -41,45 +44,54 @@ CONFIG_SCHEMA = vol.Schema( def setup(hass, config): """Set up of the Tado component.""" - username = config[DOMAIN][CONF_USERNAME] - password = config[DOMAIN][CONF_PASSWORD] + acc_list = config[DOMAIN] - tadoconnector = TadoConnector(hass, username, password) - if not tadoconnector.setup(): - return False + api_data_list = [] - hass.data[DOMAIN] = tadoconnector + for acc in acc_list: + username = acc[CONF_USERNAME] + password = acc[CONF_PASSWORD] + fallback = acc[CONF_FALLBACK] - # Do first update - tadoconnector.update() + tadoconnector = TadoConnector(hass, username, password, fallback) + if not tadoconnector.setup(): + continue + + # Do first update + tadoconnector.update() + + api_data_list.append(tadoconnector) + # Poll for updates in the background + hass.helpers.event.track_time_interval( + # we're using here tadoconnector as a parameter of lambda + # to capture actual value instead of closuring of latest value + lambda now, tc=tadoconnector: tc.update(), + SCAN_INTERVAL, + ) + + hass.data[DOMAIN] = {} + hass.data[DOMAIN][DATA] = api_data_list # Load components for component in TADO_COMPONENTS: load_platform( - hass, - component, - DOMAIN, - {CONF_FALLBACK: config[DOMAIN][CONF_FALLBACK]}, - config, + hass, component, DOMAIN, {}, config, ) - # Poll for updates in the background - hass.helpers.event.track_time_interval( - lambda now: tadoconnector.update(), SCAN_INTERVAL - ) - return True class TadoConnector: """An object to store the Tado data.""" - def __init__(self, hass, username, password): + def __init__(self, hass, username, password, fallback): """Initialize Tado Connector.""" self.hass = hass self._username = username self._password = password + self._fallback = fallback + self.device_id = None self.tado = None self.zones = None self.devices = None @@ -88,6 +100,11 @@ class TadoConnector: "device": {}, } + @property + def fallback(self): + """Return fallback flag to Smart Schedule.""" + return self._fallback + def setup(self): """Connect to Tado and fetch the zones.""" try: @@ -101,7 +118,7 @@ class TadoConnector: # Load zones and devices self.zones = self.tado.getZones() self.devices = self.tado.getMe()["homes"] - + self.device_id = self.devices[0]["id"] return True @Throttle(MIN_TIME_BETWEEN_UPDATES) diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 88433db0991..44e35bce787 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -25,7 +25,7 @@ from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from . import CONF_FALLBACK, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED +from . import DATA, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED from .const import ( CONST_MODE_OFF, CONST_MODE_SMART_SCHEDULE, @@ -70,21 +70,20 @@ SUPPORT_PRESET = [PRESET_AWAY, PRESET_HOME] def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Tado climate platform.""" - tado = hass.data[DOMAIN] - + api_list = hass.data[DOMAIN][DATA] entities = [] - for zone in tado.zones: - entity = create_climate_entity( - tado, zone["name"], zone["id"], discovery_info[CONF_FALLBACK] - ) - if entity: - entities.append(entity) + + for tado in api_list: + for zone in tado.zones: + entity = create_climate_entity(tado, zone["name"], zone["id"]) + if entity: + entities.append(entity) if entities: add_entities(entities, True) -def create_climate_entity(tado, name: str, zone_id: int, fallback: bool): +def create_climate_entity(tado, name: str, zone_id: int): """Create a Tado climate entity.""" capabilities = tado.get_capabilities(zone_id) _LOGGER.debug("Capabilities for zone %s: %s", zone_id, capabilities) @@ -112,15 +111,7 @@ def create_climate_entity(tado, name: str, zone_id: int, fallback: bool): step = temperatures["celsius"].get("step", PRECISION_TENTHS) entity = TadoClimate( - tado, - name, - zone_id, - zone_type, - min_temp, - max_temp, - step, - ac_support_heat, - fallback, + tado, name, zone_id, zone_type, min_temp, max_temp, step, ac_support_heat, ) return entity @@ -138,7 +129,6 @@ class TadoClimate(ClimateDevice): max_temp, step, ac_support_heat, - fallback, ): """Initialize of Tado climate entity.""" self._tado = tado @@ -146,6 +136,7 @@ class TadoClimate(ClimateDevice): self.zone_name = zone_name self.zone_id = zone_id self.zone_type = zone_type + 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 @@ -162,7 +153,7 @@ class TadoClimate(ClimateDevice): self._step = step self._target_temp = None - if fallback: + if tado.fallback: _LOGGER.debug("Default overlay is set to TADO MODE") # Fallback to Smart Schedule at next Schedule switch self._default_overlay = CONST_OVERLAY_TADO_MODE @@ -199,6 +190,11 @@ class TadoClimate(ClimateDevice): """Return the name of the entity.""" return self.zone_name + @property + def unique_id(self): + """Return the unique id.""" + return self._unique_id + @property def should_poll(self) -> bool: """Do not poll.""" diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index 3c0232c8ba2..8d67e3bf9f8 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -2,6 +2,7 @@ # Configuration CONF_FALLBACK = "fallback" +DATA = "data" # Types TYPE_AIR_CONDITIONING = "AIR_CONDITIONING" diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index a928b61a508..f5f32a6ed1a 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -6,7 +6,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from . import DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED +from . import DATA, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED from .const import TYPE_AIR_CONDITIONING, TYPE_HEATING, TYPE_HOT_WATER _LOGGER = logging.getLogger(__name__) @@ -40,26 +40,29 @@ DEVICE_SENSORS = ["tado bridge status"] def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the sensor platform.""" - tado = hass.data[DOMAIN] + api_list = hass.data[DOMAIN][DATA] - # Create zone sensors entities = [] - for zone in tado.zones: - entities.extend( - [ - create_zone_sensor(tado, zone["name"], zone["id"], variable) - for variable in ZONE_SENSORS.get(zone["type"]) - ] - ) - # Create device sensors - for home in tado.devices: - entities.extend( - [ - create_device_sensor(tado, home["name"], home["id"], variable) - for variable in DEVICE_SENSORS - ] - ) + for tado in api_list: + # Create zone sensors + + for zone in tado.zones: + entities.extend( + [ + create_zone_sensor(tado, zone["name"], zone["id"], variable) + for variable in ZONE_SENSORS.get(zone["type"]) + ] + ) + + # Create device sensors + for home in tado.devices: + entities.extend( + [ + create_device_sensor(tado, home["name"], home["id"], variable) + for variable in DEVICE_SENSORS + ] + ) add_entities(entities, True) @@ -86,7 +89,7 @@ class TadoSensor(Entity): self.zone_variable = zone_variable self.sensor_type = sensor_type - self._unique_id = f"{zone_variable} {zone_id}" + self._unique_id = f"{zone_variable} {zone_id} {tado.device_id}" self._state = None self._state_attributes = None @@ -227,23 +230,16 @@ class TadoSensor(Entity): self._state = data["tadoMode"] elif self.zone_variable == "overlay": - if "overlay" in data and data["overlay"] is not None: - self._state = True - self._state_attributes = { - "termination": data["overlay"]["termination"]["type"] - } - else: - self._state = False - self._state_attributes = {} + self._state = "overlay" in data and data["overlay"] is not None + self._state_attributes = ( + {"termination": data["overlay"]["termination"]["type"]} + if self._state + else {} + ) elif self.zone_variable == "early start": - if "preparation" in data and data["preparation"] is not None: - self._state = True - else: - self._state = False + self._state = "preparation" in data and data["preparation"] is not None elif self.zone_variable == "open window": - if "openWindowDetected" in data: - self._state = data["openWindowDetected"] - else: - self._state = False + self._state = "openWindow" in data and data["openWindow"] is not None + self._state_attributes = data["openWindow"] if self._state else {}