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.
- {python_roborock-2.9.7 → python_roborock-2.10.0}/PKG-INFO +1 -1
- {python_roborock-2.9.7 → python_roborock-2.10.0}/pyproject.toml +1 -1
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/api.py +4 -1
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/web_api.py +91 -5
- {python_roborock-2.9.7 → python_roborock-2.10.0}/LICENSE +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/README.md +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/__init__.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/cli.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/cloud_api.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/code_mappings.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/const.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/containers.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/exceptions.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/local_api.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/protocol.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/py.typed +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/roborock_future.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/util.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
|
@@ -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
|
|
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}",
|
|
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()
|
|
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
|
|
File without changes
|
{python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_local_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.9.7 → python_roborock-2.10.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|