python-tado 0.18.7__tar.gz → 0.19.0__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.0/PKG-INFO +205 -0
  2. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/__main__.py +5 -4
  3. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/const.py +1 -1
  4. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/http.py +3 -2
  5. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/__init__.py +2 -0
  6. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/api/__init__.py +2 -0
  7. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/api/hops_tado.py +63 -37
  8. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/api/my_tado.py +1 -1
  9. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/interface.py +1 -1
  10. python_tado-0.19.0/PyTado/logger.py +24 -0
  11. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/zone/__init__.py +2 -0
  12. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/zone/hops_zone.py +17 -34
  13. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/zone/my_zone.py +21 -7
  14. {python_tado-0.18.7 → python_tado-0.19.0}/README.md +48 -13
  15. python_tado-0.19.0/pyproject.toml +74 -0
  16. python_tado-0.18.7/PKG-INFO +0 -840
  17. python_tado-0.18.7/PyTado/logger.py +0 -50
  18. python_tado-0.18.7/pyproject.toml +0 -309
  19. python_tado-0.18.7/python_tado.egg-info/PKG-INFO +0 -840
  20. python_tado-0.18.7/python_tado.egg-info/SOURCES.txt +0 -30
  21. python_tado-0.18.7/python_tado.egg-info/dependency_links.txt +0 -1
  22. python_tado-0.18.7/python_tado.egg-info/entry_points.txt +0 -2
  23. python_tado-0.18.7/python_tado.egg-info/not-zip-safe +0 -1
  24. python_tado-0.18.7/python_tado.egg-info/requires.txt +0 -16
  25. python_tado-0.18.7/python_tado.egg-info/top_level.txt +0 -1
  26. python_tado-0.18.7/setup.cfg +0 -4
  27. python_tado-0.18.7/tests/test_hops_zone.py +0 -152
  28. python_tado-0.18.7/tests/test_http.py +0 -190
  29. python_tado-0.18.7/tests/test_my_tado.py +0 -97
  30. python_tado-0.18.7/tests/test_my_zone.py +0 -680
  31. python_tado-0.18.7/tests/test_tado_interface.py +0 -81
  32. {python_tado-0.18.7 → python_tado-0.19.0}/AUTHORS +0 -0
  33. {python_tado-0.18.7 → python_tado-0.19.0}/LICENSE +0 -0
  34. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/__init__.py +0 -0
  35. {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/exceptions.py +0 -0
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.3
2
+ Name: python-tado
3
+ Version: 0.19.0
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.3) ; 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
+ 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.
73
+
74
+ PyTado handles this as following:
75
+
76
+ 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`.
77
+
78
+ 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.
79
+
80
+ 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.
81
+ The `device_verification_url()` will be reset to `None` and the `device_activation_status()` will return `COMPLETED`.
82
+
83
+ ### Screenshots of the device flow
84
+ ![Tado device flow: invoking](/screenshots/tado-device-flow-0.png)
85
+ ![Tado device flow: browser](/screenshots/tado-device-flow-1.png)
86
+ ![Tado device flow: complete](/screenshots/tado-device-flow-2.png)
87
+
88
+ ## Example code
89
+ ```python
90
+ """Example client for PyTado"""
91
+
92
+ from PyTado.interface.interface import Tado
93
+
94
+
95
+ def main() -> None:
96
+ """Retrieve all zones, once successfully logged in"""
97
+ tado = Tado()
98
+
99
+ print("Device activation status: ", tado.device_activation_status())
100
+ print("Device verification URL: ", tado.device_verification_url())
101
+
102
+ print("Starting device activation")
103
+ tado.device_activation()
104
+
105
+ print("Device activation status: ", tado.device_activation_status())
106
+
107
+ zones = tado.get_zones()
108
+ print(zones)
109
+
110
+
111
+ if __name__ == "__main__":
112
+ main()
113
+ ```
114
+
115
+ Note: For developers, there is an `example.py` script in `examples/` which is configured to fetch data from your account.
116
+ You can then invoke `python examples/example.py`.
117
+
118
+
119
+ ## Contributing
120
+
121
+ We are very open to the community's contributions - be it a quick fix of a typo, or a completely new feature!
122
+
123
+ You don't need to be a Python expert to provide meaningful improvements. To learn how to get started, check out our
124
+ [Contributor Guidelines](https://github.com/wmalgadey/econnect-python/blob/main/CONTRIBUTING.md) first, and ask for help
125
+ in [GitHub Discussions](https://github.com/wmalgadey/PyTado/discussions) if you have questions.
126
+
127
+ ## Development
128
+
129
+ We welcome external contributions, even though the project was initially intended for personal use. If you think some
130
+ parts could be exposed with a more generic interface, please open a [GitHub issue](https://github.com/wmalgadey/PyTado/issues)
131
+ to discuss your suggestion.
132
+
133
+ ### Setting up a devcontainer
134
+
135
+ The easiest way to start, is by opening a CodeSpace here on GitHub, or by using
136
+ the [Dev Container][devcontainer] feature of Visual Studio Code.
137
+
138
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
139
+
140
+ ### Dev Environment
141
+
142
+ To contribute to this repository, you should first clone your fork and then setup your development environment. Clone
143
+ your repository as follows (replace yourusername with your GitHub account name):
144
+
145
+ ```bash
146
+ git clone https://github.com/yourusername/PyTado.git
147
+ cd PyTado
148
+ ```
149
+
150
+ Then, to create your development environment and install the project with its dependencies, execute the following
151
+ commands in your terminal:
152
+
153
+ ```bash
154
+ # Create and activate a new virtual environment
155
+ python3 -m venv venv
156
+ source venv/bin/activate
157
+
158
+ # install all dependencies
159
+ poetry install
160
+
161
+ # Install pre-commit hooks
162
+ poetry run pre-commit install
163
+ ```
164
+
165
+ ### Coding Guidelines
166
+
167
+ To maintain a consistent codebase, we utilize [black][1]. Consistency is crucial as it helps readability, reduces errors,
168
+ and facilitates collaboration among developers.
169
+
170
+ To ensure that every commit adheres to our coding standards, we've integrated [pre-commit hooks][2]. These hooks
171
+ automatically run `black` before each commit, ensuring that all code changes are automatically checked and formatted.
172
+
173
+ For details on how to set up your development environment to make use of these hooks, please refer to the
174
+ [Development][3] section of our documentation.
175
+
176
+ [1]: https://github.com/ambv/black
177
+ [2]: https://pre-commit.com/
178
+ [3]: https://github.com/wmalgadey/PyTado#development
179
+
180
+ ### Testing
181
+
182
+ Ensuring the robustness and reliability of our code is paramount. Therefore, all contributions must include at least one
183
+ test to verify the intended behavior.
184
+
185
+ To run tests locally, execute the test suite using `pytest` with the following command:
186
+
187
+ ```bash
188
+ pytest tests/ --cov --cov-branch -vv
189
+ ```
190
+
191
+ ---
192
+
193
+ A message from the original author:
194
+
195
+ > This software is at a purely experimental stage. If you're interested and can write Python, clone the Github repo,
196
+ > drop me a line, and get involved!
197
+ >
198
+ > Best wishes and a warm winter to all!
199
+ >
200
+ > Chris Jewell
201
+
202
+
203
+ [devcontainer-shield]: https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode
204
+ [devcontainer]: https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/wmalgadey/PyTado
205
+
@@ -9,9 +9,8 @@ import sys
9
9
  from PyTado.interface import Tado
10
10
 
11
11
 
12
- def log_in(username, password):
13
- t = Tado()
14
- t.device_activation()
12
+ def log_in(email, password):
13
+ t = Tado(email, password)
15
14
  return t
16
15
 
17
16
 
@@ -77,7 +76,9 @@ def main():
77
76
  start_activity_parser = subparsers.add_parser("get_states", help="Get states of all zones.")
78
77
  start_activity_parser.set_defaults(func=get_states)
79
78
 
80
- start_activity_parser = subparsers.add_parser("get_capabilities", help="Get capabilities of zone.")
79
+ start_activity_parser = subparsers.add_parser(
80
+ "get_capabilities", help="Get capabilities of zone."
81
+ )
81
82
  start_activity_parser.add_argument("--zone", help="Zone to get the capabilities of.")
82
83
  start_activity_parser.set_defaults(func=get_capabilities)
83
84
 
@@ -96,4 +96,4 @@ DEFAULT_TADO_PRECISION = 0.1
96
96
  DEFAULT_TADOX_PRECISION = 0.01
97
97
 
98
98
  HOME_DOMAIN = "homes"
99
- DEVICE_DOMAIN = "devices"
99
+ DEVICE_DOMAIN = "devices"
@@ -57,7 +57,8 @@ class Mode(enum.Enum):
57
57
  PLAIN = 2
58
58
 
59
59
 
60
- class DeviceActivationStatus(enum.StrEnum):
60
+ class DeviceActivationStatus(enum.Enum):
61
+ """Device Activation Status Enum"""
61
62
  NOT_STARTED = "NOT_STARTED"
62
63
  PENDING = "PENDING"
63
64
  COMPLETED = "COMPLETED"
@@ -438,4 +439,4 @@ class Http:
438
439
 
439
440
  home_ = self.request(request)
440
441
 
441
- return "generation" in home_ and home_["generation"] == "LINE_X"
442
+ return "generation" in home_ and home_["generation"] == "LINE_X"
@@ -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,23 +2,26 @@
2
2
  PyTado interface implementation for hops.tado.com (Tado X).
3
3
  """
4
4
 
5
+ import functools
5
6
  import logging
6
-
7
7
  from typing import Any
8
8
 
9
+ from ...exceptions import TadoNotSupportedException
10
+ from ...http import Action, Domain, Http, Mode, TadoRequest, TadoXRequest
11
+ from ...logger import Logger
12
+ from ...zone import TadoXZone, TadoZone
9
13
  from .my_tado import Tado, Timetable
10
14
 
11
- from ...logger import Logger
12
- from ...exceptions import TadoNotSupportedException
13
- from ...http import (
14
- Action,
15
- Domain,
16
- Http,
17
- Mode,
18
- TadoRequest,
19
- TadoXRequest,
20
- )
21
- from ...zone import TadoZone, TadoXZone
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
22
25
 
23
26
 
24
27
  _LOGGER = Logger(__name__)
@@ -42,9 +45,7 @@ class TadoX(Tado):
42
45
  super().__init__(http=http, debug=debug)
43
46
 
44
47
  if not http.is_x_line:
45
- raise TadoNotSupportedException(
46
- "TadoX is only usable with LINE_X Generation"
47
- )
48
+ raise TadoNotSupportedException("TadoX is only usable with LINE_X Generation")
48
49
 
49
50
  if debug:
50
51
  _LOGGER.setLevel(logging.DEBUG)
@@ -71,9 +72,13 @@ class TadoX(Tado):
71
72
  devices = [device for room in rooms for device in room["devices"]]
72
73
 
73
74
  for device in devices:
75
+ serial_number = device.get("serialNo", device.get("serialNumber"))
76
+ if not serial_number:
77
+ continue
78
+
74
79
  request = TadoXRequest()
75
80
  request.domain = Domain.DEVICES
76
- request.device = device["serialNo"]
81
+ request.device = serial_number
77
82
  device.update(self._http.request(request))
78
83
 
79
84
  if "otherDevices" in rooms_and_devices:
@@ -119,14 +124,12 @@ class TadoX(Tado):
119
124
 
120
125
  return data
121
126
 
127
+ @not_supported("This method is not currently supported by the Tado X API")
122
128
  def get_capabilities(self, zone):
123
129
  """
124
130
  Gets current capabilities of zone.
125
131
  """
126
-
127
- raise TadoNotSupportedException(
128
- "This method is not currently supported by the Tado X API"
129
- )
132
+ pass
130
133
 
131
134
  def get_climate(self, zone):
132
135
  """
@@ -139,6 +142,7 @@ class TadoX(Tado):
139
142
  "humidity": data["humidity"]["percentage"],
140
143
  }
141
144
 
145
+ @not_supported("Tado X API only support seven days timetable")
142
146
  def set_timetable(self, zone: int, timetable: Timetable) -> None:
143
147
  """
144
148
  Set the Timetable type currently active
@@ -146,14 +150,9 @@ class TadoX(Tado):
146
150
  id = 1 : THREE_DAY (MONDAY_TO_FRIDAY, SATURDAY, SUNDAY)
147
151
  id = 3 : SEVEN_DAY (MONDAY, TUESDAY, WEDNESDAY ...)
148
152
  """
153
+ pass
149
154
 
150
- raise TadoNotSupportedException(
151
- "Tado X API only support seven days timetable"
152
- )
153
-
154
- def get_schedule(
155
- self, zone: int, timetable: Timetable, day=None
156
- ) -> dict[str, Any]:
155
+ def get_schedule(self, zone: int, timetable: Timetable, day=None) -> dict[str, Any]:
157
156
  """
158
157
  Get the JSON representation of the schedule for a zone.
159
158
  Zone has 3 different schedules, one for each timetable (see setTimetable)
@@ -266,14 +265,12 @@ class TadoX(Tado):
266
265
 
267
266
  return self._http.request(request)
268
267
 
268
+ @not_supported("Concept of zones is not available by Tado X API, they use rooms")
269
269
  def get_zone_overlay_default(self, zone: int):
270
270
  """
271
271
  Get current overlay default settings for zone.
272
272
  """
273
-
274
- raise TadoNotSupportedException(
275
- "Concept of zones is not available by Tado X API, they use rooms"
276
- )
273
+ pass
277
274
 
278
275
  def get_open_window_detected(self, zone):
279
276
  """
@@ -292,19 +289,21 @@ class TadoX(Tado):
292
289
  Sets the window in zone to open
293
290
  Note: This can only be set if an open window was detected in this zone
294
291
  """
292
+ request = TadoXRequest()
293
+ request.command = f"rooms/{zone}/openWindow"
294
+ request.action = Action.SET
295
295
 
296
- raise TadoNotSupportedException(
297
- "This method is not currently supported by the Tado X API"
298
- )
296
+ return self._http.request(request)
299
297
 
300
298
  def reset_open_window(self, zone):
301
299
  """
302
300
  Sets the window in zone to closed
303
301
  """
302
+ request = TadoXRequest()
303
+ request.command = f"rooms/{zone}/openWindow"
304
+ request.action = Action.RESET
304
305
 
305
- raise TadoNotSupportedException(
306
- "This method is not currently supported by the Tado X API"
307
- )
306
+ return self._http.request(request)
308
307
 
309
308
  def get_device_info(self, device_id, cmd=""):
310
309
  """
@@ -347,3 +346,30 @@ class TadoX(Tado):
347
346
  request.payload = {"childLockEnabled": child_lock}
348
347
 
349
348
  self._http.request(request)
349
+
350
+ def set_flow_temperature_optimization(self, max_flow_temperature: float):
351
+ """
352
+ Set the flow temperature optimization.
353
+
354
+ max_flow_temperature: float, the maximum flow temperature in Celsius
355
+ """
356
+
357
+ request = TadoXRequest()
358
+ request.action = Action.CHANGE
359
+ request.domain = Domain.HOME
360
+ request.command = "settings/flowTemperatureOptimization"
361
+ request.payload = {"maxFlowTemperature": max_flow_temperature}
362
+
363
+ return self._http.request(request)
364
+
365
+ def get_flow_temperature_optimization(self):
366
+ """
367
+ Get the current flow temperature optimization
368
+ """
369
+
370
+ request = TadoXRequest()
371
+ request.action = Action.GET
372
+ request.domain = Domain.HOME
373
+ request.command = "settings/flowTemperatureOptimization"
374
+
375
+ return self._http.request(request)
@@ -619,4 +619,4 @@ class Tado:
619
619
  request.endpoint = Endpoint.MINDER
620
620
  request.params = {"from": date}
621
621
 
622
- return self._http.request(request)
622
+ return self._http.request(request)
@@ -317,4 +317,4 @@ class Tado:
317
317
  )
318
318
 
319
319
  # pylint: enable=invalid-name
320
- # endregion
320
+ # endregion
@@ -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"]
@@ -7,19 +7,19 @@ import logging
7
7
  from typing import Any, Self
8
8
 
9
9
  from PyTado.const import (
10
+ CONST_CONNECTION_OFFLINE,
11
+ CONST_HORIZONTAL_SWING_OFF,
10
12
  CONST_HVAC_HEAT,
11
13
  CONST_HVAC_IDLE,
12
14
  CONST_HVAC_OFF,
13
- CONST_CONNECTION_OFFLINE,
14
15
  CONST_MODE_HEAT,
15
16
  CONST_MODE_OFF,
16
17
  CONST_MODE_SMART_SCHEDULE,
17
18
  CONST_VERTICAL_SWING_OFF,
18
- CONST_HORIZONTAL_SWING_OFF,
19
19
  DEFAULT_TADOX_PRECISION,
20
20
  )
21
- from .my_zone import TadoZone
22
21
 
22
+ from .my_zone import TadoZone
23
23
 
24
24
  _LOGGER = logging.getLogger(__name__)
25
25
 
@@ -46,15 +46,11 @@ class TadoXZone(TadoZone):
46
46
  kwargs["current_temp"] = float(inside_temp["value"])
47
47
  kwargs["current_temp_timestamp"] = None
48
48
  if "precision" in sensor_data["insideTemperature"]:
49
- kwargs["precision"] = sensor_data["insideTemperature"][
50
- "precision"
51
- ]["celsius"]
49
+ kwargs["precision"] = sensor_data["insideTemperature"]["precision"]["celsius"]
52
50
 
53
51
  # X-specific humidity parsing
54
52
  if "humidity" in sensor_data:
55
- kwargs["current_humidity"] = float(
56
- sensor_data["humidity"]["percentage"]
57
- )
53
+ kwargs["current_humidity"] = float(sensor_data["humidity"]["percentage"])
58
54
  kwargs["current_humidity_timestamp"] = None
59
55
 
60
56
  # Tado mode processing
@@ -74,13 +70,8 @@ class TadoXZone(TadoZone):
74
70
  # Setting processing
75
71
  if "setting" in data:
76
72
  # X-specific temperature setting
77
- if (
78
- "temperature" in data["setting"]
79
- and data["setting"]["temperature"] is not None
80
- ):
81
- kwargs["target_temp"] = float(
82
- data["setting"]["temperature"]["value"]
83
- )
73
+ if "temperature" in data["setting"] and data["setting"]["temperature"] is not None:
74
+ kwargs["target_temp"] = float(data["setting"]["temperature"]["value"])
84
75
 
85
76
  setting = data["setting"]
86
77
 
@@ -106,9 +97,7 @@ class TadoXZone(TadoZone):
106
97
  else:
107
98
  kwargs["current_hvac_action"] = CONST_HVAC_HEAT
108
99
 
109
- kwargs["heating_power_percentage"] = data["heatingPower"][
110
- "percentage"
111
- ]
100
+ kwargs["heating_power_percentage"] = data["heatingPower"]["percentage"]
112
101
  else:
113
102
  kwargs["heating_power_percentage"] = 0
114
103
  kwargs["current_hvac_action"] = CONST_HVAC_OFF
@@ -120,12 +109,8 @@ class TadoXZone(TadoZone):
120
109
  kwargs["current_hvac_mode"] = (
121
110
  CONST_MODE_HEAT if power == "ON" else CONST_MODE_OFF
122
111
  )
123
- kwargs["overlay_termination_type"] = manual_termination[
124
- "type"
125
- ]
126
- kwargs["overlay_termination_timestamp"] = (
127
- manual_termination["projectedExpiry"]
128
- )
112
+ kwargs["overlay_termination_type"] = manual_termination["type"]
113
+ kwargs["overlay_termination_timestamp"] = manual_termination["projectedExpiry"]
129
114
  else:
130
115
  kwargs["current_hvac_mode"] = CONST_MODE_SMART_SCHEDULE
131
116
  kwargs["overlay_termination_type"] = None
@@ -133,17 +118,15 @@ class TadoXZone(TadoZone):
133
118
  else:
134
119
  kwargs["current_hvac_mode"] = CONST_MODE_SMART_SCHEDULE
135
120
 
136
- kwargs["available"] = (
137
- kwargs.get("connection") != CONST_CONNECTION_OFFLINE
138
- )
121
+ kwargs["available"] = kwargs.get("connection") != CONST_CONNECTION_OFFLINE
139
122
 
140
123
  # Termination conditions
141
124
  if "terminationCondition" in data:
142
- kwargs["default_overlay_termination_type"] = data[
143
- "terminationCondition"
144
- ].get("type", None)
145
- kwargs["default_overlay_termination_duration"] = data[
146
- "terminationCondition"
147
- ].get("durationInSeconds", None)
125
+ kwargs["default_overlay_termination_type"] = data["terminationCondition"].get(
126
+ "type", None
127
+ )
128
+ kwargs["default_overlay_termination_duration"] = data["terminationCondition"].get(
129
+ "durationInSeconds", None
130
+ )
148
131
 
149
132
  return cls(zone_id=zone_id, **kwargs)
@@ -35,7 +35,6 @@ class TadoZone:
35
35
 
36
36
  zone_id: int
37
37
  current_temp: float | None = None
38
- connection: str | None = None
39
38
  current_temp_timestamp: str | None = None
40
39
  current_humidity: float | None = None
41
40
  current_humidity_timestamp: str | None = None
@@ -51,6 +50,7 @@ class TadoZone:
51
50
  available: bool = False
52
51
  power: str | None = None
53
52
  link: str | None = None
53
+ connection: str | None = None
54
54
  ac_power_timestamp: str | None = None
55
55
  heating_power_timestamp: str | None = None
56
56
  ac_power: str | None = None
@@ -137,7 +137,11 @@ class TadoZone:
137
137
  kwargs["power"] = power
138
138
  if power == "ON":
139
139
  kwargs["current_hvac_action"] = CONST_HVAC_IDLE
140
- if "mode" not in setting and "type" in setting and setting["type"] in TADO_HVAC_ACTION_TO_MODES:
140
+ if (
141
+ "mode" not in setting
142
+ and "type" in setting
143
+ and setting["type"] in TADO_HVAC_ACTION_TO_MODES
144
+ ):
141
145
  # v2 devices do not have mode so we have to figure it out
142
146
  # from type
143
147
  kwargs["current_hvac_mode"] = TADO_HVAC_ACTION_TO_MODES[setting["type"]]
@@ -177,7 +181,9 @@ class TadoZone:
177
181
  if "heatingPower" in activity_data and activity_data["heatingPower"] is not None:
178
182
  kwargs["heating_power"] = activity_data["heatingPower"].get("value", None)
179
183
  kwargs["heating_power_timestamp"] = activity_data["heatingPower"]["timestamp"]
180
- kwargs["heating_power_percentage"] = float(activity_data["heatingPower"].get("percentage", 0))
184
+ kwargs["heating_power_percentage"] = float(
185
+ activity_data["heatingPower"].get("percentage", 0)
186
+ )
181
187
 
182
188
  if kwargs["heating_power_percentage"] > 0.0 and power == "ON":
183
189
  kwargs["current_hvac_action"] = CONST_HVAC_HEAT
@@ -187,15 +193,23 @@ class TadoZone:
187
193
  if "overlay" in data and data["overlay"] is not None:
188
194
  if "termination" in data["overlay"] and "type" in data["overlay"]["termination"]:
189
195
  kwargs["overlay_termination_type"] = data["overlay"]["termination"]["type"]
190
- kwargs["overlay_termination_timestamp"] = data["overlay"]["termination"].get("expiry", None)
196
+ kwargs["overlay_termination_timestamp"] = data["overlay"]["termination"].get(
197
+ "expiry", None
198
+ )
191
199
  else:
192
200
  kwargs["current_hvac_mode"] = CONST_MODE_SMART_SCHEDULE
193
201
 
194
- kwargs["connection"] = data["connectionState"]["value"] if "connectionState" in data else None
202
+ kwargs["connection"] = (
203
+ data["connectionState"]["value"] if "connectionState" in data else None
204
+ )
195
205
  kwargs["available"] = kwargs["link"] != CONST_LINK_OFFLINE
196
206
 
197
207
  if "terminationCondition" in data:
198
- kwargs["default_overlay_termination_type"] = data["terminationCondition"].get("type", None)
199
- kwargs["default_overlay_termination_duration"] = data["terminationCondition"].get("durationInSeconds", None)
208
+ kwargs["default_overlay_termination_type"] = data["terminationCondition"].get(
209
+ "type", None
210
+ )
211
+ kwargs["default_overlay_termination_duration"] = data["terminationCondition"].get(
212
+ "durationInSeconds", None
213
+ )
200
214
 
201
215
  return cls(zone_id=zone_id, **kwargs)