Skip to content

Commit

Permalink
Rework tado component (home-assistant#29246)
Browse files Browse the repository at this point in the history
* Fix imports so it works in custom_components

* Rework tado component

* Code cleanup

* Remove water_heater

* Address pylint warnings

* Remove water_heater from components

* Raise PlatformNotReady when we couldn't connect

* Revert PlatformNotReady since we are not a platform

* Add debugging information

* Add fallback setting

* Import with relative path

* Address race condition

* Cleanup

* Catch 422 Errors and log the real error

* Use async_schedule_update_ha_state to update the entities

* Forgot the True
  • Loading branch information
michaelarnauts authored Dec 20, 2019
1 parent 92fd3e3 commit 04b5d6c
Show file tree
Hide file tree
Showing 4 changed files with 341 additions and 416 deletions.
192 changes: 116 additions & 76 deletions homeassistant/components/tado/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,29 @@
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.util import Throttle

from .const import CONF_FALLBACK

_LOGGER = logging.getLogger(__name__)

DATA_TADO = "tado_data"
DOMAIN = "tado"

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
SIGNAL_TADO_UPDATE_RECEIVED = "tado_update_received_{}_{}"

TADO_COMPONENTS = ["sensor", "climate"]

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
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,
}
)
},
Expand All @@ -38,91 +44,106 @@ def setup(hass, config):
username = config[DOMAIN][CONF_USERNAME]
password = config[DOMAIN][CONF_PASSWORD]

try:
tado = Tado(username, password)
tado.setDebugging(True)
except (RuntimeError, urllib.error.HTTPError):
_LOGGER.error("Unable to connect to mytado with username and password")
tadoconnector = TadoConnector(hass, username, password)
if not tadoconnector.setup():
return False

hass.data[DATA_TADO] = TadoDataStore(tado)
hass.data[DOMAIN] = tadoconnector

# Do first update
tadoconnector.update()

# Load components
for component in TADO_COMPONENTS:
load_platform(hass, component, DOMAIN, {}, config)
load_platform(
hass,
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


class TadoDataStore:
class TadoConnector:
"""An object to store the Tado data."""

def __init__(self, tado):
"""Initialize Tado data store."""
self.tado = tado
def __init__(self, hass, username, password):
"""Initialize Tado Connector."""
self.hass = hass
self._username = username
self._password = password

self.sensors = {}
self.data = {}
self.tado = None
self.zones = None
self.devices = None
self.data = {
"zone": {},
"device": {},
}

def setup(self):
"""Connect to Tado and fetch the zones."""
try:
self.tado = Tado(self._username, self._password)
except (RuntimeError, urllib.error.HTTPError) as exc:
_LOGGER.error("Unable to connect: %s", exc)
return False

self.tado.setDebugging(True)

# Load zones and devices
self.zones = self.tado.getZones()
self.devices = self.tado.getMe()["homes"]

return True

@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update the internal data from mytado.com."""
for data_id, sensor in list(self.sensors.items()):
data = None

try:
if "zone" in sensor:
_LOGGER.debug(
"Querying mytado.com for zone %s %s",
sensor["id"],
sensor["name"],
)
data = self.tado.getState(sensor["id"])

if "device" in sensor:
_LOGGER.debug(
"Querying mytado.com for device %s %s",
sensor["id"],
sensor["name"],
)
data = self.tado.getDevices()[0]

except RuntimeError:
_LOGGER.error(
"Unable to connect to myTado. %s %s", sensor["id"], sensor["id"]
)

self.data[data_id] = data

def add_sensor(self, data_id, sensor):
"""Add a sensor to update in _update()."""
self.sensors[data_id] = sensor
self.data[data_id] = None

def get_data(self, data_id):
"""Get the cached data."""
data = {"error": "no data"}

if data_id in self.data:
data = self.data[data_id]

return data

def get_zones(self):
"""Wrap for getZones()."""
return self.tado.getZones()

def get_capabilities(self, tado_id):
"""Wrap for getCapabilities(..)."""
return self.tado.getCapabilities(tado_id)

def get_me(self):
"""Wrap for getMe()."""
return self.tado.getMe()
"""Update the registered zones."""
for zone in self.zones:
self.update_sensor("zone", zone["id"])
for device in self.devices:
self.update_sensor("device", device["id"])

def update_sensor(self, sensor_type, sensor):
"""Update the internal data from Tado."""
_LOGGER.debug("Updating %s %s", sensor_type, sensor)
try:
if sensor_type == "zone":
data = self.tado.getState(sensor)
elif sensor_type == "device":
data = self.tado.getDevices()[0]
else:
_LOGGER.debug("Unknown sensor: %s", sensor_type)
return
except RuntimeError:
_LOGGER.error(
"Unable to connect to Tado while updating %s %s", sensor_type, sensor,
)
return

self.data[sensor_type][sensor] = data

_LOGGER.debug("Dispatching update to %s %s: %s", sensor_type, sensor, data)
dispatcher_send(
self.hass, SIGNAL_TADO_UPDATE_RECEIVED.format(sensor_type, sensor)
)

def get_capabilities(self, zone_id):
"""Return the capabilities of the devices."""
return self.tado.getCapabilities(zone_id)

def reset_zone_overlay(self, zone_id):
"""Wrap for resetZoneOverlay(..)."""
"""Reset the zone back to the default operation."""
self.tado.resetZoneOverlay(zone_id)
self.update(no_throttle=True) # pylint: disable=unexpected-keyword-arg
self.update_sensor("zone", zone_id)

def set_zone_overlay(
self,
Expand All @@ -133,13 +154,32 @@ def set_zone_overlay(
device_type="HEATING",
mode=None,
):
"""Wrap for setZoneOverlay(..)."""
self.tado.setZoneOverlay(
zone_id, overlay_mode, temperature, duration, device_type, "ON", mode
"""Set a zone overlay."""
_LOGGER.debug(
"Set overlay for zone %s: mode=%s, temp=%s, duration=%s, type=%s, mode=%s",
zone_id,
overlay_mode,
temperature,
duration,
device_type,
mode,
)
self.update(no_throttle=True) # pylint: disable=unexpected-keyword-arg
try:
self.tado.setZoneOverlay(
zone_id, overlay_mode, temperature, duration, device_type, "ON", mode
)
except urllib.error.HTTPError as exc:
_LOGGER.error("Could not set zone overlay: %s", exc.read())

self.update_sensor("zone", zone_id)

def set_zone_off(self, zone_id, overlay_mode, device_type="HEATING"):
"""Set a zone to off."""
self.tado.setZoneOverlay(zone_id, overlay_mode, None, None, device_type, "OFF")
self.update(no_throttle=True) # pylint: disable=unexpected-keyword-arg
try:
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())

self.update_sensor("zone", zone_id)
Loading

0 comments on commit 04b5d6c

Please sign in to comment.