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.
- python_tado-0.19.0/PKG-INFO +205 -0
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/__main__.py +5 -4
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/const.py +1 -1
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/http.py +3 -2
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/__init__.py +2 -0
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/api/__init__.py +2 -0
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/api/hops_tado.py +63 -37
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/api/my_tado.py +1 -1
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/interface/interface.py +1 -1
- python_tado-0.19.0/PyTado/logger.py +24 -0
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/zone/__init__.py +2 -0
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/zone/hops_zone.py +17 -34
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/zone/my_zone.py +21 -7
- {python_tado-0.18.7 → python_tado-0.19.0}/README.md +48 -13
- python_tado-0.19.0/pyproject.toml +74 -0
- python_tado-0.18.7/PKG-INFO +0 -840
- python_tado-0.18.7/PyTado/logger.py +0 -50
- python_tado-0.18.7/pyproject.toml +0 -309
- python_tado-0.18.7/python_tado.egg-info/PKG-INFO +0 -840
- python_tado-0.18.7/python_tado.egg-info/SOURCES.txt +0 -30
- python_tado-0.18.7/python_tado.egg-info/dependency_links.txt +0 -1
- python_tado-0.18.7/python_tado.egg-info/entry_points.txt +0 -2
- python_tado-0.18.7/python_tado.egg-info/not-zip-safe +0 -1
- python_tado-0.18.7/python_tado.egg-info/requires.txt +0 -16
- python_tado-0.18.7/python_tado.egg-info/top_level.txt +0 -1
- python_tado-0.18.7/setup.cfg +0 -4
- python_tado-0.18.7/tests/test_hops_zone.py +0 -152
- python_tado-0.18.7/tests/test_http.py +0 -190
- python_tado-0.18.7/tests/test_my_tado.py +0 -97
- python_tado-0.18.7/tests/test_my_zone.py +0 -680
- python_tado-0.18.7/tests/test_tado_interface.py +0 -81
- {python_tado-0.18.7 → python_tado-0.19.0}/AUTHORS +0 -0
- {python_tado-0.18.7 → python_tado-0.19.0}/LICENSE +0 -0
- {python_tado-0.18.7 → python_tado-0.19.0}/PyTado/__init__.py +0 -0
- {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
|
+
[](https://github.com/wmalgadey/PyTado/actions/workflows/lint-and-test-matrix.yml)
|
|
43
|
+
[](https://github.com/wmalgadey/PyTado/actions/workflows/publish-to-pypi.yml)
|
|
44
|
+
[](https://badge.fury.io/py/python-tado)
|
|
45
|
+
[](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
|
+

|
|
85
|
+

|
|
86
|
+

|
|
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(
|
|
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(
|
|
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
|
|
|
@@ -57,7 +57,8 @@ class Mode(enum.Enum):
|
|
|
57
57
|
PLAIN = 2
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
class DeviceActivationStatus(enum.
|
|
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"
|
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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)
|
|
@@ -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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
144
|
-
|
|
145
|
-
kwargs["default_overlay_termination_duration"] = data[
|
|
146
|
-
"
|
|
147
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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"] =
|
|
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(
|
|
199
|
-
|
|
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)
|