mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
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
This commit is contained in:
parent
8a6158116a
commit
3df2cb6b78
@ -12,7 +12,7 @@ from homeassistant.helpers.discovery import load_platform
|
|||||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from .const import CONF_FALLBACK
|
from .const import CONF_FALLBACK, DATA
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -27,12 +27,15 @@ SCAN_INTERVAL = timedelta(seconds=15)
|
|||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
[
|
||||||
{
|
{
|
||||||
vol.Required(CONF_USERNAME): cv.string,
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
vol.Required(CONF_PASSWORD): cv.string,
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
vol.Optional(CONF_FALLBACK, default=True): cv.boolean,
|
vol.Optional(CONF_FALLBACK, default=True): cv.boolean,
|
||||||
}
|
}
|
||||||
|
],
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
@ -41,31 +44,38 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Set up of the Tado component."""
|
"""Set up of the Tado component."""
|
||||||
username = config[DOMAIN][CONF_USERNAME]
|
acc_list = config[DOMAIN]
|
||||||
password = config[DOMAIN][CONF_PASSWORD]
|
|
||||||
|
|
||||||
tadoconnector = TadoConnector(hass, username, password)
|
api_data_list = []
|
||||||
|
|
||||||
|
for acc in acc_list:
|
||||||
|
username = acc[CONF_USERNAME]
|
||||||
|
password = acc[CONF_PASSWORD]
|
||||||
|
fallback = acc[CONF_FALLBACK]
|
||||||
|
|
||||||
|
tadoconnector = TadoConnector(hass, username, password, fallback)
|
||||||
if not tadoconnector.setup():
|
if not tadoconnector.setup():
|
||||||
return False
|
continue
|
||||||
|
|
||||||
hass.data[DOMAIN] = tadoconnector
|
|
||||||
|
|
||||||
# Do first update
|
# Do first update
|
||||||
tadoconnector.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
|
# Load components
|
||||||
for component in TADO_COMPONENTS:
|
for component in TADO_COMPONENTS:
|
||||||
load_platform(
|
load_platform(
|
||||||
hass,
|
hass, component, DOMAIN, {}, config,
|
||||||
component,
|
|
||||||
DOMAIN,
|
|
||||||
{CONF_FALLBACK: config[DOMAIN][CONF_FALLBACK]},
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Poll for updates in the background
|
|
||||||
hass.helpers.event.track_time_interval(
|
|
||||||
lambda now: tadoconnector.update(), SCAN_INTERVAL
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -74,12 +84,14 @@ def setup(hass, config):
|
|||||||
class TadoConnector:
|
class TadoConnector:
|
||||||
"""An object to store the Tado data."""
|
"""An object to store the Tado data."""
|
||||||
|
|
||||||
def __init__(self, hass, username, password):
|
def __init__(self, hass, username, password, fallback):
|
||||||
"""Initialize Tado Connector."""
|
"""Initialize Tado Connector."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
|
self._fallback = fallback
|
||||||
|
|
||||||
|
self.device_id = None
|
||||||
self.tado = None
|
self.tado = None
|
||||||
self.zones = None
|
self.zones = None
|
||||||
self.devices = None
|
self.devices = None
|
||||||
@ -88,6 +100,11 @@ class TadoConnector:
|
|||||||
"device": {},
|
"device": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fallback(self):
|
||||||
|
"""Return fallback flag to Smart Schedule."""
|
||||||
|
return self._fallback
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""Connect to Tado and fetch the zones."""
|
"""Connect to Tado and fetch the zones."""
|
||||||
try:
|
try:
|
||||||
@ -101,7 +118,7 @@ class TadoConnector:
|
|||||||
# Load zones and devices
|
# Load zones and devices
|
||||||
self.zones = self.tado.getZones()
|
self.zones = self.tado.getZones()
|
||||||
self.devices = self.tado.getMe()["homes"]
|
self.devices = self.tado.getMe()["homes"]
|
||||||
|
self.device_id = self.devices[0]["id"]
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
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 (
|
from .const import (
|
||||||
CONST_MODE_OFF,
|
CONST_MODE_OFF,
|
||||||
CONST_MODE_SMART_SCHEDULE,
|
CONST_MODE_SMART_SCHEDULE,
|
||||||
@ -70,13 +70,12 @@ SUPPORT_PRESET = [PRESET_AWAY, PRESET_HOME]
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the Tado climate platform."""
|
"""Set up the Tado climate platform."""
|
||||||
tado = hass.data[DOMAIN]
|
api_list = hass.data[DOMAIN][DATA]
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
|
for tado in api_list:
|
||||||
for zone in tado.zones:
|
for zone in tado.zones:
|
||||||
entity = create_climate_entity(
|
entity = create_climate_entity(tado, zone["name"], zone["id"])
|
||||||
tado, zone["name"], zone["id"], discovery_info[CONF_FALLBACK]
|
|
||||||
)
|
|
||||||
if entity:
|
if entity:
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
add_entities(entities, True)
|
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."""
|
"""Create a Tado climate entity."""
|
||||||
capabilities = tado.get_capabilities(zone_id)
|
capabilities = tado.get_capabilities(zone_id)
|
||||||
_LOGGER.debug("Capabilities for zone %s: %s", zone_id, capabilities)
|
_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)
|
step = temperatures["celsius"].get("step", PRECISION_TENTHS)
|
||||||
|
|
||||||
entity = TadoClimate(
|
entity = TadoClimate(
|
||||||
tado,
|
tado, name, zone_id, zone_type, min_temp, max_temp, step, ac_support_heat,
|
||||||
name,
|
|
||||||
zone_id,
|
|
||||||
zone_type,
|
|
||||||
min_temp,
|
|
||||||
max_temp,
|
|
||||||
step,
|
|
||||||
ac_support_heat,
|
|
||||||
fallback,
|
|
||||||
)
|
)
|
||||||
return entity
|
return entity
|
||||||
|
|
||||||
@ -138,7 +129,6 @@ class TadoClimate(ClimateDevice):
|
|||||||
max_temp,
|
max_temp,
|
||||||
step,
|
step,
|
||||||
ac_support_heat,
|
ac_support_heat,
|
||||||
fallback,
|
|
||||||
):
|
):
|
||||||
"""Initialize of Tado climate entity."""
|
"""Initialize of Tado climate entity."""
|
||||||
self._tado = tado
|
self._tado = tado
|
||||||
@ -146,6 +136,7 @@ class TadoClimate(ClimateDevice):
|
|||||||
self.zone_name = zone_name
|
self.zone_name = zone_name
|
||||||
self.zone_id = zone_id
|
self.zone_id = zone_id
|
||||||
self.zone_type = zone_type
|
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_device = zone_type == TYPE_AIR_CONDITIONING
|
||||||
self._ac_support_heat = ac_support_heat
|
self._ac_support_heat = ac_support_heat
|
||||||
@ -162,7 +153,7 @@ class TadoClimate(ClimateDevice):
|
|||||||
self._step = step
|
self._step = step
|
||||||
self._target_temp = None
|
self._target_temp = None
|
||||||
|
|
||||||
if fallback:
|
if tado.fallback:
|
||||||
_LOGGER.debug("Default overlay is set to TADO MODE")
|
_LOGGER.debug("Default overlay is set to TADO MODE")
|
||||||
# Fallback to Smart Schedule at next Schedule switch
|
# Fallback to Smart Schedule at next Schedule switch
|
||||||
self._default_overlay = CONST_OVERLAY_TADO_MODE
|
self._default_overlay = CONST_OVERLAY_TADO_MODE
|
||||||
@ -199,6 +190,11 @@ class TadoClimate(ClimateDevice):
|
|||||||
"""Return the name of the entity."""
|
"""Return the name of the entity."""
|
||||||
return self.zone_name
|
return self.zone_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return the unique id."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self) -> bool:
|
def should_poll(self) -> bool:
|
||||||
"""Do not poll."""
|
"""Do not poll."""
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
CONF_FALLBACK = "fallback"
|
CONF_FALLBACK = "fallback"
|
||||||
|
DATA = "data"
|
||||||
|
|
||||||
# Types
|
# Types
|
||||||
TYPE_AIR_CONDITIONING = "AIR_CONDITIONING"
|
TYPE_AIR_CONDITIONING = "AIR_CONDITIONING"
|
||||||
|
@ -6,7 +6,7 @@ from homeassistant.core import callback
|
|||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import Entity
|
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
|
from .const import TYPE_AIR_CONDITIONING, TYPE_HEATING, TYPE_HOT_WATER
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -40,10 +40,13 @@ DEVICE_SENSORS = ["tado bridge status"]
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the sensor platform."""
|
"""Set up the sensor platform."""
|
||||||
tado = hass.data[DOMAIN]
|
api_list = hass.data[DOMAIN][DATA]
|
||||||
|
|
||||||
# Create zone sensors
|
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
|
for tado in api_list:
|
||||||
|
# Create zone sensors
|
||||||
|
|
||||||
for zone in tado.zones:
|
for zone in tado.zones:
|
||||||
entities.extend(
|
entities.extend(
|
||||||
[
|
[
|
||||||
@ -86,7 +89,7 @@ class TadoSensor(Entity):
|
|||||||
self.zone_variable = zone_variable
|
self.zone_variable = zone_variable
|
||||||
self.sensor_type = sensor_type
|
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 = None
|
||||||
self._state_attributes = None
|
self._state_attributes = None
|
||||||
@ -227,23 +230,16 @@ class TadoSensor(Entity):
|
|||||||
self._state = data["tadoMode"]
|
self._state = data["tadoMode"]
|
||||||
|
|
||||||
elif self.zone_variable == "overlay":
|
elif self.zone_variable == "overlay":
|
||||||
if "overlay" in data and data["overlay"] is not None:
|
self._state = "overlay" in data and data["overlay"] is not None
|
||||||
self._state = True
|
self._state_attributes = (
|
||||||
self._state_attributes = {
|
{"termination": data["overlay"]["termination"]["type"]}
|
||||||
"termination": data["overlay"]["termination"]["type"]
|
if self._state
|
||||||
}
|
else {}
|
||||||
else:
|
)
|
||||||
self._state = False
|
|
||||||
self._state_attributes = {}
|
|
||||||
|
|
||||||
elif self.zone_variable == "early start":
|
elif self.zone_variable == "early start":
|
||||||
if "preparation" in data and data["preparation"] is not None:
|
self._state = "preparation" in data and data["preparation"] is not None
|
||||||
self._state = True
|
|
||||||
else:
|
|
||||||
self._state = False
|
|
||||||
|
|
||||||
elif self.zone_variable == "open window":
|
elif self.zone_variable == "open window":
|
||||||
if "openWindowDetected" in data:
|
self._state = "openWindow" in data and data["openWindow"] is not None
|
||||||
self._state = data["openWindowDetected"]
|
self._state_attributes = data["openWindow"] if self._state else {}
|
||||||
else:
|
|
||||||
self._state = False
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user