python-tado 0.18.9__tar.gz → 0.19.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. python_tado-0.19.1/PKG-INFO +240 -0
  2. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/__main__.py +62 -11
  3. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/http.py +20 -13
  4. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/interface/__init__.py +2 -0
  5. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/interface/api/__init__.py +2 -0
  6. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/interface/api/hops_tado.py +59 -9
  7. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/interface/interface.py +6 -5
  8. python_tado-0.19.1/PyTado/logger.py +24 -0
  9. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/zone/__init__.py +2 -0
  10. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/zone/hops_zone.py +17 -34
  11. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/zone/my_zone.py +21 -7
  12. {python_tado-0.18.9 → python_tado-0.19.1}/README.md +31 -13
  13. python_tado-0.19.1/pyproject.toml +74 -0
  14. python_tado-0.18.9/PKG-INFO +0 -892
  15. python_tado-0.18.9/PyTado/logger.py +0 -50
  16. python_tado-0.18.9/pyproject.toml +0 -309
  17. python_tado-0.18.9/python_tado.egg-info/PKG-INFO +0 -892
  18. python_tado-0.18.9/python_tado.egg-info/SOURCES.txt +0 -30
  19. python_tado-0.18.9/python_tado.egg-info/dependency_links.txt +0 -1
  20. python_tado-0.18.9/python_tado.egg-info/entry_points.txt +0 -2
  21. python_tado-0.18.9/python_tado.egg-info/not-zip-safe +0 -1
  22. python_tado-0.18.9/python_tado.egg-info/requires.txt +0 -16
  23. python_tado-0.18.9/python_tado.egg-info/top_level.txt +0 -1
  24. python_tado-0.18.9/setup.cfg +0 -4
  25. python_tado-0.18.9/tests/test_hops_zone.py +0 -152
  26. python_tado-0.18.9/tests/test_http.py +0 -285
  27. python_tado-0.18.9/tests/test_my_tado.py +0 -97
  28. python_tado-0.18.9/tests/test_my_zone.py +0 -680
  29. python_tado-0.18.9/tests/test_tado_interface.py +0 -86
  30. {python_tado-0.18.9 → python_tado-0.19.1}/AUTHORS +0 -0
  31. {python_tado-0.18.9 → python_tado-0.19.1}/LICENSE +0 -0
  32. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/__init__.py +0 -0
  33. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/const.py +0 -0
  34. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/exceptions.py +0 -0
  35. {python_tado-0.18.9 → python_tado-0.19.1}/PyTado/interface/api/my_tado.py +0 -0
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.3
2
+ Name: python-tado
3
+ Version: 0.19.1
4
+ Summary: PyTado from chrism0dwk, modified by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi, Coffee2CodeNL, chiefdragon, FilBr, nikilase, albertomontesg, Moritz-Schmidt, palazzem
5
+ License: GPL-3.0-or-later
6
+ Keywords: tado
7
+ Author: Chris Jewell
8
+ Author-email: chrism0dwk@gmail.com
9
+ Requires-Python: >=3.11
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
14
+ Classifier: Natural Language :: English
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Home Automation
20
+ Provides-Extra: all
21
+ Provides-Extra: dev
22
+ Provides-Extra: lint
23
+ Provides-Extra: test
24
+ Requires-Dist: black (>=24.10) ; extra == "lint" or extra == "all"
25
+ Requires-Dist: pre-commit ; extra == "dev" or extra == "all"
26
+ Requires-Dist: pylint ; extra == "lint" or extra == "all"
27
+ Requires-Dist: pytest ; extra == "test" or extra == "all"
28
+ Requires-Dist: pytest-cov ; extra == "test" or extra == "all"
29
+ Requires-Dist: pytest-mock ; extra == "test" or extra == "all"
30
+ Requires-Dist: pytest-socket ; extra == "test" or extra == "all"
31
+ Requires-Dist: pytype ; extra == "dev" or extra == "all"
32
+ Requires-Dist: requests
33
+ Requires-Dist: responses ; extra == "test" or extra == "all"
34
+ Requires-Dist: types-requests ; extra == "dev" or extra == "all"
35
+ Project-URL: Documentation, https://wmalgadey.github.io/PyTado/
36
+ Project-URL: Homepage, https://github.com/wmalgadey/PyTado
37
+ Project-URL: Repository, https://github.com/wmalgadey/PyTado
38
+ Description-Content-Type: text/markdown
39
+
40
+ # PyTado -- Pythonize your central heating
41
+
42
+ [![Linting and testing](https://github.com/wmalgadey/PyTado/actions/workflows/lint-and-test-matrix.yml/badge.svg)](https://github.com/wmalgadey/PyTado/actions/workflows/lint-and-test-matrix.yml)
43
+ [![Build and deploy to pypi](https://github.com/wmalgadey/PyTado/actions/workflows/publish-to-pypi.yml/badge.svg?event=release)](https://github.com/wmalgadey/PyTado/actions/workflows/publish-to-pypi.yml)
44
+ [![PyPI version](https://badge.fury.io/py/python-tado.svg)](https://badge.fury.io/py/python-tado)
45
+ [![codecov](https://codecov.io/github/wmalgadey/PyTado/graph/badge.svg?token=14TT00IWJI)](https://codecov.io/github/wmalgadey/PyTado)
46
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
47
+
48
+ PyTado is a Python module implementing an interface to the Tado web API. It allows a user to interact with their
49
+ Tado heating system for the purposes of monitoring or controlling their heating system, beyond what Tado themselves
50
+ currently offer.
51
+
52
+ It is hoped that this module might be used by those who wish to tweak their Tado systems, and further optimise their
53
+ heating setups.
54
+
55
+ ---
56
+
57
+ Original author: Chris Jewell <chrism0dwk@gmail.com>
58
+
59
+ Licence: GPL v3
60
+
61
+ Copyright: Chris Jewell 2016-2018
62
+
63
+ ## Disclaimer
64
+
65
+ Besides owning a Tado system, I have no connection with the Tado company themselves. PyTado was created for my own use,
66
+ and for others who may wish to experiment with personal Internet of Things systems. I receive no help (financial or
67
+ otherwise) from Tado, and have no business interest with them. This software is provided without warranty, according to
68
+ the GNU Public Licence version 3, and should therefore not be used where it may endanger life, financial stakes, or
69
+ cause discomfort and inconvenience to others.
70
+
71
+ ## Usage
72
+
73
+ As of the 15th of March 2025, Tado has updated their OAuth2 authentication flow. It will now use the device flow, instead of a username/password flow. This means that the user will have to authenticate the device using a browser, and then enter the code that is displayed on the browser into the terminal.
74
+
75
+ PyTado handles this as following:
76
+
77
+ 1. The `_login_device_flow()` will be invoked at the initialization of a PyTado object. This will start the device flow and will return a URL and a code that the user will have to enter in the browser. The URL can be obtained via the method `device_verification_url()`. Or, when in debug mode, the URL will be printed. Alternatively, you can use the `device_activation_status()` method to check if the device has been activated. It returns three statuses: `NOT_STARTED`, `PENDING`, and `COMPLETED`. Wait to invoke the `device_activation()` method until the status is `PENDING`.
78
+
79
+ 2. Once the URL is obtained, the user will have to enter the code that is displayed on the browser into the terminal. By default, the URL has the `user_code` attached, for the ease of going trough the flow. At this point, run the method `device_activation()`. It will poll every five seconds to see if the flow has been completed. If the flow has been completed, the method will return a token that will be used for all further requests. It will timeout after five minutes.
80
+
81
+ 3. Once the token has been obtained, the user can use the PyTado object to interact with the Tado API. The token will be stored in the `Tado` object, and will be used for all further requests. The token will be refreshed automatically when it expires.
82
+ The `device_verification_url()` will be reset to `None` and the `device_activation_status()` will return `COMPLETED`.
83
+
84
+ ### Screenshots of the device flow
85
+
86
+ ![Tado device flow: invoking](/screenshots/tado-device-flow-0.png)
87
+ ![Tado device flow: browser](/screenshots/tado-device-flow-1.png)
88
+ ![Tado device flow: complete](/screenshots/tado-device-flow-2.png)
89
+
90
+ ### How to not authenticate the device again
91
+
92
+ It is possible to save the refresh token and reuse to skip the next login.
93
+
94
+ The following code will use the `refresh_token` file to save the refresh-token after login, and load the refresh-token if you create the Tado interface class again.
95
+
96
+ If the file doesn't exists, the webbrowser is started and the device authentication url is automatically opened. You can activate the device in the browser. When you restart the program, the refresh-token is reused and no webbrowser will be opened.
97
+
98
+ ```python
99
+ import webbrowser # only needed for direct web browser access
100
+
101
+ from PyTado.interface.interface import Tado
102
+
103
+ tado = Tado(token_file_path="/var/tado/refresh_token")
104
+
105
+ status = tado.device_activation_status()
106
+
107
+ if status == "PENDING":
108
+ url = tado.device_verification_url()
109
+
110
+ webbrowser.open_new_tab(url)
111
+
112
+ tado.device_activation()
113
+
114
+ status = tado.device_activation_status()
115
+
116
+ if status == "COMPLETED":
117
+ print("Login successful")
118
+ else:
119
+ print(f"Login status is {status}")
120
+ ```
121
+
122
+ ## Example code
123
+
124
+ ```python
125
+ """Example client for PyTado"""
126
+
127
+ from PyTado.interface.interface import Tado
128
+
129
+
130
+ def main() -> None:
131
+ """Retrieve all zones, once successfully logged in"""
132
+ tado = Tado()
133
+
134
+ print("Device activation status: ", tado.device_activation_status())
135
+ print("Device verification URL: ", tado.device_verification_url())
136
+
137
+ print("Starting device activation")
138
+ tado.device_activation()
139
+
140
+ print("Device activation status: ", tado.device_activation_status())
141
+
142
+ zones = tado.get_zones()
143
+ print(zones)
144
+
145
+
146
+ if __name__ == "__main__":
147
+ main()
148
+ ```
149
+
150
+ Note: For developers, there is an `example.py` script in `examples/` which is configured to fetch data from your account.
151
+ You can then invoke `python examples/example.py`.
152
+
153
+
154
+ ## Contributing
155
+
156
+ We are very open to the community's contributions - be it a quick fix of a typo, or a completely new feature!
157
+
158
+ You don't need to be a Python expert to provide meaningful improvements. To learn how to get started, check out our
159
+ [Contributor Guidelines](https://github.com/wmalgadey/econnect-python/blob/main/CONTRIBUTING.md) first, and ask for help
160
+ in [GitHub Discussions](https://github.com/wmalgadey/PyTado/discussions) if you have questions.
161
+
162
+ ## Development
163
+
164
+ We welcome external contributions, even though the project was initially intended for personal use. If you think some
165
+ parts could be exposed with a more generic interface, please open a [GitHub issue](https://github.com/wmalgadey/PyTado/issues)
166
+ to discuss your suggestion.
167
+
168
+ ### Setting up a devcontainer
169
+
170
+ The easiest way to start, is by opening a CodeSpace here on GitHub, or by using
171
+ the [Dev Container][devcontainer] feature of Visual Studio Code.
172
+
173
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
174
+
175
+ ### Dev Environment
176
+
177
+ To contribute to this repository, you should first clone your fork and then setup your development environment. Clone
178
+ your repository as follows (replace yourusername with your GitHub account name):
179
+
180
+ ```bash
181
+ git clone https://github.com/yourusername/PyTado.git
182
+ cd PyTado
183
+ ```
184
+
185
+ Then, to create your development environment and install the project with its dependencies, execute the following
186
+ commands in your terminal:
187
+
188
+ ```bash
189
+ # Create and activate a new virtual environment
190
+ python3 -m venv venv
191
+ source venv/bin/activate
192
+
193
+ # install all dependencies
194
+ poetry install
195
+
196
+ # Install pre-commit hooks
197
+ poetry run pre-commit install
198
+ ```
199
+
200
+ ### Coding Guidelines
201
+
202
+ To maintain a consistent codebase, we utilize [black][1]. Consistency is crucial as it helps readability, reduces errors,
203
+ and facilitates collaboration among developers.
204
+
205
+ To ensure that every commit adheres to our coding standards, we've integrated [pre-commit hooks][2]. These hooks
206
+ automatically run `black` before each commit, ensuring that all code changes are automatically checked and formatted.
207
+
208
+ For details on how to set up your development environment to make use of these hooks, please refer to the
209
+ [Development][3] section of our documentation.
210
+
211
+ [1]: https://github.com/ambv/black
212
+ [2]: https://pre-commit.com/
213
+ [3]: https://github.com/wmalgadey/PyTado#development
214
+
215
+ ### Testing
216
+
217
+ Ensuring the robustness and reliability of our code is paramount. Therefore, all contributions must include at least one
218
+ test to verify the intended behavior.
219
+
220
+ To run tests locally, execute the test suite using `pytest` with the following command:
221
+
222
+ ```bash
223
+ pytest tests/ --cov --cov-branch -vv
224
+ ```
225
+
226
+ ---
227
+
228
+ A message from the original author:
229
+
230
+ > This software is at a purely experimental stage. If you're interested and can write Python, clone the Github repo,
231
+ > drop me a line, and get involved!
232
+ >
233
+ > Best wishes and a warm winter to all!
234
+ >
235
+ > Chris Jewell
236
+
237
+
238
+ [devcontainer-shield]: https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode
239
+ [devcontainer]: https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/wmalgadey/PyTado
240
+
@@ -9,43 +9,92 @@ import sys
9
9
  from PyTado.interface import Tado
10
10
 
11
11
 
12
- def log_in():
13
- t = Tado()
12
+ def log_in(args):
13
+ """
14
+ Log in to the Tado API by activating the current device.
15
+
16
+ Add --token_file_path to the command line arguments to store the
17
+ refresh token in a file.
18
+
19
+ Args:
20
+ args (argparse.Namespace): The parsed command-line arguments.
21
+
22
+ Returns:
23
+ Tado: An instance of the Tado interface.
24
+ """
25
+ t = Tado(token_file_path=args.token_file_path)
14
26
  t.device_activation()
15
27
  return t
16
28
 
17
29
 
18
- def get_me():
19
- t = log_in()
30
+ def get_me(args):
31
+ """
32
+ Retrieve and print home information from the Tado API.
33
+
34
+ Args:
35
+ args (argparse.Namespace): The parsed command-line arguments.
36
+ """
37
+ t = log_in(args)
20
38
  me = t.get_me()
21
39
  print(me)
22
40
 
23
41
 
24
42
  def get_state(args):
25
- t = log_in()
43
+ """
44
+ Retrieve and print the state of a specific zone from the Tado API.
45
+
46
+ Args:
47
+ args (argparse.Namespace): The parsed command-line arguments.
48
+ """
49
+ t = log_in(args)
26
50
  zone = t.get_state(int(args.zone))
27
51
  print(zone)
28
52
 
29
53
 
30
54
  def get_states(args):
31
- t = log_in()
32
- zone = t.get_zone_states()
33
- print(zone)
55
+ """
56
+ Retrieve and print the states of all zones from the Tado API.
57
+
58
+ Args:
59
+ args (argparse.Namespace): The parsed command-line arguments.
60
+ """
61
+ t = log_in(args)
62
+ zones = t.get_zone_states()
63
+ print(zones)
34
64
 
35
65
 
36
66
  def get_capabilities(args):
37
- t = log_in()
67
+ """
68
+ Retrieve and print the capabilities of a specific zone from the Tado API.
69
+
70
+ Args:
71
+ args (argparse.Namespace): The parsed command-line arguments.
72
+ """
73
+ t = log_in(args)
38
74
  capabilities = t.get_capabilities(int(args.zone))
39
75
  print(capabilities)
40
76
 
41
77
 
42
78
  def main():
43
- """Main method for the script."""
79
+ """
80
+ Main method for the script.
81
+
82
+ Sets up the argument parser, handles subcommands, and executes the appropriate function.
83
+ """
44
84
  parser = argparse.ArgumentParser(
45
85
  description="Pytado - Tado thermostat device control",
46
86
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
47
87
  )
48
88
 
89
+ required_flags = parser.add_argument_group("required arguments")
90
+
91
+ # Required flags go here.
92
+ required_flags.add_argument(
93
+ "--token_file_path",
94
+ required=True,
95
+ help="Path to the file where the refresh token should be stored.",
96
+ )
97
+
49
98
  # Flags with default values go here.
50
99
  log_levels = {logging.getLevelName(level): level for level in [10, 20, 30, 40, 50]}
51
100
  parser.add_argument(
@@ -67,7 +116,9 @@ def main():
67
116
  start_activity_parser = subparsers.add_parser("get_states", help="Get states of all zones.")
68
117
  start_activity_parser.set_defaults(func=get_states)
69
118
 
70
- start_activity_parser = subparsers.add_parser("get_capabilities", help="Get capabilities of zone.")
119
+ start_activity_parser = subparsers.add_parser(
120
+ "get_capabilities", help="Get capabilities of zone."
121
+ )
71
122
  start_activity_parser.add_argument("--zone", help="Zone to get the capabilities of.")
72
123
  start_activity_parser.set_defaults(func=get_capabilities)
73
124
 
@@ -159,8 +159,8 @@ class Http:
159
159
  Args:
160
160
  token_file_path (str | None): Path to the file where the token is stored.
161
161
  If None, the token will not be saved to a file.
162
- saved_refresh_token (str | None): A previously saved refresh token to use for authentication.
163
- If None, a new token will be requested.
162
+ saved_refresh_token (str | None): A previously saved refresh token to use for
163
+ authentication. If None, a new token will be requested.
164
164
  http_session (requests.Session | None): An optional pre-configured HTTP session.
165
165
  If None, a new session will be created.
166
166
  debug (bool): If True, enables debug logging. Defaults to False.
@@ -189,10 +189,9 @@ class Http:
189
189
  self._x_api: bool | None = None
190
190
  self._token_file_path = token_file_path
191
191
 
192
- if (saved_refresh_token or self._load_token()) and self._refresh_token(
193
- refresh_token=saved_refresh_token, force_refresh=True
194
- ):
195
- self._device_ready()
192
+ if saved_refresh_token or self._load_token():
193
+ if self._refresh_token(refresh_token=saved_refresh_token, force_refresh=True):
194
+ self._device_ready()
196
195
  else:
197
196
  self._device_activation_status = self._login_device_flow()
198
197
 
@@ -383,9 +382,9 @@ class Http:
383
382
 
384
383
  Args:
385
384
  refresh_token (str | None, optional): The refresh token to use for obtaining a new
386
- access token.
385
+ access token.
387
386
  force_refresh (bool, optional): If True, forces a token refresh regardless of
388
- expiration. Defaults to False.
387
+ expiration. Defaults to False.
389
388
 
390
389
  Returns:
391
390
  bool: True if the token was successfully refreshed, False if the refresh failed due
@@ -394,7 +393,7 @@ class Http:
394
393
  Raises:
395
394
  TadoException: If a connection error occurs during the token refresh process.
396
395
  TadoWrongCredentialsException: If the token refresh fails due to invalid credentials
397
- and force_refresh is False.
396
+ and force_refresh is False.
398
397
  """
399
398
 
400
399
  if self._refresh_at >= datetime.now() and not force_refresh:
@@ -435,7 +434,8 @@ class Http:
435
434
  return False
436
435
 
437
436
  raise TadoWrongCredentialsException(
438
- f"Failed to refresh token, probably wrong credentials. Status code: {response.status_code}"
437
+ "Failed to refresh token, probably wrong credentials. "
438
+ f"Status code: {response.status_code}"
439
439
  )
440
440
 
441
441
  self._set_oauth_header(response.json())
@@ -491,7 +491,9 @@ class Http:
491
491
  raise TadoException(e) from e
492
492
 
493
493
  if response.status_code != 200:
494
- raise TadoException(f"Login failed. Status code: {response.status_code} and reason: {response.reason}")
494
+ raise TadoException(
495
+ f"Login failed. Status code: {response.status_code} and reason: {response.reason}"
496
+ )
495
497
 
496
498
  self._device_flow_data = response.json()
497
499
  _LOGGER.debug("Device flow response: %s", self._device_flow_data)
@@ -514,7 +516,9 @@ class Http:
514
516
  return DeviceActivationStatus.PENDING
515
517
 
516
518
  def _check_device_activation(self) -> bool:
517
- if self._expires_at is not None and datetime.timestamp(datetime.now()) > datetime.timestamp(self._expires_at):
519
+ if self._expires_at is not None and datetime.timestamp(datetime.now()) > datetime.timestamp(
520
+ self._expires_at
521
+ ):
518
522
  raise TadoException("User took too long to enter key")
519
523
 
520
524
  # Await the desired interval, before polling the API again
@@ -538,7 +542,10 @@ class Http:
538
542
  return True
539
543
 
540
544
  # The user has not yet authorized the device, let's continue
541
- if token_response.status_code == 400 and token_response.json()["error"] == "authorization_pending":
545
+ if (
546
+ token_response.status_code == 400
547
+ and token_response.json()["error"] == "authorization_pending"
548
+ ):
542
549
  _LOGGER.info("Authorization pending, waiting for user to authorize. Continue polling.")
543
550
  return False
544
551
 
@@ -1,3 +1,5 @@
1
1
  """Abstraction layer for API implementation."""
2
2
 
3
3
  from .interface import Tado
4
+
5
+ __all__ = ["Tado"]
@@ -2,3 +2,5 @@
2
2
 
3
3
  from .hops_tado import TadoX
4
4
  from .my_tado import Tado
5
+
6
+ __all__ = ["Tado", "TadoX"]
@@ -2,6 +2,7 @@
2
2
  PyTado interface implementation for hops.tado.com (Tado X).
3
3
  """
4
4
 
5
+ import functools
5
6
  import logging
6
7
  from typing import Any
7
8
 
@@ -11,6 +12,18 @@ from ...logger import Logger
11
12
  from ...zone import TadoXZone, TadoZone
12
13
  from .my_tado import Tado, Timetable
13
14
 
15
+
16
+ def not_supported(reason):
17
+ def decorator(func):
18
+ @functools.wraps(func)
19
+ def wrapper(*args, **kwargs):
20
+ raise TadoNotSupportedException(f"{func.__name__} is not supported: {reason}")
21
+
22
+ return wrapper
23
+
24
+ return decorator
25
+
26
+
14
27
  _LOGGER = Logger(__name__)
15
28
 
16
29
 
@@ -60,9 +73,13 @@ class TadoX(Tado):
60
73
  devices = [device for room in rooms for device in room["devices"]]
61
74
 
62
75
  for device in devices:
76
+ serial_number = device.get("serialNo", device.get("serialNumber"))
77
+ if not serial_number:
78
+ continue
79
+
63
80
  request = TadoXRequest()
64
81
  request.domain = Domain.DEVICES
65
- request.device = device["serialNo"]
82
+ request.device = serial_number
66
83
  device.update(self._http.request(request))
67
84
 
68
85
  if "otherDevices" in rooms_and_devices:
@@ -108,12 +125,12 @@ class TadoX(Tado):
108
125
 
109
126
  return data
110
127
 
128
+ @not_supported("This method is not currently supported by the Tado X API")
111
129
  def get_capabilities(self, zone):
112
130
  """
113
131
  Gets current capabilities of zone.
114
132
  """
115
-
116
- raise TadoNotSupportedException("This method is not currently supported by the Tado X API")
133
+ pass
117
134
 
118
135
  def get_climate(self, zone):
119
136
  """
@@ -126,6 +143,7 @@ class TadoX(Tado):
126
143
  "humidity": data["humidity"]["percentage"],
127
144
  }
128
145
 
146
+ @not_supported("Tado X API only support seven days timetable")
129
147
  def set_timetable(self, zone: int, timetable: Timetable) -> None:
130
148
  """
131
149
  Set the Timetable type currently active
@@ -133,8 +151,7 @@ class TadoX(Tado):
133
151
  id = 1 : THREE_DAY (MONDAY_TO_FRIDAY, SATURDAY, SUNDAY)
134
152
  id = 3 : SEVEN_DAY (MONDAY, TUESDAY, WEDNESDAY ...)
135
153
  """
136
-
137
- raise TadoNotSupportedException("Tado X API only support seven days timetable")
154
+ pass
138
155
 
139
156
  def get_schedule(self, zone: int, timetable: Timetable, day=None) -> dict[str, Any]:
140
157
  """
@@ -249,12 +266,12 @@ class TadoX(Tado):
249
266
 
250
267
  return self._http.request(request)
251
268
 
269
+ @not_supported("Concept of zones is not available by Tado X API, they use rooms")
252
270
  def get_zone_overlay_default(self, zone: int):
253
271
  """
254
272
  Get current overlay default settings for zone.
255
273
  """
256
-
257
- raise TadoNotSupportedException("Concept of zones is not available by Tado X API, they use rooms")
274
+ pass
258
275
 
259
276
  def get_open_window_detected(self, zone):
260
277
  """
@@ -273,15 +290,21 @@ class TadoX(Tado):
273
290
  Sets the window in zone to open
274
291
  Note: This can only be set if an open window was detected in this zone
275
292
  """
293
+ request = TadoXRequest()
294
+ request.command = f"rooms/{zone}/openWindow"
295
+ request.action = Action.SET
276
296
 
277
- raise TadoNotSupportedException("This method is not currently supported by the Tado X API")
297
+ return self._http.request(request)
278
298
 
279
299
  def reset_open_window(self, zone):
280
300
  """
281
301
  Sets the window in zone to closed
282
302
  """
303
+ request = TadoXRequest()
304
+ request.command = f"rooms/{zone}/openWindow"
305
+ request.action = Action.RESET
283
306
 
284
- raise TadoNotSupportedException("This method is not currently supported by the Tado X API")
307
+ return self._http.request(request)
285
308
 
286
309
  def get_device_info(self, device_id, cmd=""):
287
310
  """
@@ -324,3 +347,30 @@ class TadoX(Tado):
324
347
  request.payload = {"childLockEnabled": child_lock}
325
348
 
326
349
  self._http.request(request)
350
+
351
+ def set_flow_temperature_optimization(self, max_flow_temperature: float):
352
+ """
353
+ Set the flow temperature optimization.
354
+
355
+ max_flow_temperature: float, the maximum flow temperature in Celsius
356
+ """
357
+
358
+ request = TadoXRequest()
359
+ request.action = Action.CHANGE
360
+ request.domain = Domain.HOME
361
+ request.command = "settings/flowTemperatureOptimization"
362
+ request.payload = {"maxFlowTemperature": max_flow_temperature}
363
+
364
+ return self._http.request(request)
365
+
366
+ def get_flow_temperature_optimization(self):
367
+ """
368
+ Get the current flow temperature optimization
369
+ """
370
+
371
+ request = TadoXRequest()
372
+ request.action = Action.GET
373
+ request.domain = Domain.HOME
374
+ request.command = "settings/flowTemperatureOptimization"
375
+
376
+ return self._http.request(request)
@@ -61,12 +61,11 @@ class Tado:
61
61
 
62
62
  Args:
63
63
  token_file_path (str | None, optional): Path to a file which will be used to persist
64
- the refresh_token token. Defaults to None.
64
+ the refresh_token token. Defaults to None.
65
65
  saved_refresh_token (str | None, optional): A previously saved refresh token.
66
- Defaults to None.
66
+ Defaults to None.
67
67
  http_session (requests.Session | None, optional): An optional HTTP session to use for
68
- requests (can be used in unit tests).
69
- Defaults to None.
68
+ requests (can be used in unit tests). Defaults to None.
70
69
  debug (bool, optional): Flag to enable or disable debug mode. Defaults to False.
71
70
  """
72
71
 
@@ -117,7 +116,9 @@ class Tado:
117
116
  else:
118
117
  self._api = API.Tado(http=self._http, debug=self._debug)
119
118
  else:
120
- raise TadoException("API is not initialized. Please complete device authentication first.")
119
+ raise TadoException(
120
+ "API is not initialized. Please complete device authentication first."
121
+ )
121
122
 
122
123
  # region Deprecated Methods
123
124
  # pylint: disable=invalid-name
@@ -0,0 +1,24 @@
1
+ """
2
+ This file contains anything related to logging
3
+ """
4
+
5
+ import logging
6
+
7
+
8
+ class Logger(logging.Logger):
9
+ """
10
+ This class provides a custom logger without masking sensitive information.
11
+ """
12
+
13
+ class SimpleFormatter(logging.Formatter):
14
+ """
15
+ Simple formatter that does not remove any information in logs.
16
+ """
17
+
18
+ def __init__(self, name: str, level=logging.NOTSET):
19
+ super().__init__(name)
20
+ log_sh = logging.StreamHandler()
21
+ log_fmt = self.SimpleFormatter(fmt="%(name)s :: %(levelname)-8s :: %(message)s")
22
+ log_sh.setFormatter(log_fmt)
23
+ self.addHandler(log_sh)
24
+ self.setLevel(level)
@@ -2,3 +2,5 @@
2
2
 
3
3
  from .hops_zone import TadoXZone
4
4
  from .my_zone import TadoZone
5
+
6
+ __all__ = ["TadoZone", "TadoXZone"]