From a759a2d89c17a51a6f42cacbeb60cccca73a0e74 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Mon, 20 Mar 2017 22:27:02 +0100 Subject: [PATCH 1/8] changed to hass version --- custom_components/climate/tado_v1.py | 329 ++++++++++++--------------- custom_components/sensor/tado_v1.py | 201 +++++++--------- custom_components/tado_v1.py | 107 +++++++-- 3 files changed, 321 insertions(+), 316 deletions(-) diff --git a/custom_components/climate/tado_v1.py b/custom_components/climate/tado_v1.py index f801973..6f8247b 100644 --- a/custom_components/climate/tado_v1.py +++ b/custom_components/climate/tado_v1.py @@ -1,67 +1,98 @@ -""" -tado component to create a climate device for each zone -""" +"""tado component to create a climate device for each zone.""" import logging from homeassistant.const import TEMP_CELSIUS -from homeassistant.helpers.event import track_state_change from homeassistant.components.climate import ( ClimateDevice) from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, ATTR_TEMPERATURE) + ATTR_TEMPERATURE) +from homeassistant.components.tado import ( + DATA_TADO) -CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode -CONST_MODE_OFF = "OFF" # Switch off heating in a zone +CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode +CONST_MODE_OFF = "OFF" # Switch off heating in a zone # 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 - -CONST_DEFAULT_OPERATION_MODE = CONST_OVERLAY_TADO_MODE # will be used when changing temperature -CONST_DEFAULT_OFF_MODE = CONST_OVERLAY_MANUAL # will be used when switching to CONST_MODE_OFF - -# DOMAIN = 'tado_v1' +# wait until tado changes the mode automatic +CONST_OVERLAY_TADO_MODE = "TADO_MODE" +# the user has change the temperature or mode manually +CONST_OVERLAY_MANUAL = "MANUAL" +# the temperature will be reset after a timespan +CONST_OVERLAY_TIMER = "TIMER" + +OPERATION_LIST = { + CONST_OVERLAY_MANUAL: "Manual", + CONST_OVERLAY_TIMER: "Timer", + CONST_OVERLAY_TADO_MODE: "Tado mode", + CONST_MODE_SMART_SCHEDULE: "Smart schedule", + CONST_MODE_OFF: "Off"} _LOGGER = logging.getLogger(__name__) -SENSOR_TYPES = ['temperature', 'humidity', 'tado mode', 'power', 'overlay'] + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the climate platform.""" - # pylint: disable=W0613 - # get the PyTado object from the hub component - tado = hass.data['Mytado'] + tado = hass.data[DATA_TADO] try: - zones = tado.getZones() + zones = tado.get_zones() except RuntimeError: _LOGGER.error("Unable to get zone info from mytado") return False - tado_data = TadoData(tado) - climate_devices = [] for zone in zones: - climate_devices.append(tado_data.create_climate_device(hass, zone['name'], zone['id'])) + climate_devices.append(create_climate_device(tado, hass, + zone, + zone['name'], + zone['id'])) if len(climate_devices) > 0: - add_devices(climate_devices) - tado_data.activate_tracking(hass) + add_devices(climate_devices, True) return True else: return False + +def create_climate_device(tado, hass, zone, name, zone_id): + """Create a climate device.""" + capabilities = tado.get_capabilities(zone_id) + + unit = TEMP_CELSIUS + min_temp = float(capabilities["temperatures"]["celsius"]["min"]) + max_temp = float(capabilities["temperatures"]["celsius"]["max"]) + ac_mode = capabilities["type"] != "HEATING" + + data_id = 'zone {} {}'.format(name, zone_id) + device = TadoClimate(tado, + name, zone_id, data_id, + hass.config.units.temperature(min_temp, unit), + hass.config.units.temperature(max_temp, unit), + ac_mode) + + tado.add_sensor(data_id, { + "id": zone_id, + "zone": zone, + "name": name, + "climate": device + }) + + return device + + class TadoClimate(ClimateDevice): """Representation of a tado climate device.""" - # pylint: disable=R0902, R0913 - def __init__(self, tado, zone_name, zone_id, - min_temp, max_temp, target_temp, ac_mode, + def __init__(self, store, zone_name, zone_id, data_id, + min_temp, max_temp, ac_mode, tolerance=0.3): - self._tado = tado + """Initialization of TadoClimate device.""" + self._store = store + self._data_id = data_id + self.zone_name = zone_name self.zone_id = zone_id @@ -75,35 +106,17 @@ def __init__(self, tado, zone_name, zone_id, self._is_away = False self._min_temp = min_temp self._max_temp = max_temp - self._target_temp = target_temp + self._target_temp = None self._tolerance = tolerance - self._unit = TEMP_CELSIUS - self._operation_list = [CONST_OVERLAY_MANUAL, CONST_OVERLAY_TIMER, CONST_OVERLAY_TADO_MODE, - CONST_MODE_SMART_SCHEDULE, CONST_MODE_OFF] self._current_operation = CONST_MODE_SMART_SCHEDULE - self._overlay_mode = self._current_operation - - @property - def should_poll(self): - """No Polling needed for tado climate device (because it reuses sensors).""" - return False + self._overlay_mode = CONST_MODE_SMART_SCHEDULE @property def name(self): """Return the name of the sensor.""" return self.zone_name - @property - def temperature_unit(self): - """Return the unit of measurement.""" - return self._unit - - @property - def state(self): - """Return the current temperature as the state, instead of operation_mode""" - return self._cur_temp - @property def current_humidity(self): """Return the current humidity.""" @@ -116,24 +129,19 @@ def current_temperature(self): @property def current_operation(self): - """Return current operation ie. heat, cool, idle.""" - return self._current_operation + """Return current readable operation mode.""" + return OPERATION_LIST.get(self._current_operation) @property def operation_list(self): - """List of available operation modes.""" - return self._operation_list + """List of available operation modes (readable).""" + return list(OPERATION_LIST.values()) @property def is_away_mode_on(self): """Return true if away mode is on.""" return self._is_away - @property - def _is_device_active(self): - """If the toggleable device is currently active.""" - return self._device_is_active - @property def target_temperature(self): """Return the temperature we try to reach.""" @@ -145,98 +153,103 @@ def set_temperature(self, **kwargs): if temperature is None: return - self._current_operation = CONST_DEFAULT_OPERATION_MODE + self._current_operation = CONST_OVERLAY_TADO_MODE self._overlay_mode = None self._target_temp = temperature self._control_heating() - self.update_ha_state() - def set_operation_mode(self, operation_mode): - """Set new target temperature.""" + def set_operation_mode(self, readable_operation_mode): + """Set new operation mode.""" + operation_mode = CONST_MODE_SMART_SCHEDULE + + for mode, readable in OPERATION_LIST.items(): + if readable == readable_operation_mode: + operation_mode = mode + break + self._current_operation = operation_mode self._overlay_mode = None self._control_heating() - self.update_ha_state() @property def min_temp(self): """Return the minimum temperature.""" - # pylint: disable=no-member if self._min_temp: return self._min_temp else: # get default temp from super class - return ClimateDevice.min_temp.fget(self) + return super().min_temp @property def max_temp(self): """Return the maximum temperature.""" - # pylint: disable=no-member if self._max_temp: return self._max_temp else: - # Get default temp from super class - return ClimateDevice.max_temp.fget(self) - - def sensor_changed(self, entity_id, old_state, new_state): - # pylint: disable=W0613 - """Called when a depending sensor changes.""" - if new_state is None or new_state.state is None: - return - - self.update_state(entity_id, new_state, True) - - def update_state(self, entity_type, state, update_ha): - """update the internal state.""" - if state.state == "unknown": - return - - _LOGGER.info("%s changed to %s", entity_type, state.state) - - try: - if entity_type.endswith("temperature"): - unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - - self._cur_temp = self.hass.config.units.temperature( - float(state.state), unit) - - self._target_temp = self.hass.config.units.temperature( - float(state.attributes.get("setting")), unit) - - elif entity_type.endswith("humidity"): - self._cur_humidity = float(state.state) - - elif entity_type.endswith("tado mode"): - self._is_away = state.state == "AWAY" - - elif entity_type.endswith("power"): - if state.state == "OFF": - self._current_operation = CONST_MODE_OFF - self._device_is_active = False - else: - self._device_is_active = True - - elif entity_type.endswith("overlay"): - # if you set mode manualy to off, there will be an overlay - # and a termination, but we want to see the mode "OFF" - overlay = state.state - termination = state.attributes.get("termination") - - if overlay == "True" and self._device_is_active: - # there is an overlay the device is on - self._overlay_mode = termination - self._current_operation = termination - elif overlay == "False": - # there is no overlay, the mode will always be - # "SMART_SCHEDULE" - self._overlay_mode = CONST_MODE_SMART_SCHEDULE - self._current_operation = CONST_MODE_SMART_SCHEDULE + # Get default temp from super class + return super().max_temp + + def update(self): + """Update the state of this climate device.""" + self._store.update() + + data = self._store.get_data(self._data_id) + + if 'sensorDataPoints' in data: + sensor_data = data['sensorDataPoints'] + temperature = float( + sensor_data['insideTemperature']['celsius']) + humidity = float( + sensor_data['humidity']['percentage']) + setting = 0 + + # 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']) + + unit = TEMP_CELSIUS + + self._cur_temp = self.hass.config.units.temperature( + temperature, unit) + + self._target_temp = self.hass.config.units.temperature( + setting, unit) + + self._cur_humidity = humidity + + if 'tadoMode' in data: + mode = data['tadoMode'] + self._is_away = mode == "AWAY" + + if 'setting' in data: + power = data['setting']['power'] + if power == "OFF": + self._current_operation = CONST_MODE_OFF + self._device_is_active = False + else: + self._device_is_active = True + + if 'overlay' in data and data['overlay'] is not None: + overlay = True + termination = data['overlay']['termination']['type'] + else: + overlay = False + termination = "" - if update_ha: - self.schedule_update_ha_state() + # if you set mode manualy to off, there will be an overlay + # and a termination, but we want to see the mode "OFF" - except ValueError: - _LOGGER.error("Unable to update from sensor: %s", entity_type) + if overlay and self._device_is_active: + # there is an overlay the device is on + self._overlay_mode = termination + self._current_operation = termination + else: + # there is no overlay, the mode will always be + # "SMART_SCHEDULE" + self._overlay_mode = CONST_MODE_SMART_SCHEDULE + self._current_operation = CONST_MODE_SMART_SCHEDULE def _control_heating(self): """Send new target temperature to mytado.""" @@ -250,67 +263,23 @@ def _control_heating(self): return if self._current_operation == CONST_MODE_SMART_SCHEDULE: - _LOGGER.info('Switching mytado.com to SCHEDULE (default) for zone %s', self.zone_name) - self._tado.resetZoneOverlay(self.zone_id) + _LOGGER.info('Switching mytado.com to SCHEDULE (default) ' + 'for zone %s', self.zone_name) + self._store.reset_zone_overlay(self.zone_id) self._overlay_mode = self._current_operation return if self._current_operation == CONST_MODE_OFF: - _LOGGER.info('Switching mytado.com to OFF for zone %s', self.zone_name) - self._tado.setZoneOverlay(self.zone_id, CONST_DEFAULT_OFF_MODE) + _LOGGER.info('Switching mytado.com to OFF for zone %s', + self.zone_name) + self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL) self._overlay_mode = self._current_operation return - _LOGGER.info("Switching mytado.com to %s mode for zone %s", + _LOGGER.info('Switching mytado.com to %s mode for zone %s', self._current_operation, self.zone_name) - self._tado.setZoneOverlay(self.zone_id, self._current_operation, self._target_temp) + self._store.set_zone_overlay(self.zone_id, + self._current_operation, + self._target_temp) self._overlay_mode = self._current_operation - -class TadoData(object): - """Tado data object to control the tado functionality""" - def __init__(self, tado): - self._tado = tado - self._tracking_active = False - - self.sensors = [] - - def create_climate_device(self, hass, name, tado_id): - """create a climate device""" - capabilities = self._tado.getCapabilities(tado_id) - - min_temp = float(capabilities["temperatures"]["celsius"]["min"]) - max_temp = float(capabilities["temperatures"]["celsius"]["max"]) - target_temp = 21 - ac_mode = capabilities["type"] != "HEATING" - - device_id = 'climate {} {}'.format(name, tado_id) - device = TadoClimate(self._tado, name, tado_id, - min_temp, max_temp, target_temp, ac_mode) - sensor = { - "id" : device_id, - "device" : device, - "sensors" : [] - } - - self.sensors.append(sensor) - - for sensor_type in SENSOR_TYPES: - entity_id = 'sensor.{} {}'.format(name, sensor_type).lower().replace(" ", "_") - sensor["sensors"].append(entity_id) - - sensor_state = hass.states.get(entity_id) - if sensor_state: - device.update_state(sensor_type, sensor_state, False) - - return device - - def activate_tracking(self, hass): - """activate tracking of dependend sensors""" - if self._tracking_active is False: - for data in self.sensors: - for entity_id in data["sensors"]: - track_state_change(hass, entity_id, data["device"].sensor_changed) - _LOGGER.info("activated state tracking for %s.", entity_id) - - self._tracking_active = True diff --git a/custom_components/sensor/tado_v1.py b/custom_components/sensor/tado_v1.py index 45c00ee..b07954e 100644 --- a/custom_components/sensor/tado_v1.py +++ b/custom_components/sensor/tado_v1.py @@ -1,79 +1,96 @@ -""" -tado component to create some sensors for each zone -""" +"""tado component to create some sensors for each zone.""" import logging -from datetime import timedelta from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle - -# DOMAIN = 'tado_v1' +from homeassistant.components.tado import ( + DATA_TADO) _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = ['temperature', 'humidity', 'power', 'link', 'heating', 'tado mode', 'overlay'] -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the sensor platform.""" - # pylint: disable=W0613 - - # get the PyTado object from the hub component - tado = hass.data['Mytado'] + # get the PyTado object from the hub component + tado = hass.data[DATA_TADO] try: - zones = tado.getZones() + zones = tado.get_zones() except RuntimeError: _LOGGER.error("Unable to get zone info from mytado") return False - tado_data = TadoData(tado, MIN_TIME_BETWEEN_SCANS) - sensor_items = [] for zone in zones: if zone['type'] == 'HEATING': for variable in SENSOR_TYPES: - sensor_items.append(tado_data.create_zone_sensor( - zone, zone['name'], zone['id'], variable)) + sensor_items.append(create_zone_sensor( + tado, zone, zone['name'], zone['id'], + variable)) - me_data = tado.getMe() - sensor_items.append(tado_data.create_device_sensor( - me_data, me_data['homes'][0]['name'], me_data['homes'][0]['id'], "tado bridge status")) - - tado_data.update() + me_data = tado.get_me() + sensor_items.append(create_device_sensor( + tado, me_data, me_data['homes'][0]['name'], + me_data['homes'][0]['id'], "tado bridge status")) if len(sensor_items) > 0: - add_devices(sensor_items) + add_devices(sensor_items, True) return True else: return False + +def create_zone_sensor(tado, zone, name, zone_id, variable): + """Create a zone sensor.""" + data_id = 'zone {} {}'.format(name, zone_id) + + tado.add_sensor(data_id, { + "zone": zone, + "name": name, + "id": zone_id, + "data_id": data_id + }) + + return TadoSensor(tado, name, zone_id, variable, data_id) + + +def create_device_sensor(tado, device, name, device_id, variable): + """Create a device sensor.""" + data_id = 'device {} {}'.format(name, device_id) + + tado.add_sensor(data_id, { + "device": device, + "name": name, + "id": device_id, + "data_id": data_id + }) + + return TadoSensor(tado, name, device_id, variable, data_id) + + class TadoSensor(Entity): """Representation of a tado Sensor.""" - # pylint: disable=R0902, R0913 - def __init__(self, tado_data, zone_name, zone_id, zone_variable, data_id): - self._tado_data = tado_data + def __init__(self, store, zone_name, zone_id, zone_variable, data_id): + """Initialization of TadoSensor class.""" + self._store = store + self.zone_name = zone_name self.zone_id = zone_id self.zone_variable = zone_variable + self._unique_id = '{} {}'.format(zone_variable, zone_id) self._data_id = data_id self._state = None self._state_attributes = None - @property - def should_poll(self): - """Polling needed for tado sensors.""" - return True - @property def unique_id(self): - """Return the unique id""" + """Return the unique id.""" return self._unique_id @property @@ -87,7 +104,7 @@ def state(self): return self._state @property - def state_attributes(self): + def device_state_attributes(self): """Return the state attributes.""" return self._state_attributes @@ -95,7 +112,7 @@ def state_attributes(self): def unit_of_measurement(self): """Return the unit of measurement.""" if self.zone_variable == "temperature": - return TEMP_CELSIUS + return self.hass.config.units.temperature_unit elif self.zone_variable == "humidity": return '%' elif self.zone_variable == "heating": @@ -103,39 +120,51 @@ def unit_of_measurement(self): @property def icon(self): - """Icon for the sensor""" + """Icon for the sensor.""" if self.zone_variable == "temperature": return 'mdi:thermometer' elif self.zone_variable == "humidity": return 'mdi:water-percent' def update(self): - """Update method called when should_poll is true""" - self._tado_data.update() + """Update method called when should_poll is true.""" + self._store.update() - self.push_update(self._tado_data.get_data(self._data_id), True) + data = self._store.get_data(self._data_id) + unit = TEMP_CELSIUS - def push_update(self, data, update_ha): - """Push the update to the current object""" # pylint: disable=R0912 if self.zone_variable == 'temperature': if 'sensorDataPoints' in data: - self._state = float(data['sensorDataPoints']['insideTemperature']['celsius']) + sensor_data = data['sensorDataPoints'] + temperature = float( + sensor_data['insideTemperature']['celsius']) + + self._state = self.hass.config.units.temperature( + temperature, unit) self._state_attributes = { - "time": data['sensorDataPoints']['insideTemperature']['timestamp'], - "setting": 0 # setting is used in climate device + "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: - self._state_attributes["setting"] = float( + 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) + elif self.zone_variable == 'humidity': if 'sensorDataPoints' in data: - self._state = float(data['sensorDataPoints']['humidity']['percentage']) + sensor_data = data['sensorDataPoints'] + self._state = float( + sensor_data['humidity']['percentage']) self._state_attributes = { - "time": data['sensorDataPoints']['humidity']['timestamp'], + "time": sensor_data['humidity']['timestamp'], } elif self.zone_variable == 'power': @@ -148,9 +177,11 @@ def push_update(self, data, update_ha): elif self.zone_variable == 'heating': if 'activityDataPoints' in data: - self._state = float(data['activityDataPoints']['heatingPower']['percentage']) + activity_data = data['activityDataPoints'] + self._state = float( + activity_data['heatingPower']['percentage']) self._state_attributes = { - "time": data['activityDataPoints']['heatingPower']['timestamp'], + "time": activity_data['heatingPower']['timestamp'], } elif self.zone_variable == 'tado bridge status': @@ -163,7 +194,6 @@ def push_update(self, data, update_ha): elif self.zone_variable == 'overlay': if 'overlay' in data and data['overlay'] is not None: - # pylint: disable=R0204 self._state = True self._state_attributes = { "termination": data['overlay']['termination']['type'], @@ -171,74 +201,3 @@ def push_update(self, data, update_ha): else: self._state = False self._state_attributes = {} - - if update_ha: - super().update_ha_state() - -class TadoData(object): - """Tado data object to control the tado functionality""" - def __init__(self, tado, interval): - self._tado = tado - - self.sensors = {} - self.data = {} - - # Apply throttling to methods using configured interval - self.update = Throttle(interval)(self._update) - - def create_zone_sensor(self, zone, name, zone_id, variable): - """create a zone sensor""" - data_id = 'zone {} {}'.format(name, zone_id) - - self.sensors[data_id] = { - "zone" : zone, - "name" : name, - "id" : zone_id, - "data_id" : data_id - } - self.data[data_id] = None - - return TadoSensor(self, name, zone_id, variable, data_id) - - def create_device_sensor(self, device, name, device_id, variable): - """create a device sensor""" - data_id = 'device {} {}'.format(name, device_id) - - self.sensors[data_id] = { - "device" : device, - "name" : name, - "id" : device_id, - "data_id" : data_id - } - self.data[data_id] = None - - return TadoSensor(self, name, device_id, variable, data_id) - - 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 _update(self): - """update the internal data-array from mytado.com""" - for data_id, sensor in self.sensors.items(): - data = None - - try: - if "zone" in sensor: - _LOGGER.info("querying mytado.com for zone %s %s", - sensor["id"], sensor["name"]) - data = self._tado.getState(sensor["id"]) - if "device" in sensor: - _LOGGER.info("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 diff --git a/custom_components/tado_v1.py b/custom_components/tado_v1.py index 9f8c646..e1b4ad1 100644 --- a/custom_components/tado_v1.py +++ b/custom_components/tado_v1.py @@ -1,42 +1,51 @@ """ -main file for the (unofficial) tado component +Support for the (unofficial) tado api. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/tado/ """ import logging import urllib +from datetime import timedelta +import voluptuous as vol + from homeassistant.helpers.discovery import load_platform from homeassistant.helpers import config_validation as cv - -import voluptuous as vol +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD) +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) DOMAIN = 'tado_v1' -REQUIREMENTS = ['https://github.com/wmalgadey/PyTado/archive/0.1.10.zip#PyTado==0.1.10'] +REQUIREMENTS = ['https://github.com/wmalgadey/PyTado/archive/' + '0.1.10.zip#' + 'PyTado==0.1.10'] -TADO_V1_COMPONENTS = [ +TADO_COMPONENTS = [ 'sensor', 'climate' ] -CONF_MYTADO_USERNAME = 'mytado_username' -CONF_MYTADO_PASSWORD = 'mytado_password' - CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_MYTADO_USERNAME, default=''): cv.string, - vol.Required(CONF_MYTADO_PASSWORD, default=''): cv.string + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string }) }, extra=vol.ALLOW_EXTRA) +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) + +DATA_TADO = 'tado_data' + def setup(hass, config): """Your controller/hub specific code.""" - - username = config[DOMAIN][CONF_MYTADO_USERNAME] - password = config[DOMAIN][CONF_MYTADO_PASSWORD] + username = config[DOMAIN][CONF_USERNAME] + password = config[DOMAIN][CONF_PASSWORD] from PyTado.interface import Tado @@ -46,9 +55,77 @@ def setup(hass, config): _LOGGER.error("Unable to connect to mytado with username and password") return False - hass.data['Mytado'] = tado + hass.data[DATA_TADO] = TadoDataStore(tado) - for component in TADO_V1_COMPONENTS: + for component in TADO_COMPONENTS: load_platform(hass, component, DOMAIN, {}, config) return True + + +class TadoDataStore: + """An object to store the tado data.""" + + def __init__(self, tado): + """Initialize Tado data store.""" + self.tado = tado + + self.sensors = {} + self.data = {} + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Update the internal data from mytado.com.""" + for data_id, sensor in self.sensors.items(): + data = None + + try: + if "zone" in sensor: + _LOGGER.info("querying mytado.com for zone %s %s", + sensor["id"], sensor["name"]) + data = self.tado.getState(sensor["id"]) + + if "device" in sensor: + _LOGGER.info("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): + """Wrapper for getZones().""" + return self.tado.getZones() + + def get_capabilities(self, tado_id): + """Wrapper for getCapabilities(..).""" + return self.tado.getCapabilities(tado_id) + + def get_me(self): + """Wrapper for getMet().""" + return self.tado.getMe() + + def reset_zone_overlay(self, zone_id): + """Wrapper for resetZoneOverlay(..).""" + return self.tado.resetZoneOverlay(zone_id) + + def set_zone_overlay(self, zone_id, mode, temperature): + """Wrapper for setZoneOverlay(..).""" + return self.tado.setZoneOverlay(zone_id, mode, temperature) From 0880dc8b89447df588f916f586b78f7f24c19ac8 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Mon, 20 Mar 2017 23:03:26 +0100 Subject: [PATCH 2/8] no "base" component tado_v1 * could be found in custom component --- custom_components/climate/tado_v1.py | 14 ++++++++++++-- custom_components/sensor/tado_v1.py | 9 +++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/custom_components/climate/tado_v1.py b/custom_components/climate/tado_v1.py index 6f8247b..4aec8af 100644 --- a/custom_components/climate/tado_v1.py +++ b/custom_components/climate/tado_v1.py @@ -8,8 +8,8 @@ ClimateDevice) from homeassistant.const import ( ATTR_TEMPERATURE) -from homeassistant.components.tado import ( - DATA_TADO) + +DATA_TADO = 'tado_data' CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode CONST_MODE_OFF = "OFF" # Switch off heating in a zone @@ -101,6 +101,7 @@ def __init__(self, store, zone_name, zone_id, data_id, self._active = False self._device_is_active = False + self._unit = TEMP_CELSIUS self._cur_temp = None self._cur_humidity = None self._is_away = False @@ -142,6 +143,11 @@ def is_away_mode_on(self): """Return true if away mode is on.""" return self._is_away + @property + def temperature_unit(self): + """The unit of measurement used by the platform.""" + return self._unit + @property def target_temperature(self): """Return the temperature we try to reach.""" @@ -194,6 +200,10 @@ def update(self): self._store.update() data = self._store.get_data(self._data_id) + + if data is None: + _LOGGER.error('no data recieved for %s', self.zone_name) + return if 'sensorDataPoints' in data: sensor_data = data['sensorDataPoints'] diff --git a/custom_components/sensor/tado_v1.py b/custom_components/sensor/tado_v1.py index b07954e..2db24db 100644 --- a/custom_components/sensor/tado_v1.py +++ b/custom_components/sensor/tado_v1.py @@ -4,8 +4,8 @@ from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from homeassistant.components.tado import ( - DATA_TADO) + +DATA_TADO = 'tado_data' _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = ['temperature', 'humidity', 'power', @@ -131,6 +131,11 @@ def update(self): self._store.update() data = self._store.get_data(self._data_id) + + if data is None: + _LOGGER.error('no data recieved for %s', self.zone_name) + return + unit = TEMP_CELSIUS # pylint: disable=R0912 From 0966b4821d485ce90f4424008b058566d978b149 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Mon, 20 Mar 2017 23:04:03 +0100 Subject: [PATCH 3/8] had to create a copy of sensors * it is beeing queried to fast, so it will be changed while iterating --- custom_components/tado_v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/tado_v1.py b/custom_components/tado_v1.py index e1b4ad1..7cf58da 100644 --- a/custom_components/tado_v1.py +++ b/custom_components/tado_v1.py @@ -76,7 +76,7 @@ def __init__(self, tado): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Update the internal data from mytado.com.""" - for data_id, sensor in self.sensors.items(): + for data_id, sensor in list(self.sensors.items()): data = None try: From 3e021396ba88f48633d3f16f2d802130012c87c5 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Tue, 21 Mar 2017 20:03:59 +0100 Subject: [PATCH 4/8] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2cf1cd0..f9b747f 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ Custom home-assistant component for tado (using my fork of PyTado for a py3 comp It is highly inspired by https://community.home-assistant.io/t/tado-api-json/272/5 and the comments by diplix (https://community.home-assistant.io/users/diplix) -It is called `tado_v1` because it is build upon the unofficial API used by the myTado.com-Webapp. - +It is called `tado_v1` because it is build upon the unofficial API used by the myTado.com-Webapp. It will be merged as `tado` component in hass main repository, but I will leave this here for those willing to use a custom component. # Howto use I created a new custom_component which adds multiple sensors for every zone in myTado.com (not for every device) @@ -19,8 +18,8 @@ For hass manual installation it is: ## Edit configuration.yaml ``` tado_v1: - mytado_username: <.. your username ..> - mytado_password: <.. your password ..> + username: <.. your username ..> + password: <.. your password ..> ``` ## Use the new sensors in home-assistant From 81436a46ee76feaa56b60db8935ac5371ba22c28 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Wed, 29 Mar 2017 22:55:02 +0200 Subject: [PATCH 5/8] merged the current hass changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * reverted the unit/current_state to °C and the actual temperature --- custom_components/climate/tado_v1.py | 120 ++++++++++++++------------- custom_components/sensor/tado_v1.py | 8 +- custom_components/tado_v1.py | 50 +++++------ 3 files changed, 88 insertions(+), 90 deletions(-) diff --git a/custom_components/climate/tado_v1.py b/custom_components/climate/tado_v1.py index 4aec8af..582f6da 100644 --- a/custom_components/climate/tado_v1.py +++ b/custom_components/climate/tado_v1.py @@ -1,40 +1,40 @@ -"""tado component to create a climate device for each zone.""" +""" +Tado component to create a climate device for each zone. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.tado/ +""" import logging from homeassistant.const import TEMP_CELSIUS +from homeassistant.components.climate import ClimateDevice +from homeassistant.const import ATTR_TEMPERATURE +DATA_TADO = 'tado_v1_data' -from homeassistant.components.climate import ( - ClimateDevice) -from homeassistant.const import ( - ATTR_TEMPERATURE) - -DATA_TADO = 'tado_data' +_LOGGER = logging.getLogger(__name__) -CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode -CONST_MODE_OFF = "OFF" # Switch off heating in a zone +CONST_MODE_SMART_SCHEDULE = 'SMART_SCHEDULE' # Default mytado mode +CONST_MODE_OFF = 'OFF' # Switch off heating in a zone # When we change the temperature setting, we need an overlay mode # wait until tado changes the mode automatic -CONST_OVERLAY_TADO_MODE = "TADO_MODE" +CONST_OVERLAY_TADO_MODE = 'TADO_MODE' # the user has change the temperature or mode manually -CONST_OVERLAY_MANUAL = "MANUAL" +CONST_OVERLAY_MANUAL = 'MANUAL' # the temperature will be reset after a timespan -CONST_OVERLAY_TIMER = "TIMER" +CONST_OVERLAY_TIMER = 'TIMER' OPERATION_LIST = { - CONST_OVERLAY_MANUAL: "Manual", - CONST_OVERLAY_TIMER: "Timer", - CONST_OVERLAY_TADO_MODE: "Tado mode", - CONST_MODE_SMART_SCHEDULE: "Smart schedule", - CONST_MODE_OFF: "Off"} - -_LOGGER = logging.getLogger(__name__) + CONST_OVERLAY_MANUAL: 'Manual', + CONST_OVERLAY_TIMER: 'Timer', + CONST_OVERLAY_TADO_MODE: 'Tado mode', + CONST_MODE_SMART_SCHEDULE: 'Smart schedule', + CONST_MODE_OFF: 'Off', +} def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the climate platform.""" - # get the PyTado object from the hub component + """Set up the Tado climate platform.""" tado = hass.data[DATA_TADO] try: @@ -45,10 +45,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): climate_devices = [] for zone in zones: - climate_devices.append(create_climate_device(tado, hass, - zone, - zone['name'], - zone['id'])) + climate_devices.append(create_climate_device( + tado, hass, zone, zone['name'], zone['id'])) if len(climate_devices) > 0: add_devices(climate_devices, True) @@ -58,13 +56,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def create_climate_device(tado, hass, zone, name, zone_id): - """Create a climate device.""" + """Create a Tado climate device.""" capabilities = tado.get_capabilities(zone_id) unit = TEMP_CELSIUS - min_temp = float(capabilities["temperatures"]["celsius"]["min"]) - max_temp = float(capabilities["temperatures"]["celsius"]["max"]) - ac_mode = capabilities["type"] != "HEATING" + min_temp = float(capabilities['temperatures']['celsius']['min']) + max_temp = float(capabilities['temperatures']['celsius']['max']) + ac_mode = capabilities['type'] != 'HEATING' data_id = 'zone {} {}'.format(name, zone_id) device = TadoClimate(tado, @@ -74,10 +72,10 @@ def create_climate_device(tado, hass, zone, name, zone_id): ac_mode) tado.add_sensor(data_id, { - "id": zone_id, - "zone": zone, - "name": name, - "climate": device + 'id': zone_id, + 'zone': zone, + 'name': name, + 'climate': device }) return device @@ -89,7 +87,7 @@ class TadoClimate(ClimateDevice): def __init__(self, store, zone_name, zone_id, data_id, min_temp, max_temp, ac_mode, tolerance=0.3): - """Initialization of TadoClimate device.""" + """Initialization of Tado climate device.""" self._store = store self._data_id = data_id @@ -118,6 +116,11 @@ def name(self): """Return the name of the sensor.""" return self.zone_name + @property + def state(self): + """Return the current temperature as the state, instead of operation_mode""" + return self._cur_temp + @property def current_humidity(self): """Return the current humidity.""" @@ -138,16 +141,16 @@ def operation_list(self): """List of available operation modes (readable).""" return list(OPERATION_LIST.values()) - @property - def is_away_mode_on(self): - """Return true if away mode is on.""" - return self._is_away - @property def temperature_unit(self): """The unit of measurement used by the platform.""" return self._unit + @property + def is_away_mode_on(self): + """Return true if away mode is on.""" + return self._is_away + @property def target_temperature(self): """Return the temperature we try to reach.""" @@ -200,9 +203,9 @@ def update(self): self._store.update() data = self._store.get_data(self._data_id) - + if data is None: - _LOGGER.error('no data recieved for %s', self.zone_name) + _LOGGER.debug("Recieved no data for zone %s", self.zone_name) return if 'sensorDataPoints' in data: @@ -231,11 +234,11 @@ def update(self): if 'tadoMode' in data: mode = data['tadoMode'] - self._is_away = mode == "AWAY" + self._is_away = mode == 'AWAY' if 'setting' in data: power = data['setting']['power'] - if power == "OFF": + if power == 'OFF': self._current_operation = CONST_MODE_OFF self._device_is_active = False else: @@ -248,48 +251,47 @@ def update(self): overlay = False termination = "" - # if you set mode manualy to off, there will be an overlay - # and a termination, but we want to see the mode "OFF" + # If you set mode manualy to off, there will be an overlay + # and a termination, but we want to see the mode "OFF" if overlay and self._device_is_active: - # there is an overlay the device is on + # There is an overlay the device is on self._overlay_mode = termination self._current_operation = termination else: - # there is no overlay, the mode will always be - # "SMART_SCHEDULE" + # There is no overlay, the mode will always be + # "SMART_SCHEDULE" self._overlay_mode = CONST_MODE_SMART_SCHEDULE self._current_operation = CONST_MODE_SMART_SCHEDULE def _control_heating(self): """Send new target temperature to mytado.""" - if not self._active and None not in (self._cur_temp, - self._target_temp): + if not self._active and None not in ( + self._cur_temp, self._target_temp): self._active = True - _LOGGER.info('Obtained current and target temperature. ' - 'tado thermostat active.') + _LOGGER.info("Obtained current and target temperature. " + "Tado thermostat active") if not self._active or self._current_operation == self._overlay_mode: return if self._current_operation == CONST_MODE_SMART_SCHEDULE: - _LOGGER.info('Switching mytado.com to SCHEDULE (default) ' - 'for zone %s', self.zone_name) + _LOGGER.info("Switching mytado.com to SCHEDULE (default) " + "for zone %s", self.zone_name) self._store.reset_zone_overlay(self.zone_id) self._overlay_mode = self._current_operation return if self._current_operation == CONST_MODE_OFF: - _LOGGER.info('Switching mytado.com to OFF for zone %s', + _LOGGER.info("Switching mytado.com to OFF for zone %s", self.zone_name) self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL) self._overlay_mode = self._current_operation return - _LOGGER.info('Switching mytado.com to %s mode for zone %s', + _LOGGER.info("Switching mytado.com to %s mode for zone %s", self._current_operation, self.zone_name) - self._store.set_zone_overlay(self.zone_id, - self._current_operation, - self._target_temp) + self._store.set_zone_overlay( + self.zone_id, self._current_operation, self._target_temp) self._overlay_mode = self._current_operation diff --git a/custom_components/sensor/tado_v1.py b/custom_components/sensor/tado_v1.py index 2db24db..093ba3d 100644 --- a/custom_components/sensor/tado_v1.py +++ b/custom_components/sensor/tado_v1.py @@ -4,8 +4,7 @@ from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity - -DATA_TADO = 'tado_data' +DATA_TADO = 'tado_v1_data' _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = ['temperature', 'humidity', 'power', @@ -131,9 +130,10 @@ def update(self): self._store.update() data = self._store.get_data(self._data_id) - + if data is None: - _LOGGER.error('no data recieved for %s', self.zone_name) + _LOGGER.debug('Recieved no data for zone %s', + self.zone_name) return unit = TEMP_CELSIUS diff --git a/custom_components/tado_v1.py b/custom_components/tado_v1.py index 7cf58da..b7758c9 100644 --- a/custom_components/tado_v1.py +++ b/custom_components/tado_v1.py @@ -1,30 +1,30 @@ """ -Support for the (unofficial) tado api. +Support for the (unofficial) Tado api. For more details about this component, please refer to the documentation at https://home-assistant.io/components/tado/ """ - import logging import urllib - from datetime import timedelta + import voluptuous as vol from homeassistant.helpers.discovery import load_platform from homeassistant.helpers import config_validation as cv -from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD) +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.util import Throttle +REQUIREMENTS = ['https://github.com/wmalgadey/PyTado/archive/' + '0.1.10.zip#' + 'PyTado==0.1.10'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'tado_v1' +DATA_TADO = 'tado_data' +DOMAIN = 'tado' -REQUIREMENTS = ['https://github.com/wmalgadey/PyTado/archive/' - '0.1.10.zip#' - 'PyTado==0.1.10'] +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) TADO_COMPONENTS = [ 'sensor', 'climate' @@ -37,13 +37,9 @@ }) }, extra=vol.ALLOW_EXTRA) -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) - -DATA_TADO = 'tado_data' - def setup(hass, config): - """Your controller/hub specific code.""" + """Set up of the Tado component.""" username = config[DOMAIN][CONF_USERNAME] password = config[DOMAIN][CONF_PASSWORD] @@ -64,7 +60,7 @@ def setup(hass, config): class TadoDataStore: - """An object to store the tado data.""" + """An object to store the Tado data.""" def __init__(self, tado): """Initialize Tado data store.""" @@ -80,19 +76,19 @@ def update(self): data = None try: - if "zone" in sensor: - _LOGGER.info("querying mytado.com for zone %s %s", - sensor["id"], sensor["name"]) - data = self.tado.getState(sensor["id"]) - - if "device" in sensor: - _LOGGER.info("querying mytado.com for device %s %s", - sensor["id"], sensor["name"]) + if 'zone' in sensor: + _LOGGER.info("Querying mytado.com for zone %s %s", + sensor['id'], sensor['name']) + data = self.tado.getState(sensor['id']) + + if 'device' in sensor: + _LOGGER.info("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"]) + sensor['id'], sensor['id']) self.data[data_id] = data @@ -103,7 +99,7 @@ def add_sensor(self, data_id, sensor): def get_data(self, data_id): """Get the cached data.""" - data = {"error": "no data"} + data = {'error': 'no data'} if data_id in self.data: data = self.data[data_id] @@ -126,6 +122,6 @@ def reset_zone_overlay(self, zone_id): """Wrapper for resetZoneOverlay(..).""" return self.tado.resetZoneOverlay(zone_id) - def set_zone_overlay(self, zone_id, mode, temperature): + def set_zone_overlay(self, zone_id, mode, temperature=None, duration=None): """Wrapper for setZoneOverlay(..).""" - return self.tado.setZoneOverlay(zone_id, mode, temperature) + return self.tado.setZoneOverlay(zone_id, mode, temperature, duration) From b202633eec36bd072519bb4e53083f359c7b7f0a Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Sun, 14 May 2017 23:09:25 +0200 Subject: [PATCH 6/8] updated to hass version --- custom_components/climate/tado_v1.py | 119 ++++++++++++++++++--------- custom_components/sensor/tado_v1.py | 0 custom_components/tado_v1.py | 9 +- 3 files changed, 86 insertions(+), 42 deletions(-) mode change 100644 => 100755 custom_components/climate/tado_v1.py mode change 100644 => 100755 custom_components/sensor/tado_v1.py mode change 100644 => 100755 custom_components/tado_v1.py diff --git a/custom_components/climate/tado_v1.py b/custom_components/climate/tado_v1.py old mode 100644 new mode 100755 index 582f6da..9c57dec --- a/custom_components/climate/tado_v1.py +++ b/custom_components/climate/tado_v1.py @@ -24,6 +24,17 @@ # the temperature will be reset after a timespan CONST_OVERLAY_TIMER = 'TIMER' +CONST_MODE_FAN_HIGH = 'HIGH' +CONST_MODE_FAN_MIDDLE = 'MIDDLE' +CONST_MODE_FAN_LOW = 'LOW' + +FAN_MODES_LIST = { + CONST_MODE_FAN_HIGH: 'High', + CONST_MODE_FAN_MIDDLE: 'Middle', + CONST_MODE_FAN_LOW: 'Low', + CONST_MODE_OFF: 'Off', +} + OPERATION_LIST = { CONST_OVERLAY_MANUAL: 'Manual', CONST_OVERLAY_TIMER: 'Timer', @@ -60,9 +71,14 @@ def create_climate_device(tado, hass, zone, name, zone_id): capabilities = tado.get_capabilities(zone_id) unit = TEMP_CELSIUS - min_temp = float(capabilities['temperatures']['celsius']['min']) - max_temp = float(capabilities['temperatures']['celsius']['max']) - ac_mode = capabilities['type'] != 'HEATING' + ac_mode = capabilities['type'] == 'AIR_CONDITIONING' + + if ac_mode: + min_temp = float(capabilities['HEAT']['temperatures']['celsius']['min']) + max_temp = float(capabilities['HEAT']['temperatures']['celsius']['max']) + else: + min_temp = float(capabilities['temperatures']['celsius']['min']) + max_temp = float(capabilities['temperatures']['celsius']['max']) data_id = 'zone {} {}'.format(name, zone_id) device = TadoClimate(tado, @@ -107,7 +123,9 @@ def __init__(self, store, zone_name, zone_id, data_id, self._max_temp = max_temp self._target_temp = None self._tolerance = tolerance + self._cooling = False + self._current_fan = CONST_MODE_OFF self._current_operation = CONST_MODE_SMART_SCHEDULE self._overlay_mode = CONST_MODE_SMART_SCHEDULE @@ -116,11 +134,6 @@ def name(self): """Return the name of the sensor.""" return self.zone_name - @property - def state(self): - """Return the current temperature as the state, instead of operation_mode""" - return self._cur_temp - @property def current_humidity(self): """Return the current humidity.""" @@ -134,13 +147,32 @@ def current_temperature(self): @property def current_operation(self): """Return current readable operation mode.""" - return OPERATION_LIST.get(self._current_operation) + if self._cooling: + return "Cooling" + else: + return OPERATION_LIST.get(self._current_operation) @property def operation_list(self): """List of available operation modes (readable).""" return list(OPERATION_LIST.values()) + @property + def current_fan_mode(self): + """Return the fan setting.""" + if self.ac_mode: + return FAN_MODES_LIST.get(self._current_fan) + else: + return None + + @property + def fan_list(self): + """List of available fan modes.""" + if self.ac_mode: + return list(FAN_MODES_LIST.values()) + else: + return None + @property def temperature_unit(self): """The unit of measurement used by the platform.""" @@ -210,27 +242,27 @@ def update(self): if 'sensorDataPoints' in data: sensor_data = data['sensorDataPoints'] - temperature = float( - sensor_data['insideTemperature']['celsius']) - humidity = float( - sensor_data['humidity']['percentage']) - setting = 0 + + unit = TEMP_CELSIUS + + if 'insideTemperature' in sensor_data: + temperature = float( + sensor_data['insideTemperature']['celsius']) + self._cur_temp = self.hass.config.units.temperature( + temperature, unit) + + 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: setting = float( data['setting']['temperature']['celsius']) - - unit = TEMP_CELSIUS - - self._cur_temp = self.hass.config.units.temperature( - temperature, unit) - - self._target_temp = self.hass.config.units.temperature( - setting, unit) - - self._cur_humidity = humidity + self._target_temp = self.hass.config.units.temperature( + setting, unit) if 'tadoMode' in data: mode = data['tadoMode'] @@ -240,29 +272,39 @@ def update(self): 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 else: self._device_is_active = True - if 'overlay' in data and data['overlay'] is not None: - overlay = True - termination = data['overlay']['termination']['type'] - else: + if self._device_is_active: overlay = False - termination = "" + overlay_data = None + termination = self._current_operation + 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'] - # If you set mode manualy to off, there will be an overlay - # and a termination, but we want to see the mode "OFF" + if 'setting' in overlay_data: + cooling = overlay_data['setting']['mode'] == 'COOL' + fan_speed = overlay_data['setting']['fanSpeed'] + + # If you set mode manualy to off, there will be an overlay + # and a termination, but we want to see the mode "OFF" - if overlay and self._device_is_active: - # There is an overlay the device is on self._overlay_mode = termination self._current_operation = termination - else: - # There is no overlay, the mode will always be - # "SMART_SCHEDULE" - self._overlay_mode = CONST_MODE_SMART_SCHEDULE - self._current_operation = CONST_MODE_SMART_SCHEDULE + self._cooling = cooling + self._current_fan = fan_speed def _control_heating(self): """Send new target temperature to mytado.""" @@ -291,6 +333,7 @@ def _control_heating(self): _LOGGER.info("Switching mytado.com to %s mode for zone %s", self._current_operation, self.zone_name) + self._store.set_zone_overlay( self.zone_id, self._current_operation, self._target_temp) diff --git a/custom_components/sensor/tado_v1.py b/custom_components/sensor/tado_v1.py old mode 100644 new mode 100755 diff --git a/custom_components/tado_v1.py b/custom_components/tado_v1.py old mode 100644 new mode 100755 index b7758c9..d8c8495 --- a/custom_components/tado_v1.py +++ b/custom_components/tado_v1.py @@ -16,13 +16,13 @@ from homeassistant.util import Throttle REQUIREMENTS = ['https://github.com/wmalgadey/PyTado/archive/' - '0.1.10.zip#' - 'PyTado==0.1.10'] + '0.2.0.zip#' + 'PyTado==0.2.0'] _LOGGER = logging.getLogger(__name__) -DATA_TADO = 'tado_data' -DOMAIN = 'tado' +DATA_TADO = 'tado_v1_data' +DOMAIN = 'tado_v1' MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) @@ -47,6 +47,7 @@ def setup(hass, config): try: tado = Tado(username, password) + tado.setDebugging(True) except (RuntimeError, urllib.error.HTTPError): _LOGGER.error("Unable to connect to mytado with username and password") return False From b809016c690bf2838556478d3a50cbcf7883c824 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Tue, 13 Jun 2017 15:17:38 +0200 Subject: [PATCH 7/8] Changed Pytado version * supports new auth details --- custom_components/tado_v1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/tado_v1.py b/custom_components/tado_v1.py index d8c8495..c450fe9 100755 --- a/custom_components/tado_v1.py +++ b/custom_components/tado_v1.py @@ -16,8 +16,8 @@ from homeassistant.util import Throttle REQUIREMENTS = ['https://github.com/wmalgadey/PyTado/archive/' - '0.2.0.zip#' - 'PyTado==0.2.0'] + '0.2.1.zip#' + 'PyTado==0.2.1'] _LOGGER = logging.getLogger(__name__) From 479de2a57bd6c8008d0b8220cb5fcb5d8e3410a8 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Thu, 15 Jun 2017 14:09:44 +0200 Subject: [PATCH 8/8] changed .gitignore --- .gitignore | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fd20fdd..dc38629 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,11 @@ +# Hide some OS X stuff +.DS_Store +.AppleDouble +.LSOverride +Icon -*.pyc +# GITHUB Proposed Python stuff: +*.py[cod] + +# Visual Studio Code +.vscode