python-roborock 2.9.8__tar.gz → 2.10.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 (28) hide show
  1. {python_roborock-2.9.8 → python_roborock-2.10.0}/PKG-INFO +1 -1
  2. {python_roborock-2.9.8 → python_roborock-2.10.0}/pyproject.toml +1 -1
  3. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/web_api.py +91 -5
  4. {python_roborock-2.9.8 → python_roborock-2.10.0}/LICENSE +0 -0
  5. {python_roborock-2.9.8 → python_roborock-2.10.0}/README.md +0 -0
  6. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/__init__.py +0 -0
  7. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/api.py +0 -0
  8. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/cli.py +0 -0
  9. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/cloud_api.py +0 -0
  10. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/code_mappings.py +0 -0
  11. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/command_cache.py +0 -0
  12. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/const.py +0 -0
  13. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/containers.py +0 -0
  14. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/exceptions.py +0 -0
  15. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/local_api.py +0 -0
  16. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/protocol.py +0 -0
  17. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/py.typed +0 -0
  18. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/roborock_future.py +0 -0
  19. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/roborock_message.py +0 -0
  20. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/roborock_typing.py +0 -0
  21. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/util.py +0 -0
  22. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/version_1_apis/__init__.py +0 -0
  23. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  24. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
  25. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  26. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/version_a01_apis/__init__.py +0 -0
  27. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  28. {python_roborock-2.9.8 → python_roborock-2.10.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-roborock
3
- Version: 2.9.8
3
+ Version: 2.10.0
4
4
  Summary: A package to control Roborock vacuums.
5
5
  License: GPL-3.0-only
6
6
  Keywords: roborock,vacuum,homeassistant
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-roborock"
3
- version = "2.9.8"
3
+ version = "2.10.0"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
6
6
  license = "GPL-3.0-only"
@@ -9,7 +9,7 @@ import secrets
9
9
  import time
10
10
 
11
11
  import aiohttp
12
- from aiohttp import ContentTypeError
12
+ from aiohttp import ContentTypeError, FormData
13
13
 
14
14
  from roborock.containers import HomeData, HomeDataRoom, ProductResponse, RRiot, UserData
15
15
  from roborock.exceptions import (
@@ -67,9 +67,25 @@ class RoborockApiClient:
67
67
  md5.update(self._device_identifier.encode())
68
68
  return base64.b64encode(md5.digest()).decode()
69
69
 
70
- def _get_hawk_authentication(self, rriot: RRiot, url: str) -> str:
70
+ def _process_extra_hawk_values(self, values: dict | None) -> str:
71
+ if values is None:
72
+ return ""
73
+ else:
74
+ sorted_keys = sorted(values.keys())
75
+ result = []
76
+ for key in sorted_keys:
77
+ value = values.get(key)
78
+ result.append(f"{key}={value}")
79
+ return hashlib.md5("&".join(result).encode()).hexdigest()
80
+
81
+ def _get_hawk_authentication(
82
+ self, rriot: RRiot, url: str, formdata: dict | None = None, params: dict | None = None
83
+ ) -> str:
71
84
  timestamp = math.floor(time.time())
72
85
  nonce = secrets.token_urlsafe(6)
86
+ formdata_str = self._process_extra_hawk_values(formdata)
87
+ params_str = self._process_extra_hawk_values(params)
88
+
73
89
  prestr = ":".join(
74
90
  [
75
91
  rriot.u,
@@ -77,12 +93,82 @@ class RoborockApiClient:
77
93
  nonce,
78
94
  str(timestamp),
79
95
  hashlib.md5(url.encode()).hexdigest(),
80
- "",
81
- "",
96
+ params_str,
97
+ formdata_str,
82
98
  ]
83
99
  )
84
100
  mac = base64.b64encode(hmac.new(rriot.h.encode(), prestr.encode(), hashlib.sha256).digest()).decode()
85
- return f'Hawk id="{rriot.u}", s="{rriot.s}", ts="{timestamp}", nonce="{nonce}", mac="{mac}"'
101
+ return f'Hawk id="{rriot.u}",s="{rriot.s}",ts="{timestamp}",nonce="{nonce}",mac="{mac}"'
102
+
103
+ async def nc_prepare(self, user_data: UserData, timezone: str) -> dict:
104
+ """This gets a few critical parameters for adding a device to your account."""
105
+ if (
106
+ user_data.rriot is None
107
+ or user_data.rriot.r is None
108
+ or user_data.rriot.u is None
109
+ or user_data.rriot.r.a is None
110
+ ):
111
+ raise RoborockException("Your userdata is missing critical attributes.")
112
+ base_url = user_data.rriot.r.a
113
+ prepare_request = PreparedRequest(base_url)
114
+ hid = await self._get_home_id(user_data)
115
+
116
+ data = FormData()
117
+ data.add_field("hid", hid)
118
+ data.add_field("tzid", timezone)
119
+
120
+ prepare_response = await prepare_request.request(
121
+ "post",
122
+ "/nc/prepare",
123
+ headers={
124
+ "Authorization": self._get_hawk_authentication(
125
+ user_data.rriot, "/nc/prepare", {"hid": hid, "tzid": timezone}
126
+ ),
127
+ },
128
+ data=data,
129
+ )
130
+
131
+ if prepare_response is None:
132
+ raise RoborockException("prepare_response is None")
133
+ if not prepare_response.get("success"):
134
+ raise RoborockException(f"{prepare_response.get('msg')} - response code: {prepare_response.get('code')}")
135
+
136
+ return prepare_response["result"]
137
+
138
+ async def add_device(self, user_data: UserData, s: str, t: str) -> dict:
139
+ """This will add a new device to your account
140
+ it is recommended to only use this during a pairing cycle with a device.
141
+ Please see here: https://github.com/Python-roborock/Roborockmitmproxy/blob/main/handshake_protocol.md
142
+ """
143
+ if (
144
+ user_data.rriot is None
145
+ or user_data.rriot.r is None
146
+ or user_data.rriot.u is None
147
+ or user_data.rriot.r.a is None
148
+ ):
149
+ raise RoborockException("Your userdata is missing critical attributes.")
150
+ base_url = user_data.rriot.r.a
151
+ add_device_request = PreparedRequest(base_url)
152
+
153
+ add_device_response = await add_device_request.request(
154
+ "GET",
155
+ "/user/devices/newadd",
156
+ headers={
157
+ "Authorization": self._get_hawk_authentication(
158
+ user_data.rriot, "/user/devices/newadd", params={"s": s, "t": t}
159
+ ),
160
+ },
161
+ params={"s": s, "t": t},
162
+ )
163
+
164
+ if add_device_response is None:
165
+ raise RoborockException("add_device is None")
166
+ if not add_device_response.get("success"):
167
+ raise RoborockException(
168
+ f"{add_device_response.get('msg')} - response code: {add_device_response.get('code')}"
169
+ )
170
+
171
+ return add_device_response["result"]
86
172
 
87
173
  async def request_code(self) -> None:
88
174
  base_url = await self._get_base_url()