python-tado 0.17.5__tar.gz → 0.17.7__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.17.5 → python_tado-0.17.7}/AUTHORS +1 -0
- {python-tado-0.17.5/python_tado.egg-info → python_tado-0.17.7}/PKG-INFO +1 -1
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/const.py +1 -1
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/exceptions.py +3 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/http/__init__.py +1 -1
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/http/http.py +32 -1
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/interface.py +157 -35
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/zone.py +75 -11
- {python-tado-0.17.5 → python_tado-0.17.7/python_tado.egg-info}/PKG-INFO +1 -1
- {python-tado-0.17.5 → python_tado-0.17.7}/setup.py +1 -1
- {python-tado-0.17.5 → python_tado-0.17.7}/LICENSE +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/__init__.py +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/__main__.py +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/logging/__init__.py +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/logging/logging.py +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/polyfill/__init__.py +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/PyTado/polyfill/strenum.py +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/README.md +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/python_tado.egg-info/SOURCES.txt +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/python_tado.egg-info/dependency_links.txt +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/python_tado.egg-info/entry_points.txt +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/python_tado.egg-info/not-zip-safe +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/python_tado.egg-info/requires.txt +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/python_tado.egg-info/top_level.txt +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/setup.cfg +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/tests/test_tado.py +0 -0
- {python-tado-0.17.5 → python_tado-0.17.7}/tests/test_zone.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-tado
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.7
|
|
4
4
|
Summary: PyTado from chrism0dwk, modfied by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi, Coffee2CodeNL, chiefdragon
|
|
5
5
|
Home-page: https://github.com/wmalgadey/PyTado
|
|
6
6
|
Author: chrism0dwk, w.malgadey
|
|
@@ -43,7 +43,7 @@ CONST_HORIZONTAL_SWING_RIGHT = "RIGHT"
|
|
|
43
43
|
|
|
44
44
|
# When we change the temperature setting, we need an overlay mode
|
|
45
45
|
CONST_OVERLAY_TADO_MODE = "NEXT_TIME_BLOCK" # wait until tado changes the mode automatic
|
|
46
|
-
CONST_OVERLAY_MANUAL = "MANUAL" # the user has
|
|
46
|
+
CONST_OVERLAY_MANUAL = "MANUAL" # the user has changed the temperature or mode manually
|
|
47
47
|
CONST_OVERLAY_TIMER = "TIMER" # the temperature will be reset after a timespan
|
|
48
48
|
|
|
49
49
|
# Heat always comes first since we get the
|
|
@@ -8,6 +8,9 @@ class TadoException(Exception):
|
|
|
8
8
|
class TadoNotSupportedException(TadoException):
|
|
9
9
|
"""Exception to indicate a requested action is not supported by Tado."""
|
|
10
10
|
|
|
11
|
+
class TadoXNotSupportedException(TadoException):
|
|
12
|
+
"""Exception to indicate a requested action is not supported by the Tado X API."""
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
class TadoCredentialsException(TadoException):
|
|
13
16
|
"""Exception to indicate something with credentials"""
|
|
@@ -1 +1 @@
|
|
|
1
|
-
from .http import Action, Domain, Endpoint, Http, Mode, TadoRequest
|
|
1
|
+
from .http import Action, Domain, Endpoint, Http, Mode, TadoRequest, TadoXRequest
|
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
import logging
|
|
7
7
|
import pprint
|
|
8
8
|
from datetime import datetime, timedelta
|
|
9
|
+
from typing import Union
|
|
9
10
|
|
|
10
11
|
try:
|
|
11
12
|
from enum import StrEnum, ReprEnum
|
|
@@ -26,6 +27,11 @@ class Endpoint(StrEnum):
|
|
|
26
27
|
MOBILE = "https://my.tado.com/mobile/1.9/"
|
|
27
28
|
EIQ = "https://energy-insights.tado.com/api/"
|
|
28
29
|
|
|
30
|
+
class EndpointX(StrEnum):
|
|
31
|
+
"""Endpoint URL Enum"""
|
|
32
|
+
API = "https://hops.tado.com/"
|
|
33
|
+
EIQ = "https://energy-insights.tado.com/api/"
|
|
34
|
+
|
|
29
35
|
|
|
30
36
|
class Domain(StrEnum):
|
|
31
37
|
"""API Request Domain Enum"""
|
|
@@ -40,6 +46,7 @@ class Action(StrEnum):
|
|
|
40
46
|
SET = "POST"
|
|
41
47
|
RESET = "DELETE"
|
|
42
48
|
CHANGE = "PUT"
|
|
49
|
+
UPDATE = "PATCH"
|
|
43
50
|
|
|
44
51
|
|
|
45
52
|
class Mode(Enum):
|
|
@@ -58,6 +65,16 @@ class TadoRequest:
|
|
|
58
65
|
device = None
|
|
59
66
|
mode = Mode.OBJECT
|
|
60
67
|
|
|
68
|
+
class TadoXRequest:
|
|
69
|
+
"""Data Container"""
|
|
70
|
+
endpoint = EndpointX.API
|
|
71
|
+
command = None
|
|
72
|
+
action = Action.GET
|
|
73
|
+
payload = None
|
|
74
|
+
domain = Domain.HOME
|
|
75
|
+
device = None
|
|
76
|
+
mode = Mode.OBJECT
|
|
77
|
+
|
|
61
78
|
|
|
62
79
|
class TadoResponse:
|
|
63
80
|
"""Unimplemented Response Container
|
|
@@ -83,6 +100,8 @@ class Http:
|
|
|
83
100
|
# Id
|
|
84
101
|
id = 0
|
|
85
102
|
|
|
103
|
+
isX = False
|
|
104
|
+
|
|
86
105
|
__username = None
|
|
87
106
|
__password = None
|
|
88
107
|
|
|
@@ -111,7 +130,7 @@ class Http:
|
|
|
111
130
|
f"Response:\n\tStatusCode: {response_status}\n\tData: {response.json()}"
|
|
112
131
|
)
|
|
113
132
|
|
|
114
|
-
def request(self, request: TadoRequest):
|
|
133
|
+
def request(self, request: Union[TadoRequest, TadoXRequest]):
|
|
115
134
|
"""Request something from the API with a TadoRequest"""
|
|
116
135
|
self.__refresh_token()
|
|
117
136
|
|
|
@@ -250,6 +269,7 @@ class Http:
|
|
|
250
269
|
if response.status_code == 200:
|
|
251
270
|
self.__set_oauth_header(response.json())
|
|
252
271
|
self.__get_id()
|
|
272
|
+
self.__check_x()
|
|
253
273
|
|
|
254
274
|
def __get_id(self):
|
|
255
275
|
if self.id == 0:
|
|
@@ -257,3 +277,14 @@ class Http:
|
|
|
257
277
|
request.action = Action.GET
|
|
258
278
|
request.domain = Domain.ME
|
|
259
279
|
self.id = self.request(request)['homes'][0]['id']
|
|
280
|
+
|
|
281
|
+
def __check_x(self):
|
|
282
|
+
# get home info
|
|
283
|
+
request = TadoRequest()
|
|
284
|
+
request.action = Action.GET
|
|
285
|
+
request.domain = Domain.HOME
|
|
286
|
+
request.command = ""
|
|
287
|
+
home = self.request(request)
|
|
288
|
+
if 'generation' in home and home['generation'] == 'LINE_X':
|
|
289
|
+
self.isX = True
|
|
290
|
+
self.log.debug("Tado X API detected")
|
|
@@ -7,10 +7,11 @@ import logging
|
|
|
7
7
|
import warnings
|
|
8
8
|
from enum import IntEnum
|
|
9
9
|
|
|
10
|
-
from .exceptions import TadoNotSupportedException
|
|
11
|
-
from PyTado.http import Action, Domain, Endpoint, Http, Mode, TadoRequest
|
|
10
|
+
from .exceptions import TadoNotSupportedException, TadoXNotSupportedException
|
|
11
|
+
from PyTado.http import Action, Domain, Endpoint, Http, Mode, TadoRequest, TadoXRequest
|
|
12
12
|
from PyTado.logging import Logger
|
|
13
13
|
from .zone import TadoZone
|
|
14
|
+
from typing import Any
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class Tado:
|
|
@@ -77,10 +78,16 @@ class Tado:
|
|
|
77
78
|
Gets device information.
|
|
78
79
|
"""
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
if self.http.isX:
|
|
82
|
+
request = TadoXRequest()
|
|
83
|
+
request.command = "roomsAndDevices"
|
|
84
|
+
rooms: list[dict[str, Any]] = self.http.request(request)['rooms']
|
|
85
|
+
devices = [device for room in rooms for device in room['devices']]
|
|
86
|
+
return devices
|
|
87
|
+
else:
|
|
88
|
+
request = TadoRequest()
|
|
89
|
+
request.command = "devices"
|
|
90
|
+
return self.http.request(request)
|
|
84
91
|
|
|
85
92
|
# <editor-fold desc="Deprecated">
|
|
86
93
|
def getZones(self):
|
|
@@ -96,10 +103,15 @@ class Tado:
|
|
|
96
103
|
Gets zones information.
|
|
97
104
|
"""
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
if self.http.isX:
|
|
107
|
+
request = TadoXRequest()
|
|
108
|
+
request.command = "roomsAndDevices"
|
|
109
|
+
return self.http.request(request)['rooms']
|
|
110
|
+
else:
|
|
111
|
+
request = TadoRequest()
|
|
112
|
+
request.command = "zones"
|
|
113
|
+
return self.http.request(request)
|
|
101
114
|
|
|
102
|
-
return self.http.request(request)
|
|
103
115
|
|
|
104
116
|
# <editor-fold desc="Deprecated">
|
|
105
117
|
def getZoneState(self, zone):
|
|
@@ -114,8 +126,10 @@ class Tado:
|
|
|
114
126
|
"""
|
|
115
127
|
Gets current state of Zone as a TadoZone object.
|
|
116
128
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
if self.http.isX:
|
|
130
|
+
return TadoZone(self.get_state(zone), zone, isX=True)
|
|
131
|
+
else:
|
|
132
|
+
return TadoZone(self.get_state(zone), zone)
|
|
119
133
|
|
|
120
134
|
# <editor-fold desc="Deprecated">
|
|
121
135
|
def getZoneStates(self):
|
|
@@ -131,8 +145,12 @@ class Tado:
|
|
|
131
145
|
Gets current states of all zones.
|
|
132
146
|
"""
|
|
133
147
|
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
if self.http.isX:
|
|
149
|
+
request = TadoXRequest()
|
|
150
|
+
request.command = "rooms"
|
|
151
|
+
else:
|
|
152
|
+
request = TadoRequest()
|
|
153
|
+
request.command = "zoneStates"
|
|
136
154
|
|
|
137
155
|
return self.http.request(request)
|
|
138
156
|
|
|
@@ -149,10 +167,14 @@ class Tado:
|
|
|
149
167
|
"""
|
|
150
168
|
Gets current state of Zone.
|
|
151
169
|
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
170
|
+
if self.http.isX:
|
|
171
|
+
request = TadoXRequest()
|
|
172
|
+
request.command = f"rooms/{zone}"
|
|
173
|
+
data = self.http.request(request)
|
|
174
|
+
else:
|
|
175
|
+
request = TadoRequest()
|
|
176
|
+
request.command = f"zones/{zone}/state"
|
|
177
|
+
data = {**self.http.request(request), **self.get_zone_overlay_default(zone)}
|
|
156
178
|
|
|
157
179
|
return data
|
|
158
180
|
|
|
@@ -236,7 +258,10 @@ class Tado:
|
|
|
236
258
|
def get_capabilities(self, zone):
|
|
237
259
|
"""
|
|
238
260
|
Gets current capabilities of zone.
|
|
261
|
+
TODO: This method is not currently supported by the Tado X API, check if it is needed
|
|
239
262
|
"""
|
|
263
|
+
if self.http.isX:
|
|
264
|
+
raise TadoXNotSupportedException("This method is not currently supported by the Tado X API")
|
|
240
265
|
|
|
241
266
|
request = TadoRequest()
|
|
242
267
|
request.command = f"zones/{zone:d}/capabilities"
|
|
@@ -258,7 +283,11 @@ class Tado:
|
|
|
258
283
|
"""
|
|
259
284
|
|
|
260
285
|
data = self.get_state(zone)['sensorDataPoints']
|
|
261
|
-
|
|
286
|
+
if self.http.isX:
|
|
287
|
+
return {'temperature': data['insideTemperature']['value'],
|
|
288
|
+
'humidity': data['humidity']['percentage']}
|
|
289
|
+
else:
|
|
290
|
+
return {'temperature': data['insideTemperature']['celsius'],
|
|
262
291
|
'humidity': data['humidity']['percentage']}
|
|
263
292
|
|
|
264
293
|
# <editor-fold desc="Deprecated">
|
|
@@ -275,15 +304,21 @@ class Tado:
|
|
|
275
304
|
Get the Timetable type currently active
|
|
276
305
|
"""
|
|
277
306
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
307
|
+
if self.http.isX:
|
|
308
|
+
# Not currently supported by the Tado X API
|
|
309
|
+
raise TadoXNotSupportedException("This method is not currently supported by the Tado X API")
|
|
310
|
+
else:
|
|
311
|
+
request = TadoRequest()
|
|
312
|
+
request.command = f"zones/{zone:d}/schedule/activeTimetable"
|
|
313
|
+
request.mode = Mode.PLAIN
|
|
314
|
+
data = self.http.request(request)
|
|
315
|
+
|
|
316
|
+
if "id" in data:
|
|
317
|
+
return Tado.Timetable(data["id"])
|
|
318
|
+
|
|
319
|
+
raise Exception(f"Returned data did not contain \"id\" : {str(data)}")
|
|
282
320
|
|
|
283
|
-
if "id" in data:
|
|
284
|
-
return Tado.Timetable(data["id"])
|
|
285
321
|
|
|
286
|
-
raise Exception(f"Returned data did not contain \"id\" : {str(data)}")
|
|
287
322
|
|
|
288
323
|
# <editor-fold desc="Deprecated">
|
|
289
324
|
def getHistoric(self, zone, date):
|
|
@@ -328,6 +363,10 @@ class Tado:
|
|
|
328
363
|
id = 3 : SEVEN_DAY (MONDAY, TUESDAY, WEDNESDAY ...)
|
|
329
364
|
"""
|
|
330
365
|
|
|
366
|
+
if self.http.isX:
|
|
367
|
+
# Not currently supported by the Tado X API
|
|
368
|
+
raise TadoXNotSupportedException("This method is not currently supported by the Tado X API")
|
|
369
|
+
|
|
331
370
|
# Type checking
|
|
332
371
|
if not isinstance(_id, Tado.Timetable):
|
|
333
372
|
raise TypeError('id must be an instance of Tado.Timetable')
|
|
@@ -350,12 +389,19 @@ class Tado:
|
|
|
350
389
|
|
|
351
390
|
# </editor-fold>
|
|
352
391
|
|
|
353
|
-
def get_schedule(self, zone, _id, day=None):
|
|
392
|
+
def get_schedule(self, zone, _id=None, day=None):
|
|
354
393
|
"""
|
|
355
394
|
Get the JSON representation of the schedule for a zone.
|
|
356
395
|
Zone has 3 different schedules, one for each timetable (see setTimetable)
|
|
357
396
|
"""
|
|
358
397
|
|
|
398
|
+
if self.http.isX:
|
|
399
|
+
request = TadoXRequest()
|
|
400
|
+
request.command = f'rooms/{zone}/schedule'
|
|
401
|
+
return self.http.request(request)
|
|
402
|
+
|
|
403
|
+
if not _id:
|
|
404
|
+
raise ValueError('id must be an instance of Tado.Timetable')
|
|
359
405
|
# Type checking
|
|
360
406
|
if not isinstance(_id, Tado.Timetable):
|
|
361
407
|
raise TypeError('id must be an instance of Tado.Timetable')
|
|
@@ -377,14 +423,25 @@ class Tado:
|
|
|
377
423
|
|
|
378
424
|
# </editor-fold>
|
|
379
425
|
|
|
380
|
-
def set_schedule(self, zone, _id, day
|
|
426
|
+
def set_schedule(self, zone, data, _id=None, day=None):
|
|
381
427
|
"""
|
|
382
|
-
Set the schedule for a zone, day is required
|
|
428
|
+
Set the schedule for a zone, day is required for non-X API
|
|
383
429
|
"""
|
|
384
430
|
|
|
431
|
+
# TODO: sometimes gives back 400 error
|
|
432
|
+
if self.http.isX:
|
|
433
|
+
request = TadoXRequest()
|
|
434
|
+
request.action = Action.SET
|
|
435
|
+
request.command = f'rooms/{zone}/schedule'
|
|
436
|
+
request.payload = data
|
|
437
|
+
request.mode = Mode.OBJECT
|
|
438
|
+
return self.http.request(request)
|
|
439
|
+
|
|
385
440
|
# Type checking
|
|
386
441
|
if not isinstance(_id, Tado.Timetable):
|
|
387
442
|
raise TypeError('id must be an instance of Tado.Timetable')
|
|
443
|
+
if not isinstance(day, int):
|
|
444
|
+
raise ValueError('day must be an integer')
|
|
388
445
|
|
|
389
446
|
request = TadoRequest()
|
|
390
447
|
request.command = f"zones/{zone:d}/schedule/timetables/{_id:d}/blocks/{day}"
|
|
@@ -427,7 +484,10 @@ class Tado:
|
|
|
427
484
|
Gets air quality information
|
|
428
485
|
"""
|
|
429
486
|
|
|
430
|
-
|
|
487
|
+
if self.http.isX:
|
|
488
|
+
request = TadoXRequest()
|
|
489
|
+
else:
|
|
490
|
+
request = TadoRequest()
|
|
431
491
|
request.command = "airComfort"
|
|
432
492
|
|
|
433
493
|
return self.http.request(request)
|
|
@@ -461,6 +521,11 @@ class Tado:
|
|
|
461
521
|
Gets getAppUsersRelativePositions data
|
|
462
522
|
"""
|
|
463
523
|
|
|
524
|
+
if self.http.isX:
|
|
525
|
+
# Not currently supported by the Tado X API
|
|
526
|
+
# TODO: reverse engineer mobile API
|
|
527
|
+
raise TadoXNotSupportedException("This method is not currently supported by the Tado X API")
|
|
528
|
+
|
|
464
529
|
request = TadoRequest()
|
|
465
530
|
request.command = "getAppUsersRelativePositions"
|
|
466
531
|
request.endpoint = Endpoint.MOBILE
|
|
@@ -500,12 +565,19 @@ class Tado:
|
|
|
500
565
|
Delete current overlay
|
|
501
566
|
"""
|
|
502
567
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
568
|
+
if self.http.isX:
|
|
569
|
+
request = TadoXRequest()
|
|
570
|
+
request.command = f"rooms/{zone}/resumeSchedule"
|
|
571
|
+
request.action = Action.SET
|
|
507
572
|
|
|
508
|
-
|
|
573
|
+
return self.http.request(request)
|
|
574
|
+
else:
|
|
575
|
+
request = TadoRequest()
|
|
576
|
+
request.command = f"zones/{zone:d}/overlay"
|
|
577
|
+
request.action = Action.RESET
|
|
578
|
+
request.mode = Mode.PLAIN
|
|
579
|
+
|
|
580
|
+
return self.http.request(request)
|
|
509
581
|
|
|
510
582
|
# <editor-fold desc="Deprecated">
|
|
511
583
|
def setZoneOverlay(self, zone, overlayMode, setTemp=None, duration=None, deviceType='HEATING', power="ON",
|
|
@@ -540,9 +612,24 @@ class Tado:
|
|
|
540
612
|
|
|
541
613
|
post_data = {
|
|
542
614
|
"setting": {"type": device_type, "power": power},
|
|
543
|
-
"termination": {"
|
|
615
|
+
"termination": {"type": overlay_mode},
|
|
544
616
|
}
|
|
545
617
|
|
|
618
|
+
if self.http.isX:
|
|
619
|
+
if set_temp is not None:
|
|
620
|
+
post_data["setting"]["temperature"] = {"value": set_temp, "valueRaw": set_temp, "precision": 0.1}
|
|
621
|
+
|
|
622
|
+
if duration is not None:
|
|
623
|
+
post_data["termination"]["durationInSeconds"] = duration
|
|
624
|
+
|
|
625
|
+
request = TadoXRequest()
|
|
626
|
+
request.command = f"rooms/{zone}/manualControl"
|
|
627
|
+
request.action = Action.SET
|
|
628
|
+
request.payload = post_data
|
|
629
|
+
|
|
630
|
+
return self.http.request(request)
|
|
631
|
+
|
|
632
|
+
|
|
546
633
|
if set_temp is not None:
|
|
547
634
|
post_data["setting"]["temperature"] = {"celsius": set_temp}
|
|
548
635
|
if fan_speed is not None:
|
|
@@ -584,6 +671,10 @@ class Tado:
|
|
|
584
671
|
Get current overlay default settings for zone.
|
|
585
672
|
"""
|
|
586
673
|
|
|
674
|
+
if self.http.isX:
|
|
675
|
+
# Not currently supported by the Tado X API
|
|
676
|
+
raise TadoXNotSupportedException("This method is not currently supported by the Tado X API")
|
|
677
|
+
|
|
587
678
|
request = TadoRequest()
|
|
588
679
|
request.command = f"zones/{zone:d}/defaultOverlay"
|
|
589
680
|
|
|
@@ -698,6 +789,12 @@ class Tado:
|
|
|
698
789
|
|
|
699
790
|
data = self.get_state(zone)
|
|
700
791
|
|
|
792
|
+
if self.http.isX:
|
|
793
|
+
if 'activated'in data['openWindow']:
|
|
794
|
+
return {'openWindowDetected': True}
|
|
795
|
+
else:
|
|
796
|
+
return {'openWindowDetected': False}
|
|
797
|
+
|
|
701
798
|
if "openWindowDetected" in data:
|
|
702
799
|
return {'openWindowDetected': data['openWindowDetected']}
|
|
703
800
|
else:
|
|
@@ -718,6 +815,10 @@ class Tado:
|
|
|
718
815
|
Note: This can only be set if an open window was detected in this zone
|
|
719
816
|
"""
|
|
720
817
|
|
|
818
|
+
if self.http.isX:
|
|
819
|
+
# Not currently supported by the Tado X API
|
|
820
|
+
raise TadoXNotSupportedException("This method is not currently supported by the Tado X API")
|
|
821
|
+
|
|
721
822
|
request = TadoRequest()
|
|
722
823
|
request.command = f"zones/{zone:d}/state/openWindow/activate"
|
|
723
824
|
request.action = Action.SET
|
|
@@ -739,6 +840,10 @@ class Tado:
|
|
|
739
840
|
Sets the window in zone to closed
|
|
740
841
|
"""
|
|
741
842
|
|
|
843
|
+
if self.http.isX:
|
|
844
|
+
# TODO test with X
|
|
845
|
+
raise TadoXNotSupportedException("This method is not currently supported by the Tado X API")
|
|
846
|
+
|
|
742
847
|
request = TadoRequest()
|
|
743
848
|
request.command = f"zones/{zone:d}/state/openWindow"
|
|
744
849
|
request.action = Action.RESET
|
|
@@ -761,6 +866,12 @@ class Tado:
|
|
|
761
866
|
Gets information about devices
|
|
762
867
|
with option to get specific info i.e. cmd='temperatureOffset'
|
|
763
868
|
"""
|
|
869
|
+
|
|
870
|
+
if self.http.isX:
|
|
871
|
+
# Not currently supported by the Tado X API
|
|
872
|
+
# Is included in the roomsAndDevices endpoint
|
|
873
|
+
raise TadoXNotSupportedException("This method is not currently supported by the Tado X API")
|
|
874
|
+
|
|
764
875
|
request = TadoRequest()
|
|
765
876
|
request.command = cmd
|
|
766
877
|
request.action = Action.GET
|
|
@@ -783,6 +894,15 @@ class Tado:
|
|
|
783
894
|
Set the Temperature offset on the device.
|
|
784
895
|
"""
|
|
785
896
|
|
|
897
|
+
if self.http.isX:
|
|
898
|
+
request = TadoXRequest()
|
|
899
|
+
request.command = f"roomsAndDevices/devices/{device_id}"
|
|
900
|
+
request.action = Action.UPDATE
|
|
901
|
+
request.payload = {"temperatureOffset": offset}
|
|
902
|
+
|
|
903
|
+
return self.http.request(request)
|
|
904
|
+
|
|
905
|
+
|
|
786
906
|
request = TadoRequest()
|
|
787
907
|
request.command = "temperatureOffset"
|
|
788
908
|
request.action = Action.CHANGE
|
|
@@ -914,6 +1034,7 @@ class Tado:
|
|
|
914
1034
|
def get_zone_control(self, zone):
|
|
915
1035
|
"""
|
|
916
1036
|
Get zone control information
|
|
1037
|
+
TODO: Test with X
|
|
917
1038
|
"""
|
|
918
1039
|
|
|
919
1040
|
request = TadoRequest()
|
|
@@ -924,6 +1045,7 @@ class Tado:
|
|
|
924
1045
|
def set_zone_heating_circuit(self, zone, heating_circuit):
|
|
925
1046
|
"""
|
|
926
1047
|
Sets the heating circuit for a zone
|
|
1048
|
+
TODO: Test with X
|
|
927
1049
|
"""
|
|
928
1050
|
|
|
929
1051
|
request = TadoRequest()
|
|
@@ -9,12 +9,17 @@ from .const import (
|
|
|
9
9
|
CONST_HVAC_IDLE,
|
|
10
10
|
CONST_HVAC_OFF,
|
|
11
11
|
CONST_LINK_OFFLINE,
|
|
12
|
+
CONST_MODE_HEAT,
|
|
12
13
|
CONST_MODE_OFF,
|
|
13
14
|
CONST_MODE_SMART_SCHEDULE,
|
|
14
15
|
DEFAULT_TADO_PRECISION,
|
|
15
16
|
TADO_HVAC_ACTION_TO_MODES,
|
|
16
17
|
TADO_MODES_TO_HVAC_ACTION,
|
|
17
18
|
TYPE_AIR_CONDITIONING,
|
|
19
|
+
CONST_VERTICAL_SWING_OFF,
|
|
20
|
+
CONST_HORIZONTAL_SWING_OFF,
|
|
21
|
+
CONST_FAN_SPEED_AUTO,
|
|
22
|
+
CONST_FAN_SPEED_OFF,
|
|
18
23
|
)
|
|
19
24
|
|
|
20
25
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -23,7 +28,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
23
28
|
class TadoZone:
|
|
24
29
|
"""Represent a tado zone."""
|
|
25
30
|
|
|
26
|
-
def __init__(self, data, zone_id):
|
|
31
|
+
def __init__(self, data, zone_id, isX: bool = False):
|
|
27
32
|
"""Create a tado zone."""
|
|
28
33
|
self._data = data
|
|
29
34
|
self._zone_id = zone_id
|
|
@@ -35,8 +40,11 @@ class TadoZone:
|
|
|
35
40
|
self._is_away = False
|
|
36
41
|
self._current_hvac_action = None
|
|
37
42
|
self._current_fan_speed = None
|
|
43
|
+
self._current_fan_level = None
|
|
38
44
|
self._current_hvac_mode = None
|
|
39
45
|
self._current_swing_mode = None
|
|
46
|
+
self._current_vertical_swing_mode = None
|
|
47
|
+
self._current_horizontal_swing_mode = None
|
|
40
48
|
self._target_temp = None
|
|
41
49
|
self._available = False
|
|
42
50
|
self._power = None
|
|
@@ -47,7 +55,6 @@ class TadoZone:
|
|
|
47
55
|
self._heating_power = None
|
|
48
56
|
self._heating_power_percentage = None
|
|
49
57
|
self._tado_mode = None
|
|
50
|
-
self._overlay_active = None
|
|
51
58
|
self._overlay_termination_type = None
|
|
52
59
|
self._overlay_termination_timestamp = None
|
|
53
60
|
self._preparation = None
|
|
@@ -57,6 +64,7 @@ class TadoZone:
|
|
|
57
64
|
self._precision = DEFAULT_TADO_PRECISION
|
|
58
65
|
self._default_overlay_termination_type = None
|
|
59
66
|
self._default_overlay_termination_duration = None
|
|
67
|
+
self.isX = isX
|
|
60
68
|
self.update_data(data)
|
|
61
69
|
|
|
62
70
|
@property
|
|
@@ -169,6 +177,11 @@ class TadoZone:
|
|
|
169
177
|
"""TADO Fan speed (tado const)."""
|
|
170
178
|
return self._current_fan_speed
|
|
171
179
|
|
|
180
|
+
@property
|
|
181
|
+
def current_fan_level(self):
|
|
182
|
+
"""TADO Fan level (tado const)."""
|
|
183
|
+
return self._current_fan_level
|
|
184
|
+
|
|
172
185
|
@property
|
|
173
186
|
def link(self):
|
|
174
187
|
"""Link (internet connection state)."""
|
|
@@ -189,6 +202,16 @@ class TadoZone:
|
|
|
189
202
|
"""TADO SWING Mode (tado const)."""
|
|
190
203
|
return self._current_swing_mode
|
|
191
204
|
|
|
205
|
+
@property
|
|
206
|
+
def current_vertical_swing_mode(self):
|
|
207
|
+
"""TADO VERTICAL SWING Mode (tado const)."""
|
|
208
|
+
return self._current_vertical_swing_mode
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def current_horizontal_swing_mode(self):
|
|
212
|
+
"""TADO HORIZONTAL SWING Mode (tado const)."""
|
|
213
|
+
return self._current_horizontal_swing_mode
|
|
214
|
+
|
|
192
215
|
@property
|
|
193
216
|
def target_temp(self):
|
|
194
217
|
"""Target temperature (C)."""
|
|
@@ -216,11 +239,13 @@ class TadoZone:
|
|
|
216
239
|
sensor_data = data["sensorDataPoints"]
|
|
217
240
|
|
|
218
241
|
if "insideTemperature" in sensor_data:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
242
|
+
if self.isX:
|
|
243
|
+
self._current_temp = float(sensor_data["insideTemperature"]["value"])
|
|
244
|
+
self._current_temp_timestamp = None
|
|
245
|
+
else:
|
|
246
|
+
temperature = float(sensor_data["insideTemperature"]["celsius"])
|
|
247
|
+
self._current_temp = temperature
|
|
248
|
+
self._current_temp_timestamp = sensor_data["insideTemperature"]["timestamp"]
|
|
224
249
|
if "precision" in sensor_data["insideTemperature"]:
|
|
225
250
|
self._precision = sensor_data["insideTemperature"]["precision"][
|
|
226
251
|
"celsius"
|
|
@@ -229,7 +254,10 @@ class TadoZone:
|
|
|
229
254
|
if "humidity" in sensor_data:
|
|
230
255
|
humidity = float(sensor_data["humidity"]["percentage"])
|
|
231
256
|
self._current_humidity = humidity
|
|
232
|
-
self.
|
|
257
|
+
if self.isX:
|
|
258
|
+
self._current_humidity_timestamp = None
|
|
259
|
+
else:
|
|
260
|
+
self._current_humidity_timestamp = sensor_data["humidity"]["timestamp"]
|
|
233
261
|
|
|
234
262
|
self._is_away = None
|
|
235
263
|
self._tado_mode = None
|
|
@@ -237,9 +265,12 @@ class TadoZone:
|
|
|
237
265
|
self._is_away = data["tadoMode"] == "AWAY"
|
|
238
266
|
self._tado_mode = data["tadoMode"]
|
|
239
267
|
|
|
268
|
+
|
|
240
269
|
self._link = None
|
|
241
270
|
if "link" in data:
|
|
242
271
|
self._link = data["link"]["state"]
|
|
272
|
+
if "connection" in data:
|
|
273
|
+
self._link = data["connection"]["state"]
|
|
243
274
|
|
|
244
275
|
self._current_hvac_action = CONST_HVAC_OFF
|
|
245
276
|
|
|
@@ -249,16 +280,22 @@ class TadoZone:
|
|
|
249
280
|
"temperature" in data["setting"]
|
|
250
281
|
and data["setting"]["temperature"] is not None
|
|
251
282
|
):
|
|
252
|
-
|
|
253
|
-
|
|
283
|
+
if self.isX:
|
|
284
|
+
self._target_temp = float(data["setting"]["temperature"]["value"])
|
|
285
|
+
else:
|
|
286
|
+
setting = float(data["setting"]["temperature"]["celsius"])
|
|
287
|
+
self._target_temp = setting
|
|
254
288
|
|
|
255
289
|
setting = data["setting"]
|
|
256
290
|
|
|
257
291
|
self._current_fan_speed = None
|
|
292
|
+
self._current_fan_level = None
|
|
258
293
|
# If there is no overlay, the mode will always be
|
|
259
294
|
# "SMART_SCHEDULE"
|
|
260
295
|
self._current_hvac_mode = CONST_MODE_OFF
|
|
261
296
|
self._current_swing_mode = CONST_MODE_OFF
|
|
297
|
+
self._current_vertical_swing_mode = CONST_VERTICAL_SWING_OFF
|
|
298
|
+
self._current_horizontal_swing_mode = CONST_HORIZONTAL_SWING_OFF
|
|
262
299
|
|
|
263
300
|
if "mode" in setting:
|
|
264
301
|
# v3 devices use mode
|
|
@@ -267,6 +304,12 @@ class TadoZone:
|
|
|
267
304
|
if "swing" in setting:
|
|
268
305
|
self._current_swing_mode = setting["swing"]
|
|
269
306
|
|
|
307
|
+
if "verticalSwing" in setting:
|
|
308
|
+
self._current_vertical_swing_mode = setting["verticalSwing"]
|
|
309
|
+
|
|
310
|
+
if "horizontalSwing" in setting:
|
|
311
|
+
self._current_horizontal_swing_mode = setting["horizontalSwing"]
|
|
312
|
+
|
|
270
313
|
self._power = setting["power"]
|
|
271
314
|
if self._power == "ON":
|
|
272
315
|
self._current_hvac_action = CONST_HVAC_IDLE
|
|
@@ -288,6 +331,11 @@ class TadoZone:
|
|
|
288
331
|
CONST_FAN_AUTO if self._power == "ON" else CONST_FAN_OFF
|
|
289
332
|
)
|
|
290
333
|
|
|
334
|
+
if "fanLevel" in setting:
|
|
335
|
+
self._current_fan_level = setting.get(
|
|
336
|
+
"fanLevel", CONST_FAN_SPEED_AUTO if self._power == "ON" else CONST_FAN_SPEED_OFF
|
|
337
|
+
)
|
|
338
|
+
|
|
291
339
|
self._preparation = "preparation" in data and data["preparation"] is not None
|
|
292
340
|
self._open_window = "openWindow" in data and data["openWindow"] is not None
|
|
293
341
|
self._open_window_detected = (
|
|
@@ -342,5 +390,21 @@ class TadoZone:
|
|
|
342
390
|
if "terminationCondition" in data:
|
|
343
391
|
self._default_overlay_termination_type = data["terminationCondition"].get('type',None)
|
|
344
392
|
self._default_overlay_termination_duration = data["terminationCondition"].get('durationInSeconds',None)
|
|
393
|
+
|
|
394
|
+
if self.isX:
|
|
395
|
+
if self._power == "ON":
|
|
396
|
+
self._current_hvac_action = CONST_HVAC_IDLE if data["heatingPower"]["percentage"] == 0 else CONST_HVAC_HEAT
|
|
397
|
+
self._heating_power_percentage = data["heatingPower"]["percentage"]
|
|
398
|
+
else:
|
|
399
|
+
self._heating_power_percentage = 0
|
|
400
|
+
self._current_hvac_action = CONST_HVAC_OFF
|
|
345
401
|
|
|
346
|
-
|
|
402
|
+
if "manualControlTermination" in data:
|
|
403
|
+
if data["manualControlTermination"]:
|
|
404
|
+
self._current_hvac_mode = CONST_MODE_HEAT if self._power == "ON" else CONST_MODE_OFF
|
|
405
|
+
self._overlay_termination_type = data["manualControlTermination"]["type"]
|
|
406
|
+
self._overlay_termination_timestamp = data["manualControlTermination"]["projectedExpiry"]
|
|
407
|
+
else:
|
|
408
|
+
self._overlay_termination_type = None
|
|
409
|
+
self._overlay_termination_timestamp = None
|
|
410
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-tado
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.7
|
|
4
4
|
Summary: PyTado from chrism0dwk, modfied by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi, Coffee2CodeNL, chiefdragon
|
|
5
5
|
Home-page: https://github.com/wmalgadey/PyTado
|
|
6
6
|
Author: chrism0dwk, w.malgadey
|
|
@@ -19,7 +19,7 @@ readme = open(here('README.md')).read()
|
|
|
19
19
|
requirements = [x.strip() for x in open(here('requirements.txt')).readlines()]
|
|
20
20
|
|
|
21
21
|
setup(name='python-tado',
|
|
22
|
-
version='0.17.
|
|
22
|
+
version='0.17.7',
|
|
23
23
|
description='PyTado from chrism0dwk, modfied by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi, Coffee2CodeNL, chiefdragon',
|
|
24
24
|
long_description=readme,
|
|
25
25
|
keywords='tado',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|