python-tado 0.18.6__tar.gz → 0.18.8__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.18.6/python_tado.egg-info → python_tado-0.18.8}/PKG-INFO +54 -2
  2. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/__main__.py +15 -41
  3. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/const.py +4 -11
  4. python_tado-0.18.8/PyTado/http.py +584 -0
  5. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/interface/api/hops_tado.py +13 -36
  6. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/interface/api/my_tado.py +16 -45
  7. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/interface/interface.py +85 -24
  8. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/zone/my_zone.py +30 -91
  9. {python_tado-0.18.6 → python_tado-0.18.8}/README.md +53 -1
  10. {python_tado-0.18.6 → python_tado-0.18.8}/pyproject.toml +3 -3
  11. {python_tado-0.18.6 → python_tado-0.18.8/python_tado.egg-info}/PKG-INFO +54 -2
  12. {python_tado-0.18.6 → python_tado-0.18.8}/tests/test_hops_zone.py +8 -3
  13. python_tado-0.18.8/tests/test_http.py +285 -0
  14. {python_tado-0.18.6 → python_tado-0.18.8}/tests/test_my_tado.py +7 -4
  15. {python_tado-0.18.6 → python_tado-0.18.8}/tests/test_my_zone.py +8 -4
  16. python_tado-0.18.8/tests/test_tado_interface.py +86 -0
  17. python_tado-0.18.6/PyTado/http.py +0 -351
  18. python_tado-0.18.6/tests/test_http.py +0 -185
  19. python_tado-0.18.6/tests/test_tado_interface.py +0 -84
  20. {python_tado-0.18.6 → python_tado-0.18.8}/AUTHORS +0 -0
  21. {python_tado-0.18.6 → python_tado-0.18.8}/LICENSE +0 -0
  22. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/__init__.py +0 -0
  23. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/exceptions.py +0 -0
  24. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/interface/__init__.py +0 -0
  25. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/interface/api/__init__.py +0 -0
  26. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/logger.py +0 -0
  27. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/zone/__init__.py +0 -0
  28. {python_tado-0.18.6 → python_tado-0.18.8}/PyTado/zone/hops_zone.py +0 -0
  29. {python_tado-0.18.6 → python_tado-0.18.8}/python_tado.egg-info/SOURCES.txt +0 -0
  30. {python_tado-0.18.6 → python_tado-0.18.8}/python_tado.egg-info/dependency_links.txt +0 -0
  31. {python_tado-0.18.6 → python_tado-0.18.8}/python_tado.egg-info/entry_points.txt +0 -0
  32. {python_tado-0.18.6 → python_tado-0.18.8}/python_tado.egg-info/not-zip-safe +0 -0
  33. {python_tado-0.18.6 → python_tado-0.18.8}/python_tado.egg-info/requires.txt +0 -0
  34. {python_tado-0.18.6 → python_tado-0.18.8}/python_tado.egg-info/top_level.txt +0 -0
  35. {python_tado-0.18.6 → python_tado-0.18.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: python-tado
3
- Version: 0.18.6
3
+ Version: 0.18.8
4
4
  Summary: PyTado from chrism0dwk, modfied by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi, Coffee2CodeNL, chiefdragon, FilBr, nikilase, albertomontesg, Moritz-Schmidt, palazzem
5
5
  Author-email: Chris Jewell <chrism0dwk@gmail.com>, "w.malgadey" <w.malgadey@gmail.com>, FilBr <filippo.barba@protonmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -723,7 +723,7 @@ heating setups.
723
723
 
724
724
  ---
725
725
 
726
- Original author: Chris Jewell <chrism0dwk@gmail.com>
726
+ Original author: Chris Jewell <chrism0dwk@gmail.com>
727
727
 
728
728
  Licence: GPL v3
729
729
 
@@ -744,6 +744,58 @@ cause discomfort and inconvenience to others.
744
744
  >>> climate = t.get_climate(zone=1)
745
745
 
746
746
  ## Usage
747
+
748
+ 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.
749
+
750
+ PyTado handles this as following:
751
+
752
+ 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`.
753
+
754
+ 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.
755
+
756
+ 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.
757
+ The `device_verification_url()` will be reset to `None` and the `device_activation_status()` will return `COMPLETED`.
758
+
759
+ ### Screenshots of the device flow
760
+
761
+ ![Tado device flow: invoking](/screenshots/tado-device-flow-0.png)
762
+ ![Tado device flow: browser](/screenshots/tado-device-flow-1.png)
763
+ ![Tado device flow: complete](/screenshots/tado-device-flow-2.png)
764
+
765
+ ### How to not authenticate the device again
766
+
767
+ It is possible to save the refresh token and reuse to skip the next login.
768
+
769
+ 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.
770
+
771
+ 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.
772
+
773
+ ```python
774
+ import webbrowser # only needed for direct web browser access
775
+
776
+ from PyTado.interface.interface import Tado
777
+
778
+ tado = Tado(token_file_path="/var/tado/refresh_token")
779
+
780
+ status = tado.device_activation_status()
781
+
782
+ if status == "PENDING":
783
+ url = tado.device_verification_url()
784
+
785
+ webbrowser.open_new_tab(url)
786
+
787
+ tado.device_activation()
788
+
789
+ status = tado.device_activation_status()
790
+
791
+ if status == "COMPLETED":
792
+ print("Login successful")
793
+ else:
794
+ print(f"Login status is {status}")
795
+ ```
796
+
797
+ ## Example code
798
+
747
799
  ```python
748
800
  """Example client for PyTado"""
749
801
 
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
 
4
3
  """Module for querying and controlling Tado smart thermostats."""
5
4
 
@@ -10,31 +9,32 @@ import sys
10
9
  from PyTado.interface import Tado
11
10
 
12
11
 
13
- def log_in(email, password):
14
- t = Tado(email, password)
12
+ def log_in():
13
+ t = Tado()
14
+ t.device_activation()
15
15
  return t
16
16
 
17
17
 
18
- def get_me(args):
19
- t = Tado(args.email, args.password)
18
+ def get_me():
19
+ t = log_in()
20
20
  me = t.get_me()
21
21
  print(me)
22
22
 
23
23
 
24
24
  def get_state(args):
25
- t = Tado(args.email, args.password)
25
+ t = log_in()
26
26
  zone = t.get_state(int(args.zone))
27
27
  print(zone)
28
28
 
29
29
 
30
30
  def get_states(args):
31
- t = Tado(args.email, args.password)
31
+ t = log_in()
32
32
  zone = t.get_zone_states()
33
33
  print(zone)
34
34
 
35
35
 
36
36
  def get_capabilities(args):
37
- t = Tado(args.email, args.password)
37
+ t = log_in()
38
38
  capabilities = t.get_capabilities(int(args.zone))
39
39
  print(capabilities)
40
40
 
@@ -46,22 +46,8 @@ def main():
46
46
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
47
47
  )
48
48
 
49
- required_flags = parser.add_argument_group("required arguments")
50
-
51
- # Required flags go here.
52
- required_flags.add_argument(
53
- "--email",
54
- required=True,
55
- help=("Tado username in the form of an email address."),
56
- )
57
- required_flags.add_argument(
58
- "--password", required=True, help="Tado password."
59
- )
60
-
61
49
  # Flags with default values go here.
62
- log_levels = dict(
63
- (logging.getLevelName(level), level) for level in [10, 20, 30, 40, 50]
64
- )
50
+ log_levels = {logging.getLevelName(level): level for level in [10, 20, 30, 40, 50]}
65
51
  parser.add_argument(
66
52
  "--loglevel",
67
53
  default="INFO",
@@ -71,30 +57,18 @@ def main():
71
57
 
72
58
  subparsers = parser.add_subparsers()
73
59
 
74
- show_config_parser = subparsers.add_parser(
75
- "get_me", help="Get home information."
76
- )
60
+ show_config_parser = subparsers.add_parser("get_me", help="Get home information.")
77
61
  show_config_parser.set_defaults(func=get_me)
78
62
 
79
- start_activity_parser = subparsers.add_parser(
80
- "get_state", help="Get state of zone."
81
- )
82
- start_activity_parser.add_argument(
83
- "--zone", help="Zone to get the state of."
84
- )
63
+ start_activity_parser = subparsers.add_parser("get_state", help="Get state of zone.")
64
+ start_activity_parser.add_argument("--zone", help="Zone to get the state of.")
85
65
  start_activity_parser.set_defaults(func=get_state)
86
66
 
87
- start_activity_parser = subparsers.add_parser(
88
- "get_states", help="Get states of all zones."
89
- )
67
+ start_activity_parser = subparsers.add_parser("get_states", help="Get states of all zones.")
90
68
  start_activity_parser.set_defaults(func=get_states)
91
69
 
92
- start_activity_parser = subparsers.add_parser(
93
- "get_capabilities", help="Get capabilities of zone."
94
- )
95
- start_activity_parser.add_argument(
96
- "--zone", help="Zone to get the capabilities of."
97
- )
70
+ start_activity_parser = subparsers.add_parser("get_capabilities", help="Get capabilities of zone.")
71
+ start_activity_parser.add_argument("--zone", help="Zone to get the capabilities of.")
98
72
  start_activity_parser.set_defaults(func=get_capabilities)
99
73
 
100
74
  args = parser.parse_args()
@@ -1,10 +1,7 @@
1
1
  """Constant values for the Tado component."""
2
2
 
3
- # Api credentials
4
- CLIENT_ID = "tado-web-app"
5
- CLIENT_SECRET = (
6
- "wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc"
7
- )
3
+ # API client ID
4
+ CLIENT_ID_DEVICE = "1bb50063-6b0c-4d11-bd99-387f4a91cc46" # nosec B105
8
5
 
9
6
  # Types
10
7
  TYPE_AIR_CONDITIONING = "AIR_CONDITIONING"
@@ -49,12 +46,8 @@ CONST_HORIZONTAL_SWING_MID_RIGHT = "MID_RIGHT"
49
46
  CONST_HORIZONTAL_SWING_RIGHT = "RIGHT"
50
47
 
51
48
  # When we change the temperature setting, we need an overlay mode
52
- CONST_OVERLAY_TADO_MODE = (
53
- "NEXT_TIME_BLOCK" # wait until tado changes the mode automatic
54
- )
55
- CONST_OVERLAY_MANUAL = (
56
- "MANUAL" # the user has changed the temperature or mode manually
57
- )
49
+ CONST_OVERLAY_TADO_MODE = "NEXT_TIME_BLOCK" # wait until tado changes the mode automatic
50
+ CONST_OVERLAY_MANUAL = "MANUAL" # the user has changed the temperature or mode manually
58
51
  CONST_OVERLAY_TIMER = "TIMER" # the temperature will be reset after a timespan
59
52
 
60
53
  # Heat always comes first since we get the