python-roborock 2.9.7__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.7 → python_roborock-2.10.0}/PKG-INFO +1 -1
  2. {python_roborock-2.9.7 → python_roborock-2.10.0}/pyproject.toml +1 -1
  3. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/api.py +4 -1
  4. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/web_api.py +91 -5
  5. {python_roborock-2.9.7 → python_roborock-2.10.0}/LICENSE +0 -0
  6. {python_roborock-2.9.7 → python_roborock-2.10.0}/README.md +0 -0
  7. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/__init__.py +0 -0
  8. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/cli.py +0 -0
  9. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/cloud_api.py +0 -0
  10. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/code_mappings.py +0 -0
  11. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/command_cache.py +0 -0
  12. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/const.py +0 -0
  13. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/containers.py +0 -0
  14. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/exceptions.py +0 -0
  15. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/local_api.py +0 -0
  16. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/protocol.py +0 -0
  17. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/py.typed +0 -0
  18. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/roborock_future.py +0 -0
  19. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/roborock_message.py +0 -0
  20. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/roborock_typing.py +0 -0
  21. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/util.py +0 -0
  22. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/__init__.py +0 -0
  23. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  24. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
  25. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  26. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_a01_apis/__init__.py +0 -0
  27. {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  28. {python_roborock-2.9.7 → 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.7
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.7"
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"
@@ -20,6 +20,7 @@ from .exceptions import (
20
20
  from .roborock_future import RoborockFuture
21
21
  from .roborock_message import (
22
22
  RoborockMessage,
23
+ RoborockMessageProtocol,
23
24
  )
24
25
  from .util import get_next_int
25
26
 
@@ -101,7 +102,9 @@ class RoborockClient(ABC):
101
102
 
102
103
  def _async_response(self, request_id: int, protocol_id: int = 0) -> Any:
103
104
  queue = RoborockFuture(protocol_id)
104
- if request_id in self._waiting_queue:
105
+ if request_id in self._waiting_queue and not (
106
+ request_id == 2 and protocol_id == RoborockMessageProtocol.PING_REQUEST
107
+ ):
105
108
  new_id = get_next_int(10000, 32767)
106
109
  self._logger.warning(
107
110
  "Attempting to create a future with an existing id %s (%s)... New id is %s. "
@@ -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()