From 6c0a9381c0e48bfc22d8491c8654827a648f8d62 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Sat, 28 Dec 2024 21:14:29 +0100 Subject: [PATCH 01/13] changes to make tado x devices compatible with old tado devices --- PyTado/interface/api/hops_tado.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/PyTado/interface/api/hops_tado.py b/PyTado/interface/api/hops_tado.py index 74fed39..ad49e3b 100644 --- a/PyTado/interface/api/hops_tado.py +++ b/PyTado/interface/api/hops_tado.py @@ -6,6 +6,8 @@ import logging from typing import Any +from PyTado.const import TYPE_HEATING + from ...exceptions import TadoNotSupportedException from ...http import Action, Domain, Http, Mode, TadoRequest, TadoXRequest from ...logger import Logger @@ -81,6 +83,13 @@ def get_devices(self): request.device = serial_number device.update(self._http.request(request)) + # compatibility with my.tado.com API + device["shortSerialNo"] = device["serialNo"] + device["characteristics"]["capabilities"] = self.get_capabilities(device["serialNo"]) + device["name"] = device["roomName"] + device["id"] = device["roomId"] + device["generation"] = "LINE_X" + if "otherDevices" in rooms_and_devices: devices.append(rooms_and_devices["otherDevices"]) @@ -111,7 +120,12 @@ def get_zone_states(self): request = TadoXRequest() request.command = "rooms" - return self._http.request(request) + rooms_ = self._http.request(request) + + # make response my.tado.com compatible + zone_states = {"zoneStates": {"id": room["id"], "name": room["name"]} for room in rooms_} + + return {**zone_states, **rooms_} def get_state(self, zone): """ @@ -124,12 +138,17 @@ def get_state(self, zone): return data - @not_supported("This method is not currently supported by the Tado X API") def get_capabilities(self, zone): """ Gets current capabilities of zone. """ - pass + + _LOGGER.warning( + "get_capabilities is not supported by Tado X API. " + "We currently always return type heating." + ) + + return {"type": TYPE_HEATING} def get_climate(self, zone): """ From 75006f77a5e42dff1ac623756666db9522f1d957 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Sat, 28 Dec 2024 21:15:35 +0100 Subject: [PATCH 02/13] fixed serial no --- PyTado/interface/api/hops_tado.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyTado/interface/api/hops_tado.py b/PyTado/interface/api/hops_tado.py index ad49e3b..e3eab1d 100644 --- a/PyTado/interface/api/hops_tado.py +++ b/PyTado/interface/api/hops_tado.py @@ -84,7 +84,7 @@ def get_devices(self): device.update(self._http.request(request)) # compatibility with my.tado.com API - device["shortSerialNo"] = device["serialNo"] + device["shortSerialNo"] = serial_number device["characteristics"]["capabilities"] = self.get_capabilities(device["serialNo"]) device["name"] = device["roomName"] device["id"] = device["roomId"] From ea8ef12d694979c8a193559314e8f4b46278965b Mon Sep 17 00:00:00 2001 From: Moritz <88738242+Moritz-Schmidt@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:40:48 +0100 Subject: [PATCH 03/13] Refactoring the Tado Classes. (#128) Co-authored-by: Wolfgang Malgadey --- PyTado/interface/api/__init__.py | 3 +- PyTado/interface/api/base_tado.py | 391 ++++++++++++++++++++++++++++++ PyTado/interface/api/hops_tado.py | 23 +- PyTado/interface/api/my_tado.py | 283 +-------------------- PyTado/interface/interface.py | 2 +- 5 files changed, 406 insertions(+), 296 deletions(-) create mode 100644 PyTado/interface/api/base_tado.py diff --git a/PyTado/interface/api/__init__.py b/PyTado/interface/api/__init__.py index a5076fb..fe424d1 100644 --- a/PyTado/interface/api/__init__.py +++ b/PyTado/interface/api/__init__.py @@ -1,6 +1,7 @@ """Module for all API interfaces.""" +from .base_tado import TadoBase from .hops_tado import TadoX from .my_tado import Tado -__all__ = ["Tado", "TadoX"] +__all__ = ["Tado", "TadoX", "TadoBase"] diff --git a/PyTado/interface/api/base_tado.py b/PyTado/interface/api/base_tado.py new file mode 100644 index 0000000..d78905a --- /dev/null +++ b/PyTado/interface/api/base_tado.py @@ -0,0 +1,391 @@ +import datetime +import enum +import logging +from abc import ABCMeta, abstractmethod +from typing import Any + +from PyTado.exceptions import TadoNotSupportedException +from PyTado.http import Action, Domain, Endpoint, Http, TadoRequest +from PyTado.logger import Logger +from PyTado.zone.hops_zone import TadoXZone +from PyTado.zone.my_zone import TadoZone + +_LOGGER = Logger(__name__) + + +class Presence(enum.StrEnum): + """Presence Enum""" + + HOME = "HOME" + AWAY = "AWAY" + + +class Timetable(enum.IntEnum): + """Timetable Enum""" + + ONE_DAY = 0 + THREE_DAY = 1 + SEVEN_DAY = 2 + + +class TadoBase(metaclass=ABCMeta): + """Base class for Tado API classes. + Provides all common functionality for pre line X and line X systems.""" + + _http: Http + _auto_geofencing_supported: bool | None + + def __init__(self, http: Http, debug: bool = False): + if debug: + _LOGGER.setLevel(logging.DEBUG) + else: + _LOGGER.setLevel(logging.WARNING) + + self._http = http + + # Track whether the user's Tado instance supports auto-geofencing, + # set to None until explicitly set + self._auto_geofencing_supported = None + + def get_me(self): + """Gets home information.""" + + request = TadoRequest() + request.action = Action.GET + request.domain = Domain.ME + + return self._http.request(request) + + @abstractmethod + def get_devices(self) -> dict[str, Any] | list[Any]: # TODO: Typing + """Gets device information.""" + pass + + @abstractmethod + def get_zones(self) -> Any: # TODO: Typing + """Gets zones information.""" + pass + + @abstractmethod + def get_zone_state(self, zone: int) -> TadoZone | TadoXZone: + """Gets current state of Zone as a TadoZone object.""" + pass + + @abstractmethod + def get_zone_states(self) -> Any: # TODO: Typing + pass + + @abstractmethod + def get_state(self, zone: int) -> Any: # TODO: Typing + pass + + def get_home_state(self): + """Gets current state of Home.""" + # Without an auto assist skill, presence is not switched automatically. + # Instead a button is shown in the app - showHomePresenceSwitchButton, + # which is an indicator, that the homeState can be switched: + # {"presence":"HOME","showHomePresenceSwitchButton":true}. + # With an auto assist skill, a different button is present depending + # on geofencing state - showSwitchToAutoGeofencingButton is present + # when auto geofencing has been disabled due to the user selecting a + # mode manually: + # {'presence': 'HOME', 'presenceLocked': True, + # 'showSwitchToAutoGeofencingButton': True} + # showSwitchToAutoGeofencingButton is NOT present when auto + # geofencing has been enabled: + # {'presence': 'HOME', 'presenceLocked': False} + # In both scenarios with the auto assist skill, 'presenceLocked' + # indicates whether presence is current locked (manually set) to + # HOME or AWAY or not locked (automatically set based on geolocation) + + request = TadoRequest() + request.command = "state" + data = self._http.request(request) + + # Check whether Auto Geofencing is permitted via the presence of + # showSwitchToAutoGeofencingButton or currently enabled via the + # presence of presenceLocked = False + if "showSwitchToAutoGeofencingButton" in data: + self._auto_geofencing_supported = data["showSwitchToAutoGeofencingButton"] + elif "presenceLocked" in data: + if not data["presenceLocked"]: + self._auto_geofencing_supported = True + else: + self._auto_geofencing_supported = False + else: + self._auto_geofencing_supported = False + + return data + + def get_auto_geofencing_supported(self) -> bool: + """ + Return whether the Tado Home supports auto geofencing + """ + + if self._auto_geofencing_supported is None: + self.get_home_state() + + # get_home_state() narrows the type to bool + return self._auto_geofencing_supported # type: ignore + + @abstractmethod + def get_capabilities(self, zone: int): # TODO: typing + pass + + @abstractmethod + def get_climate(self, zone: int): # TODO: typing + pass + + def get_historic(self, zone, date): + """ + Gets historic information on given date for zone + """ + + try: + day = datetime.datetime.strptime(date, "%Y-%m-%d") + except ValueError as err: + raise ValueError("Incorrect date format, should be YYYY-MM-DD") from err + + request = TadoRequest() + request.command = f"zones/{zone:d}/dayReport?date={day.strftime('%Y-%m-%d')}" + return self._http.request(request) + + @abstractmethod + def get_timetable(self, zone: int) -> Timetable: + pass + + @abstractmethod + def set_timetable(self, zone: int, timetable: Timetable): + pass + + @abstractmethod + def get_schedule(self, zone: int, timetable: Timetable, day=None) -> dict[str, Any]: + pass + + @abstractmethod + def set_schedule(self, zone: int, timetable: Timetable, day, data): + pass + + @abstractmethod + def reset_zone_overlay(self, zone: int): + pass + + @abstractmethod + def set_zone_overlay( + self, + zone, + overlay_mode, + set_temp=None, + duration=None, + device_type="HEATING", + power="ON", + mode=None, + fan_speed=None, + swing=None, + fan_level=None, + vertical_swing=None, + horizontal_swing=None, + ): + pass + + @abstractmethod + def get_zone_overlay_default(self, zone: int): + pass + + def set_home(self) -> None: + """ + Sets HomeState to HOME + """ + + return self.change_presence(Presence.HOME) + + def set_away(self) -> None: + """ + Sets HomeState to AWAY + """ + + return self.change_presence(Presence.AWAY) + + def change_presence(self, presence: Presence) -> None: + """ + Sets HomeState to presence + """ + + request = TadoRequest() + request.command = "presenceLock" + request.action = Action.CHANGE + request.payload = {"homePresence": presence} + + self._http.request(request) + + def set_auto(self) -> dict[str, Any]: + """ + Sets HomeState to AUTO + """ + + # Only attempt to set Auto Geofencing if it is believed to be supported + if self._auto_geofencing_supported: + request = TadoRequest() + request.command = "presenceLock" + request.action = Action.RESET + + return self._http.request(request) + else: + raise TadoNotSupportedException("Auto mode is not known to be supported.") + + def get_window_state(self, zone): + """ + Returns the state of the window for zone + """ + + return {"openWindow": self.get_state(zone)["openWindow"]} + + def get_weather(self): + """ + Gets outside weather data + """ + + request = TadoRequest() + request.command = "weather" + + return self._http.request(request) + + def get_air_comfort(self): + """ + Gets air quality information + """ + + request = TadoRequest() + request.command = "airComfort" + + return self._http.request(request) + + def get_users(self): + """ + Gets active users in home + """ + + request = TadoRequest() + request.command = "users" + + return self._http.request(request) + + def get_mobile_devices(self): + """ + Gets information about mobile devices + """ + + request = TadoRequest() + request.command = "mobileDevices" + + return self._http.request(request) + + @abstractmethod + def get_open_window_detected(self, zone: int) -> dict[str, Any]: + pass + + @abstractmethod + def set_open_window(self, zone: int) -> dict[str, Any] | None: + pass + + @abstractmethod + def reset_open_window(self, zone: int) -> dict[str, Any] | None: + pass + + @abstractmethod + def get_device_info(self, device_id: int, cmd: str = "") -> dict[str, Any]: + pass + + @abstractmethod + def set_temp_offset( + self, device_id: int, offset: int = 0, measure: str = "celsius" + ) -> dict[str, Any]: + pass + + def get_eiq_tariffs(self): + """ + Get Energy IQ tariff history + """ + + request = TadoRequest() + request.command = "tariffs" + request.action = Action.GET + request.endpoint = Endpoint.EIQ + + return self._http.request(request) + + def get_eiq_meter_readings(self): + """ + Get Energy IQ meter readings + """ + + request = TadoRequest() + request.command = "meterReadings" + request.action = Action.GET + request.endpoint = Endpoint.EIQ + + return self._http.request(request) + + def set_eiq_meter_readings(self, date=datetime.datetime.now().strftime("%Y-%m-%d"), reading=0): + """ + Send Meter Readings to Tado, date format is YYYY-MM-DD, reading is without decimals + """ + + request = TadoRequest() + request.command = "meterReadings" + request.action = Action.SET + request.endpoint = Endpoint.EIQ + request.payload = {"date": date, "reading": reading} + + return self._http.request(request) + + def set_eiq_tariff( + self, + from_date=datetime.datetime.now().strftime("%Y-%m-%d"), + to_date=datetime.datetime.now().strftime("%Y-%m-%d"), + tariff=0, + unit="m3", + is_period=False, + ): + """ + Send Tariffs to Tado, date format is YYYY-MM-DD, + tariff is with decimals, unit is either m3 or kWh, + set is_period to true to set a period of price + """ + + tariff_in_cents = tariff * 100 + + if is_period: + payload = { + "tariffInCents": tariff_in_cents, + "unit": unit, + "startDate": from_date, + "endDate": to_date, + } + else: + payload = { + "tariffInCents": tariff_in_cents, + "unit": unit, + "startDate": from_date, + } + + request = TadoRequest() + request.command = "tariffs" + request.action = Action.SET + request.endpoint = Endpoint.EIQ + request.payload = payload + + return self._http.request(request) + + def get_running_times(self, date=datetime.datetime.now().strftime("%Y-%m-%d")) -> dict: + """ + Get the running times from the Minder API + """ + + request = TadoRequest() + request.command = "runningTimes" + request.action = Action.GET + request.endpoint = Endpoint.MINDER + request.params = {"from": date} + + return self._http.request(request) diff --git a/PyTado/interface/api/hops_tado.py b/PyTado/interface/api/hops_tado.py index e3eab1d..881e926 100644 --- a/PyTado/interface/api/hops_tado.py +++ b/PyTado/interface/api/hops_tado.py @@ -3,16 +3,15 @@ """ import functools -import logging from typing import Any from PyTado.const import TYPE_HEATING +from PyTado.interface.api.base_tado import TadoBase, Timetable from ...exceptions import TadoNotSupportedException from ...http import Action, Domain, Http, Mode, TadoRequest, TadoXRequest from ...logger import Logger from ...zone import TadoXZone, TadoZone -from .my_tado import Tado, Timetable def not_supported(reason): @@ -29,7 +28,7 @@ def wrapper(*args, **kwargs): _LOGGER = Logger(__name__) -class TadoX(Tado): +class TadoX(TadoBase): """Interacts with a Tado thermostat via hops.tado.com (Tado X) API. Example usage: http = Http('me@somewhere.com', 'mypasswd') @@ -43,22 +42,10 @@ def __init__( debug: bool = False, ): """Class Constructor""" - - super().__init__(http=http, debug=debug) - if not http.is_x_line: raise TadoNotSupportedException("TadoX is only usable with LINE_X Generation") - if debug: - _LOGGER.setLevel(logging.DEBUG) - else: - _LOGGER.setLevel(logging.WARNING) - - self._http = http - - # Track whether the user's Tado instance supports auto-geofencing, - # set to None until explicitly set - self._auto_geofencing_supported = None + super().__init__(http=http, debug=debug) def get_devices(self): """ @@ -171,6 +158,10 @@ def set_timetable(self, zone: int, timetable: Timetable) -> None: """ pass + @not_supported("Tado X API does not support historic data") + def get_timetable(self, zone: int): + pass + def get_schedule(self, zone: int, timetable: Timetable, day=None) -> dict[str, Any]: """ Get the JSON representation of the schedule for a zone. diff --git a/PyTado/interface/api/my_tado.py b/PyTado/interface/api/my_tado.py index 4044b85..7ca4955 100644 --- a/PyTado/interface/api/my_tado.py +++ b/PyTado/interface/api/my_tado.py @@ -1,37 +1,20 @@ """ PyTado interface implementation for app.tado.com. """ - import datetime -import enum -import logging from typing import Any -from ...exceptions import TadoException, TadoNotSupportedException -from ...http import Action, Domain, Endpoint, Http, Mode, TadoRequest +from PyTado.interface.api.base_tado import TadoBase, Timetable + +from ...exceptions import TadoException +from ...http import Action, Domain, Endpoint, Mode, TadoRequest from ...logger import Logger from ...zone import TadoZone - -class Timetable(enum.IntEnum): - """Timetable Enum""" - - ONE_DAY = 0 - THREE_DAY = 1 - SEVEN_DAY = 2 - - -class Presence(enum.StrEnum): - """Presence Enum""" - - HOME = "HOME" - AWAY = "AWAY" - - _LOGGER = Logger(__name__) -class Tado: +class Tado(TadoBase): """Interacts with a Tado thermostat via public my.tado.com API. Example usage: http = Http('me@somewhere.com', 'mypasswd') @@ -39,35 +22,6 @@ class Tado: t.get_climate(1) # Get climate, zone 1. """ - def __init__( - self, - http: Http, - debug: bool = False, - ): - """Class Constructor""" - - if debug: - _LOGGER.setLevel(logging.DEBUG) - else: - _LOGGER.setLevel(logging.WARNING) - - self._http = http - - # Track whether the user's Tado instance supports auto-geofencing, - # set to None until explicitly set - self._auto_geofencing_supported = None - - def get_me(self): - """ - Gets home information. - """ - - request = TadoRequest() - request.action = Action.GET - request.domain = Domain.ME - - return self._http.request(request) - def get_devices(self): """ Gets device information. @@ -118,56 +72,6 @@ def get_state(self, zone): return data - def get_home_state(self): - """ - Gets current state of Home. - """ - # Without an auto assist skill, presence is not switched automatically. - # Instead a button is shown in the app - showHomePresenceSwitchButton, - # which is an indicator, that the homeState can be switched: - # {"presence":"HOME","showHomePresenceSwitchButton":true}. - # With an auto assist skill, a different button is present depending - # on geofencing state - showSwitchToAutoGeofencingButton is present - # when auto geofencing has been disabled due to the user selecting a - # mode manually: - # {'presence': 'HOME', 'presenceLocked': True, - # 'showSwitchToAutoGeofencingButton': True} - # showSwitchToAutoGeofencingButton is NOT present when auto - # geofencing has been enabled: - # {'presence': 'HOME', 'presenceLocked': False} - # In both scenarios with the auto assist skill, 'presenceLocked' - # indicates whether presence is current locked (manually set) to - # HOME or AWAY or not locked (automatically set based on geolocation) - - request = TadoRequest() - request.command = "state" - data = self._http.request(request) - - # Check whether Auto Geofencing is permitted via the presence of - # showSwitchToAutoGeofencingButton or currently enabled via the - # presence of presenceLocked = False - if "showSwitchToAutoGeofencingButton" in data: - self._auto_geofencing_supported = data["showSwitchToAutoGeofencingButton"] - elif "presenceLocked" in data: - if not data["presenceLocked"]: - self._auto_geofencing_supported = True - else: - self._auto_geofencing_supported = False - else: - self._auto_geofencing_supported = False - - return data - - def get_auto_geofencing_supported(self): - """ - Return whether the Tado Home supports auto geofencing - """ - - if self._auto_geofencing_supported is None: - self.get_home_state() - - return self._auto_geofencing_supported - def get_capabilities(self, zone): """ Gets current capabilities of zone. @@ -204,20 +108,6 @@ def get_timetable(self, zone: int) -> Timetable: return Timetable(data["id"]) - def get_historic(self, zone, date): - """ - Gets historic information on given date for zone - """ - - try: - day = datetime.datetime.strptime(date, "%Y-%m-%d") - except ValueError as err: - raise ValueError("Incorrect date format, should be YYYY-MM-DD") from err - - request = TadoRequest() - request.command = f"zones/{zone:d}/dayReport?date={day.strftime('%Y-%m-%d')}" - return self._http.request(request) - def set_timetable(self, zone: int, timetable: Timetable) -> None: """ Set the Timetable type currently active @@ -261,46 +151,6 @@ def set_schedule(self, zone, timetable: Timetable, day, data): return self._http.request(request) - def get_weather(self): - """ - Gets outside weather data - """ - - request = TadoRequest() - request.command = "weather" - - return self._http.request(request) - - def get_air_comfort(self): - """ - Gets air quality information - """ - - request = TadoRequest() - request.command = "airComfort" - - return self._http.request(request) - - def get_users(self): - """ - Gets active users in home - """ - - request = TadoRequest() - request.command = "users" - - return self._http.request(request) - - def get_mobile_devices(self): - """ - Gets information about mobile devices - """ - - request = TadoRequest() - request.command = "mobileDevices" - - return self._http.request(request) - def reset_zone_overlay(self, zone): """ Delete current overlay @@ -374,54 +224,6 @@ def get_zone_overlay_default(self, zone: int): return self._http.request(request) - def set_home(self) -> None: - """ - Sets HomeState to HOME - """ - - return self.change_presence(Presence.HOME) - - def set_away(self) -> None: - """ - Sets HomeState to AWAY - """ - - return self.change_presence(Presence.AWAY) - - def change_presence(self, presence: Presence) -> None: - """ - Sets HomeState to presence - """ - - request = TadoRequest() - request.command = "presenceLock" - request.action = Action.CHANGE - request.payload = {"homePresence": presence} - - self._http.request(request) - - def set_auto(self) -> None: - """ - Sets HomeState to AUTO - """ - - # Only attempt to set Auto Geofencing if it is believed to be supported - if self._auto_geofencing_supported: - request = TadoRequest() - request.command = "presenceLock" - request.action = Action.RESET - - return self._http.request(request) - else: - raise TadoNotSupportedException("Auto mode is not known to be supported.") - - def get_window_state(self, zone): - """ - Returns the state of the window for zone - """ - - return {"openWindow": self.get_state(zone)["openWindow"]} - def get_open_window_detected(self, zone): """ Returns whether an open window is detected. @@ -487,81 +289,6 @@ def set_temp_offset(self, device_id, offset=0, measure="celsius"): return self._http.request(request) - def get_eiq_tariffs(self): - """ - Get Energy IQ tariff history - """ - - request = TadoRequest() - request.command = "tariffs" - request.action = Action.GET - request.endpoint = Endpoint.EIQ - - return self._http.request(request) - - def get_eiq_meter_readings(self): - """ - Get Energy IQ meter readings - """ - - request = TadoRequest() - request.command = "meterReadings" - request.action = Action.GET - request.endpoint = Endpoint.EIQ - - return self._http.request(request) - - def set_eiq_meter_readings(self, date=datetime.datetime.now().strftime("%Y-%m-%d"), reading=0): - """ - Send Meter Readings to Tado, date format is YYYY-MM-DD, reading is without decimals - """ - - request = TadoRequest() - request.command = "meterReadings" - request.action = Action.SET - request.endpoint = Endpoint.EIQ - request.payload = {"date": date, "reading": reading} - - return self._http.request(request) - - def set_eiq_tariff( - self, - from_date=datetime.datetime.now().strftime("%Y-%m-%d"), - to_date=datetime.datetime.now().strftime("%Y-%m-%d"), - tariff=0, - unit="m3", - is_period=False, - ): - """ - Send Tariffs to Tado, date format is YYYY-MM-DD, - tariff is with decimals, unit is either m3 or kWh, - set is_period to true to set a period of price - """ - - tariff_in_cents = tariff * 100 - - if is_period: - payload = { - "tariffInCents": tariff_in_cents, - "unit": unit, - "startDate": from_date, - "endDate": to_date, - } - else: - payload = { - "tariffInCents": tariff_in_cents, - "unit": unit, - "startDate": from_date, - } - - request = TadoRequest() - request.command = "tariffs" - request.action = Action.SET - request.endpoint = Endpoint.EIQ - request.payload = payload - - return self._http.request(request) - def get_heating_circuits(self): """ Gets available heating circuits diff --git a/PyTado/interface/interface.py b/PyTado/interface/interface.py index 52c3f53..0569b1d 100644 --- a/PyTado/interface/interface.py +++ b/PyTado/interface/interface.py @@ -51,7 +51,7 @@ def __init__( ) if self._http.is_x_line: - self._api: API.Tado | API.TadoX = API.TadoX(http=self._http, debug=debug) + self._api: API.TadoBase = API.TadoX(http=self._http, debug=debug) else: self._api = API.Tado(http=self._http, debug=debug) From 32cacdc20605c2bdabba5f2d2b475c41400045de Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Sun, 29 Dec 2024 13:11:01 +0100 Subject: [PATCH 04/13] moved methods to base_tado - changed compatibility of tadoX devices --- PyTado/interface/api/base_tado.py | 51 ++++++++++++++++++++- PyTado/interface/api/hops_tado.py | 26 +++++++---- PyTado/interface/api/my_tado.py | 76 +------------------------------ 3 files changed, 69 insertions(+), 84 deletions(-) diff --git a/PyTado/interface/api/base_tado.py b/PyTado/interface/api/base_tado.py index 2ce480a..8ff3ff2 100644 --- a/PyTado/interface/api/base_tado.py +++ b/PyTado/interface/api/base_tado.py @@ -1,3 +1,7 @@ +""" +Base class for Tado API classes. +""" + import datetime import enum import logging @@ -147,7 +151,7 @@ def get_historic(self, zone, date): raise ValueError("Incorrect date format, should be YYYY-MM-DD") from err request = TadoRequest() - request.command = f"zones/{zone:d}/dayReport?date={day.strftime('%Y-%m-%d')}" + request.command = f"zones/{zone:d}/dayReport?date={day.strftime("%Y-%m-%d")}" return self._http.request(request) @abstractmethod @@ -393,3 +397,48 @@ def get_running_times(self, date=datetime.datetime.now().strftime("%Y-%m-%d")) - request.params = {"from": date} return self._http.request(request) + + def get_boiler_install_state(self, bridge_id: str, auth_key: str): + """ + Get the boiler wiring installation state from home by bridge endpoint + """ + + request = TadoRequest() + request.action = Action.GET + request.domain = Domain.HOME_BY_BRIDGE + request.device = bridge_id + request.command = "boilerWiringInstallationState" + request.params = {"authKey": auth_key} + + return self._http.request(request) + + def get_boiler_max_output_temperature(self, bridge_id: str, auth_key: str): + """ + Get the boiler max output temperature from home by bridge endpoint + """ + + request = TadoRequest() + request.action = Action.GET + request.domain = Domain.HOME_BY_BRIDGE + request.device = bridge_id + request.command = "boilerMaxOutputTemperature" + request.params = {"authKey": auth_key} + + return self._http.request(request) + + def set_boiler_max_output_temperature( + self, bridge_id: str, auth_key: str, temperature_in_celcius: float + ): + """ + Set the boiler max output temperature with home by bridge endpoint + """ + + request = TadoRequest() + request.action = Action.CHANGE + request.domain = Domain.HOME_BY_BRIDGE + request.device = bridge_id + request.command = "boilerMaxOutputTemperature" + request.params = {"authKey": auth_key} + request.payload = {"boilerMaxOutputTemperatureInCelsius": temperature_in_celcius} + + return self._http.request(request) diff --git a/PyTado/interface/api/hops_tado.py b/PyTado/interface/api/hops_tado.py index 05a64f8..fae5a58 100644 --- a/PyTado/interface/api/hops_tado.py +++ b/PyTado/interface/api/hops_tado.py @@ -61,6 +61,8 @@ def get_devices(self): devices = [device for room in rooms for device in room["devices"]] for device in devices: + device["generation"] = "LINE_X" + serial_number = device.get("serialNo", device.get("serialNumber")) if not serial_number: continue @@ -72,13 +74,21 @@ def get_devices(self): # compatibility with my.tado.com API device["shortSerialNo"] = serial_number - device["characteristics"]["capabilities"] = self.get_capabilities(device["serialNo"]) device["name"] = device["roomName"] device["id"] = device["roomId"] - device["generation"] = "LINE_X" + + if "characteristics" not in device: + device["characteristics"] = {"capabilities": {}} + + device["characteristics"]["capabilities"] = self.get_capabilities(serial_number) if "otherDevices" in rooms_and_devices: - devices.append(rooms_and_devices["otherDevices"]) + for device in rooms_and_devices["otherDevices"]: + device["generation"] = "LINE_X" + + serial_number = device.get("serialNo", device.get("serialNumber")) + + devices.append(device) return devices @@ -94,14 +104,14 @@ def get_zones(self): def get_zone_state(self, zone: int) -> TadoZone: """ - Gets current state of Zone as a TadoXZone object. + Gets current state of zone/room as a TadoXZone object. """ return TadoXZone.from_data(zone, self.get_state(zone)) def get_zone_states(self): """ - Gets current states of all zones. + Gets current states of all zones/rooms. """ request = TadoXRequest() @@ -116,7 +126,7 @@ def get_zone_states(self): def get_state(self, zone): """ - Gets current state of Zone. + Gets current state of zone/room. """ request = TadoXRequest() @@ -127,7 +137,7 @@ def get_state(self, zone): def get_capabilities(self, zone): """ - Gets current capabilities of zone. + Gets current capabilities of zone/room. """ _LOGGER.warning( @@ -139,7 +149,7 @@ def get_capabilities(self, zone): def get_climate(self, zone): """ - Gets temp (centigrade) and humidity (% RH) for zone. + Gets temp (centigrade) and humidity (% RH) for zone/room. """ data = self.get_state(zone)["sensorDataPoints"] diff --git a/PyTado/interface/api/my_tado.py b/PyTado/interface/api/my_tado.py index fa864ac..42d267e 100644 --- a/PyTado/interface/api/my_tado.py +++ b/PyTado/interface/api/my_tado.py @@ -5,15 +5,12 @@ import datetime from typing import Any -from PyTado.interface.api.base_tado import Presence, TadoBase, Timetable +from PyTado.interface.api.base_tado import TadoBase, Timetable from ...exceptions import TadoException from ...http import Action, Domain, Endpoint, Mode, TadoRequest -from ...logger import Logger from ...zone import TadoZone -_LOGGER = Logger(__name__) - class Tado(TadoBase): """Interacts with a Tado thermostat via public my.tado.com API. @@ -225,32 +222,6 @@ def get_zone_overlay_default(self, zone: int): return self._http.request(request) - def set_home(self) -> None: - """ - Sets HomeState to HOME - """ - - return self.change_presence(Presence.HOME) - - def set_away(self) -> None: - """ - Sets HomeState to AWAY - """ - - return self.change_presence(Presence.AWAY) - - def change_presence(self, presence: Presence) -> None: - """ - Sets HomeState to presence - """ - - request = TadoRequest() - request.command = "presenceLock" - request.action = Action.CHANGE - request.payload = {"homePresence": presence} - - self._http.request(request) - def set_child_lock(self, device_id, child_lock) -> None: """ Sets the child lock on a device @@ -374,48 +345,3 @@ def get_running_times(self, date=datetime.datetime.now().strftime("%Y-%m-%d")) - request.params = {"from": date} return self._http.request(request) - - def get_boiler_install_state(self, bridge_id: str, auth_key: str): - """ - Get the boiler wiring installation state from home by bridge endpoint - """ - - request = TadoRequest() - request.action = Action.GET - request.domain = Domain.HOME_BY_BRIDGE - request.device = bridge_id - request.command = "boilerWiringInstallationState" - request.params = {"authKey": auth_key} - - return self._http.request(request) - - def get_boiler_max_output_temperature(self, bridge_id: str, auth_key: str): - """ - Get the boiler max output temperature from home by bridge endpoint - """ - - request = TadoRequest() - request.action = Action.GET - request.domain = Domain.HOME_BY_BRIDGE - request.device = bridge_id - request.command = "boilerMaxOutputTemperature" - request.params = {"authKey": auth_key} - - return self._http.request(request) - - def set_boiler_max_output_temperature( - self, bridge_id: str, auth_key: str, temperature_in_celcius: float - ): - """ - Set the boiler max output temperature with home by bridge endpoint - """ - - request = TadoRequest() - request.action = Action.CHANGE - request.domain = Domain.HOME_BY_BRIDGE - request.device = bridge_id - request.command = "boilerMaxOutputTemperature" - request.params = {"authKey": auth_key} - request.payload = {"boilerMaxOutputTemperatureInCelsius": temperature_in_celcius} - - return self._http.request(request) From 77ac51cc9312690ecb27d07b848a9615dcd6cafc Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Sun, 29 Dec 2024 13:14:30 +0100 Subject: [PATCH 05/13] fixed quotes --- PyTado/interface/api/base_tado.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyTado/interface/api/base_tado.py b/PyTado/interface/api/base_tado.py index 8ff3ff2..fe5fb46 100644 --- a/PyTado/interface/api/base_tado.py +++ b/PyTado/interface/api/base_tado.py @@ -151,7 +151,7 @@ def get_historic(self, zone, date): raise ValueError("Incorrect date format, should be YYYY-MM-DD") from err request = TadoRequest() - request.command = f"zones/{zone:d}/dayReport?date={day.strftime("%Y-%m-%d")}" + request.command = f"zones/{zone:d}/dayReport?date={day.strftime('%Y-%m-%d')}" return self._http.request(request) @abstractmethod From 04820ddcdb685997095efde8c33c677c8d0e6dd4 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Sun, 29 Dec 2024 13:37:31 +0100 Subject: [PATCH 06/13] changed hops zone --- PyTado/zone/hops_zone.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PyTado/zone/hops_zone.py b/PyTado/zone/hops_zone.py index fdd1a82..8437a8f 100644 --- a/PyTado/zone/hops_zone.py +++ b/PyTado/zone/hops_zone.py @@ -33,7 +33,8 @@ class TadoXZone(TadoZone): @classmethod def from_data(cls, zone_id: int, data: dict[str, Any]) -> Self: """Handle update callbacks for X zones with specific parsing.""" - _LOGGER.debug("Processing data from X zone %d", zone_id) + _LOGGER.debug("Processing data from X room %d", zone_id) + kwargs: dict[str, Any] = {} # X-specific temperature parsing @@ -118,7 +119,9 @@ def from_data(cls, zone_id: int, data: dict[str, Any]) -> Self: else: kwargs["current_hvac_mode"] = CONST_MODE_SMART_SCHEDULE - kwargs["available"] = kwargs.get("connection") != CONST_CONNECTION_OFFLINE + kwargs["available"] = ( + kwargs.get("connection", CONST_CONNECTION_OFFLINE) != CONST_CONNECTION_OFFLINE + ) # Termination conditions if "terminationCondition" in data: From f0cd46ab0d99be02c81b3dccce612e3844083e97 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Sun, 29 Dec 2024 13:44:18 +0100 Subject: [PATCH 07/13] boiler is currently not supported by tado x --- PyTado/interface/api/base_tado.py | 59 ++++++++----------------------- PyTado/interface/api/hops_tado.py | 14 ++++++++ PyTado/interface/api/my_tado.py | 45 +++++++++++++++++++++++ 3 files changed, 73 insertions(+), 45 deletions(-) diff --git a/PyTado/interface/api/base_tado.py b/PyTado/interface/api/base_tado.py index fe5fb46..cb5436b 100644 --- a/PyTado/interface/api/base_tado.py +++ b/PyTado/interface/api/base_tado.py @@ -310,6 +310,20 @@ def set_temp_offset( ) -> dict[str, Any]: pass + @abstractmethod + def get_boiler_install_state(self, bridge_id: str, auth_key: str): + pass + + @abstractmethod + def get_boiler_max_output_temperature(self, bridge_id: str, auth_key: str): + pass + + @abstractmethod + def set_boiler_max_output_temperature( + self, bridge_id: str, auth_key: str, temperature_in_celcius: float + ): + pass + def get_eiq_tariffs(self): """ Get Energy IQ tariff history @@ -397,48 +411,3 @@ def get_running_times(self, date=datetime.datetime.now().strftime("%Y-%m-%d")) - request.params = {"from": date} return self._http.request(request) - - def get_boiler_install_state(self, bridge_id: str, auth_key: str): - """ - Get the boiler wiring installation state from home by bridge endpoint - """ - - request = TadoRequest() - request.action = Action.GET - request.domain = Domain.HOME_BY_BRIDGE - request.device = bridge_id - request.command = "boilerWiringInstallationState" - request.params = {"authKey": auth_key} - - return self._http.request(request) - - def get_boiler_max_output_temperature(self, bridge_id: str, auth_key: str): - """ - Get the boiler max output temperature from home by bridge endpoint - """ - - request = TadoRequest() - request.action = Action.GET - request.domain = Domain.HOME_BY_BRIDGE - request.device = bridge_id - request.command = "boilerMaxOutputTemperature" - request.params = {"authKey": auth_key} - - return self._http.request(request) - - def set_boiler_max_output_temperature( - self, bridge_id: str, auth_key: str, temperature_in_celcius: float - ): - """ - Set the boiler max output temperature with home by bridge endpoint - """ - - request = TadoRequest() - request.action = Action.CHANGE - request.domain = Domain.HOME_BY_BRIDGE - request.device = bridge_id - request.command = "boilerMaxOutputTemperature" - request.params = {"authKey": auth_key} - request.payload = {"boilerMaxOutputTemperatureInCelsius": temperature_in_celcius} - - return self._http.request(request) diff --git a/PyTado/interface/api/hops_tado.py b/PyTado/interface/api/hops_tado.py index fae5a58..8113ee1 100644 --- a/PyTado/interface/api/hops_tado.py +++ b/PyTado/interface/api/hops_tado.py @@ -360,3 +360,17 @@ def set_child_lock(self, device_id, child_lock): request.payload = {"childLockEnabled": child_lock} self._http.request(request) + + @not_supported("This method is not currently supported by Tado X Bridges (missing authKey)") + def get_boiler_install_state(self, bridge_id: str, auth_key: str): + pass + + @not_supported("This method is not currently supported by Tado X Bridges (missing authKey)") + def get_boiler_max_output_temperature(self, bridge_id: str, auth_key: str): + pass + + @not_supported("This method is not currently supported by Tado X Bridges (missing authKey)") + def set_boiler_max_output_temperature( + self, bridge_id: str, auth_key: str, temperature_in_celcius: float + ): + pass diff --git a/PyTado/interface/api/my_tado.py b/PyTado/interface/api/my_tado.py index 42d267e..4239a83 100644 --- a/PyTado/interface/api/my_tado.py +++ b/PyTado/interface/api/my_tado.py @@ -345,3 +345,48 @@ def get_running_times(self, date=datetime.datetime.now().strftime("%Y-%m-%d")) - request.params = {"from": date} return self._http.request(request) + + def get_boiler_install_state(self, bridge_id: str, auth_key: str): + """ + Get the boiler wiring installation state from home by bridge endpoint + """ + + request = TadoRequest() + request.action = Action.GET + request.domain = Domain.HOME_BY_BRIDGE + request.device = bridge_id + request.command = "boilerWiringInstallationState" + request.params = {"authKey": auth_key} + + return self._http.request(request) + + def get_boiler_max_output_temperature(self, bridge_id: str, auth_key: str): + """ + Get the boiler max output temperature from home by bridge endpoint + """ + + request = TadoRequest() + request.action = Action.GET + request.domain = Domain.HOME_BY_BRIDGE + request.device = bridge_id + request.command = "boilerMaxOutputTemperature" + request.params = {"authKey": auth_key} + + return self._http.request(request) + + def set_boiler_max_output_temperature( + self, bridge_id: str, auth_key: str, temperature_in_celcius: float + ): + """ + Set the boiler max output temperature with home by bridge endpoint + """ + + request = TadoRequest() + request.action = Action.CHANGE + request.domain = Domain.HOME_BY_BRIDGE + request.device = bridge_id + request.command = "boilerMaxOutputTemperature" + request.params = {"authKey": auth_key} + request.payload = {"boilerMaxOutputTemperatureInCelsius": temperature_in_celcius} + + return self._http.request(request) From 42d433ccfbb50b2eb793ad0444e59a3c82e6ea9f Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Wed, 1 Jan 2025 22:55:38 +0100 Subject: [PATCH 08/13] Update PyTado/interface/api/base_tado.py Co-authored-by: Malachi Soord --- PyTado/interface/api/base_tado.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyTado/interface/api/base_tado.py b/PyTado/interface/api/base_tado.py index cb5436b..7e66398 100644 --- a/PyTado/interface/api/base_tado.py +++ b/PyTado/interface/api/base_tado.py @@ -140,7 +140,7 @@ def get_capabilities(self, zone: int): # TODO: typing def get_climate(self, zone: int): # TODO: typing pass - def get_historic(self, zone, date): + def get_historic(self, zone: int, date: str) -> dict[str, Any]: """ Gets historic information on given date for zone """ From e69b60601470918398fb12d7708f7ab44f7c75e7 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Wed, 1 Jan 2025 20:06:57 +0000 Subject: [PATCH 09/13] created enum for units --- PyTado/const.py | 9 +++++++++ PyTado/interface/api/base_tado.py | 7 ++++--- PyTado/interface/interface.py | 7 ++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/PyTado/const.py b/PyTado/const.py index f9ee4ba..c4b02eb 100644 --- a/PyTado/const.py +++ b/PyTado/const.py @@ -1,6 +1,8 @@ """Constant values for the Tado component.""" # Api credentials +import enum + CLIENT_ID = "tado-web-app" # nosec B105 CLIENT_SECRET = "wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc" # nosec B105 @@ -98,3 +100,10 @@ HOME_DOMAIN = "homes" DEVICE_DOMAIN = "devices" + + +class Unit(enum.Enum): + """unit Enum""" + + M3 = "m3" + KWH = "kWh" diff --git a/PyTado/interface/api/base_tado.py b/PyTado/interface/api/base_tado.py index 7e66398..f6bcece 100644 --- a/PyTado/interface/api/base_tado.py +++ b/PyTado/interface/api/base_tado.py @@ -8,6 +8,7 @@ from abc import ABCMeta, abstractmethod from typing import Any +from PyTado.const import Unit from PyTado.exceptions import TadoNotSupportedException from PyTado.http import Action, Domain, Endpoint, Http, TadoRequest from PyTado.logger import Logger @@ -365,9 +366,9 @@ def set_eiq_tariff( self, from_date=datetime.datetime.now().strftime("%Y-%m-%d"), to_date=datetime.datetime.now().strftime("%Y-%m-%d"), - tariff=0, - unit="m3", - is_period=False, + tariff: int = 0, + unit: Unit = Unit.M3, + is_period: bool = False, ): """ Send Tariffs to Tado, date format is YYYY-MM-DD, diff --git a/PyTado/interface/interface.py b/PyTado/interface/interface.py index 879f430..90ae637 100644 --- a/PyTado/interface/interface.py +++ b/PyTado/interface/interface.py @@ -7,6 +7,7 @@ import warnings import PyTado.interface.api as API +from PyTado.const import Unit from PyTado.http import Http @@ -283,9 +284,9 @@ def setEIQTariff( self, from_date=datetime.datetime.now().strftime("%Y-%m-%d"), to_date=datetime.datetime.now().strftime("%Y-%m-%d"), - tariff=0, - unit="m3", - is_period=False, + tariff: int = 0, + unit: Unit = Unit.M3, + is_period: bool = False, ): """Send Tariffs to Tado (Deprecated) From 75dcf503ef88426f825231ed5f8eec2f914ee593 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Wed, 1 Jan 2025 22:04:24 +0000 Subject: [PATCH 10/13] created default date format constant --- PyTado/const.py | 1 + PyTado/interface/api/base_tado.py | 16 +++++++++------- PyTado/interface/api/my_tado.py | 11 ++++++----- PyTado/interface/interface.py | 9 +++++---- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/PyTado/const.py b/PyTado/const.py index c4b02eb..cfb20fa 100644 --- a/PyTado/const.py +++ b/PyTado/const.py @@ -97,6 +97,7 @@ DEFAULT_TADO_PRECISION = 0.1 DEFAULT_TADOX_PRECISION = 0.01 +DEFAULT_DATE_FORMAT = "%Y-%m-%d" HOME_DOMAIN = "homes" DEVICE_DOMAIN = "devices" diff --git a/PyTado/interface/api/base_tado.py b/PyTado/interface/api/base_tado.py index f6bcece..2c903f1 100644 --- a/PyTado/interface/api/base_tado.py +++ b/PyTado/interface/api/base_tado.py @@ -8,7 +8,7 @@ from abc import ABCMeta, abstractmethod from typing import Any -from PyTado.const import Unit +from PyTado.const import DEFAULT_DATE_FORMAT, Unit from PyTado.exceptions import TadoNotSupportedException from PyTado.http import Action, Domain, Endpoint, Http, TadoRequest from PyTado.logger import Logger @@ -147,12 +147,12 @@ def get_historic(self, zone: int, date: str) -> dict[str, Any]: """ try: - day = datetime.datetime.strptime(date, "%Y-%m-%d") + day = datetime.datetime.strptime(date, f"{DEFAULT_DATE_FORMAT}") except ValueError as err: raise ValueError("Incorrect date format, should be YYYY-MM-DD") from err request = TadoRequest() - request.command = f"zones/{zone:d}/dayReport?date={day.strftime('%Y-%m-%d')}" + request.command = f"zones/{zone:d}/dayReport?date={day.strftime(f'{DEFAULT_DATE_FORMAT}')}" return self._http.request(request) @abstractmethod @@ -349,7 +349,8 @@ def get_eiq_meter_readings(self): return self._http.request(request) - def set_eiq_meter_readings(self, date=datetime.datetime.now().strftime("%Y-%m-%d"), reading=0): + def set_eiq_meter_readings(self, date=datetime.datetime.now().strftime( + "{DEFAULT_DATE_FORMAT}"), reading=0): """ Send Meter Readings to Tado, date format is YYYY-MM-DD, reading is without decimals """ @@ -364,8 +365,8 @@ def set_eiq_meter_readings(self, date=datetime.datetime.now().strftime("%Y-%m-%d def set_eiq_tariff( self, - from_date=datetime.datetime.now().strftime("%Y-%m-%d"), - to_date=datetime.datetime.now().strftime("%Y-%m-%d"), + from_date=datetime.datetime.now().strftime(f"{DEFAULT_DATE_FORMAT}"), + to_date=datetime.datetime.now().strftime(f"{DEFAULT_DATE_FORMAT}"), tariff: int = 0, unit: Unit = Unit.M3, is_period: bool = False, @@ -400,7 +401,8 @@ def set_eiq_tariff( return self._http.request(request) - def get_running_times(self, date=datetime.datetime.now().strftime("%Y-%m-%d")) -> dict: + def get_running_times(self, date=datetime.datetime.now().strftime( + f"{DEFAULT_DATE_FORMAT}")) -> dict: """ Get the running times from the Minder API """ diff --git a/PyTado/interface/api/my_tado.py b/PyTado/interface/api/my_tado.py index 4239a83..f4bf50d 100644 --- a/PyTado/interface/api/my_tado.py +++ b/PyTado/interface/api/my_tado.py @@ -5,11 +5,11 @@ import datetime from typing import Any +from PyTado.const import DEFAULT_DATE_FORMAT +from PyTado.exceptions import TadoException +from PyTado.http import Action, Domain, Endpoint, Mode, TadoRequest from PyTado.interface.api.base_tado import TadoBase, Timetable - -from ...exceptions import TadoException -from ...http import Action, Domain, Endpoint, Mode, TadoRequest -from ...zone import TadoZone +from PyTado.zone import TadoZone class Tado(TadoBase): @@ -333,7 +333,8 @@ def set_zone_heating_circuit(self, zone, heating_circuit): return self._http.request(request) - def get_running_times(self, date=datetime.datetime.now().strftime("%Y-%m-%d")) -> dict: + def get_running_times(self, date=datetime.datetime.now().strftime( + f"{DEFAULT_DATE_FORMAT}")) -> dict: """ Get the running times from the Minder API """ diff --git a/PyTado/interface/interface.py b/PyTado/interface/interface.py index 90ae637..1be2544 100644 --- a/PyTado/interface/interface.py +++ b/PyTado/interface/interface.py @@ -7,7 +7,7 @@ import warnings import PyTado.interface.api as API -from PyTado.const import Unit +from PyTado.const import DEFAULT_DATE_FORMAT, Unit from PyTado.http import Http @@ -272,7 +272,8 @@ def getEIQMeterReadings(self): return self.get_eiq_meter_readings() @deprecated("set_eiq_meter_readings") - def setEIQMeterReadings(self, date=datetime.datetime.now().strftime("%Y-%m-%d"), reading=0): + def setEIQMeterReadings(self, date=datetime.datetime.now().strftime( + f"{DEFAULT_DATE_FORMAT}"), reading=0): """Send Meter Readings to Tado (Deprecated) date format is YYYY-MM-DD, reading is without decimals @@ -282,8 +283,8 @@ def setEIQMeterReadings(self, date=datetime.datetime.now().strftime("%Y-%m-%d"), @deprecated("set_eiq_tariff") def setEIQTariff( self, - from_date=datetime.datetime.now().strftime("%Y-%m-%d"), - to_date=datetime.datetime.now().strftime("%Y-%m-%d"), + from_date=datetime.datetime.now().strftime(f"{DEFAULT_DATE_FORMAT}"), + to_date=datetime.datetime.now().strftime(f"{DEFAULT_DATE_FORMAT}"), tariff: int = 0, unit: Unit = Unit.M3, is_period: bool = False, From 50e4c8fa80a0485a982dbc57a55cbd9549ad8239 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Wed, 1 Jan 2025 22:06:22 +0000 Subject: [PATCH 11/13] simplified default initialization --- PyTado/interface/api/hops_tado.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/PyTado/interface/api/hops_tado.py b/PyTado/interface/api/hops_tado.py index 8113ee1..e903656 100644 --- a/PyTado/interface/api/hops_tado.py +++ b/PyTado/interface/api/hops_tado.py @@ -77,9 +77,7 @@ def get_devices(self): device["name"] = device["roomName"] device["id"] = device["roomId"] - if "characteristics" not in device: - device["characteristics"] = {"capabilities": {}} - + device.setdefault("characteristics", {"capabilities": {}}) device["characteristics"]["capabilities"] = self.get_capabilities(serial_number) if "otherDevices" in rooms_and_devices: From f5155d5c6124486a61e15228d1a3b4d93dbd1d99 Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Wed, 1 Jan 2025 22:28:26 +0000 Subject: [PATCH 12/13] fixed zone state --- PyTado/interface/api/hops_tado.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PyTado/interface/api/hops_tado.py b/PyTado/interface/api/hops_tado.py index e903656..0346779 100644 --- a/PyTado/interface/api/hops_tado.py +++ b/PyTado/interface/api/hops_tado.py @@ -115,12 +115,12 @@ def get_zone_states(self): request = TadoXRequest() request.command = "rooms" - rooms_ = self._http.request(request) + rooms = self._http.request(request) # make response my.tado.com compatible - zone_states = {"zoneStates": {"id": room["id"], "name": room["name"]} for room in rooms_} + zone_states = {"zoneStates": [{"id": room["id"], "name": room["name"]} for room in rooms]} - return {**zone_states, **rooms_} + return {**zone_states, "rooms": rooms} def get_state(self, zone): """ From 1b0642a73dc951c1b8588b2dc2e553933955dcef Mon Sep 17 00:00:00 2001 From: Wolfgang Malgadey Date: Thu, 2 Jan 2025 09:01:28 +0000 Subject: [PATCH 13/13] use serial_number in other devices --- PyTado/interface/api/hops_tado.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PyTado/interface/api/hops_tado.py b/PyTado/interface/api/hops_tado.py index 0346779..cafda05 100644 --- a/PyTado/interface/api/hops_tado.py +++ b/PyTado/interface/api/hops_tado.py @@ -85,6 +85,7 @@ def get_devices(self): device["generation"] = "LINE_X" serial_number = device.get("serialNo", device.get("serialNumber")) + device["shortSerialNo"] = serial_number devices.append(device)