python-roborock 2.44.1__tar.gz → 2.46.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.44.1 → python_roborock-2.46.0}/PKG-INFO +1 -1
- {python_roborock-2.44.1 → python_roborock-2.46.0}/pyproject.toml +1 -1
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/api.py +1 -7
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/cloud_api.py +92 -13
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/exceptions.py +1 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_local_client_v1.py +7 -1
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +1 -1
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +1 -1
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/web_api.py +108 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/LICENSE +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/README.md +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/__init__.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/b01_containers.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/callbacks.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/clean_modes.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/cli.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/code_mappings.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/const.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/containers.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/device_features.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/README.md +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/cache.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/channel.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/device.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/b01/props.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/clean_summary.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/dnd.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/dyad.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/sound_volume.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/status.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/trait.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/traits/zeo.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/devices/v1_rpc_channel.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/protocol.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/py.typed +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/roborock_future.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/util.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
|
@@ -23,7 +23,7 @@ from .roborock_message import (
|
|
|
23
23
|
from .util import get_next_int
|
|
24
24
|
|
|
25
25
|
_LOGGER = logging.getLogger(__name__)
|
|
26
|
-
KEEPALIVE =
|
|
26
|
+
KEEPALIVE = 70
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class RoborockClient(ABC):
|
|
@@ -78,12 +78,6 @@ class RoborockClient(ABC):
|
|
|
78
78
|
return False
|
|
79
79
|
return True
|
|
80
80
|
|
|
81
|
-
async def validate_connection(self) -> None:
|
|
82
|
-
if not self.should_keepalive():
|
|
83
|
-
self._logger.info("Resetting Roborock connection due to keepalive timeout")
|
|
84
|
-
await self.async_disconnect()
|
|
85
|
-
await self.async_connect()
|
|
86
|
-
|
|
87
81
|
async def _wait_response(self, request_id: int, queue: RoborockFuture) -> Any:
|
|
88
82
|
try:
|
|
89
83
|
response = await queue.async_get(self.queue_timeout)
|
|
@@ -8,6 +8,10 @@ from asyncio import Lock
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
import paho.mqtt.client as mqtt
|
|
11
|
+
from paho.mqtt.enums import MQTTErrorCode
|
|
12
|
+
|
|
13
|
+
# Mypy is not seeing this for some reason. It wants me to use the depreciated ReasonCodes
|
|
14
|
+
from paho.mqtt.reasoncodes import ReasonCode # type: ignore
|
|
11
15
|
|
|
12
16
|
from .api import KEEPALIVE, RoborockClient
|
|
13
17
|
from .containers import DeviceData, UserData
|
|
@@ -67,7 +71,8 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
67
71
|
self._mqtt_client = _Mqtt()
|
|
68
72
|
self._mqtt_client.on_connect = self._mqtt_on_connect
|
|
69
73
|
self._mqtt_client.on_message = self._mqtt_on_message
|
|
70
|
-
|
|
74
|
+
# Due to the incorrect ReasonCode, it is confused by typing
|
|
75
|
+
self._mqtt_client.on_disconnect = self._mqtt_on_disconnect # type: ignore
|
|
71
76
|
if mqtt_params.tls:
|
|
72
77
|
self._mqtt_client.tls_set()
|
|
73
78
|
|
|
@@ -76,12 +81,20 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
76
81
|
self._mutex = Lock()
|
|
77
82
|
self._decoder: Decoder = create_mqtt_decoder(device_info.device.local_key)
|
|
78
83
|
self._encoder: Encoder = create_mqtt_encoder(device_info.device.local_key)
|
|
84
|
+
self.received_message_since_last_disconnect = False
|
|
85
|
+
self._topic = f"rr/m/o/{self._mqtt_user}/{self._hashed_user}/{self.device_info.device.duid}"
|
|
79
86
|
|
|
80
|
-
def _mqtt_on_connect(
|
|
81
|
-
|
|
87
|
+
def _mqtt_on_connect(
|
|
88
|
+
self,
|
|
89
|
+
client: mqtt.Client,
|
|
90
|
+
userdata: object,
|
|
91
|
+
flags: dict[str, int],
|
|
92
|
+
rc: ReasonCode,
|
|
93
|
+
properties: mqtt.Properties | None = None,
|
|
94
|
+
):
|
|
82
95
|
connection_queue = self._waiting_queue.get(CONNECT_REQUEST_ID)
|
|
83
|
-
if rc
|
|
84
|
-
message = f"Failed to connect ({
|
|
96
|
+
if rc.is_failure:
|
|
97
|
+
message = f"Failed to connect ({rc})"
|
|
85
98
|
self._logger.error(message)
|
|
86
99
|
if connection_queue:
|
|
87
100
|
connection_queue.set_exception(VacuumError(message))
|
|
@@ -89,19 +102,19 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
89
102
|
self._logger.debug("Failed to notify connect future, not in queue")
|
|
90
103
|
return
|
|
91
104
|
self._logger.info(f"Connected to mqtt {self._mqtt_host}:{self._mqtt_port}")
|
|
92
|
-
|
|
93
|
-
(result, mid) = self._mqtt_client.subscribe(topic)
|
|
105
|
+
(result, mid) = self._mqtt_client.subscribe(self._topic)
|
|
94
106
|
if result != 0:
|
|
95
|
-
message = f"Failed to subscribe ({
|
|
107
|
+
message = f"Failed to subscribe ({str(rc)})"
|
|
96
108
|
self._logger.error(message)
|
|
97
109
|
if connection_queue:
|
|
98
110
|
connection_queue.set_exception(VacuumError(message))
|
|
99
111
|
return
|
|
100
|
-
self._logger.info(f"Subscribed to topic {
|
|
112
|
+
self._logger.info(f"Subscribed to topic {self._topic}")
|
|
101
113
|
if connection_queue:
|
|
102
114
|
connection_queue.set_result(True)
|
|
103
115
|
|
|
104
116
|
def _mqtt_on_message(self, *args, **kwargs):
|
|
117
|
+
self.received_message_since_last_disconnect = True
|
|
105
118
|
client, __, msg = args
|
|
106
119
|
try:
|
|
107
120
|
messages = self._decoder(msg.payload)
|
|
@@ -109,10 +122,16 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
109
122
|
except Exception as ex:
|
|
110
123
|
self._logger.exception(ex)
|
|
111
124
|
|
|
112
|
-
def _mqtt_on_disconnect(
|
|
113
|
-
|
|
125
|
+
def _mqtt_on_disconnect(
|
|
126
|
+
self,
|
|
127
|
+
client: mqtt.Client,
|
|
128
|
+
data: object,
|
|
129
|
+
flags: dict[str, int],
|
|
130
|
+
rc: ReasonCode | None,
|
|
131
|
+
properties: mqtt.Properties | None = None,
|
|
132
|
+
):
|
|
114
133
|
try:
|
|
115
|
-
exc = RoborockException(
|
|
134
|
+
exc = RoborockException(str(rc)) if rc is not None and rc.is_failure else None
|
|
116
135
|
super().on_connection_lost(exc)
|
|
117
136
|
connection_queue = self._waiting_queue.get(DISCONNECT_REQUEST_ID)
|
|
118
137
|
if connection_queue:
|
|
@@ -138,7 +157,7 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
138
157
|
|
|
139
158
|
if rc != mqtt.MQTT_ERR_SUCCESS:
|
|
140
159
|
disconnected_future.cancel()
|
|
141
|
-
raise RoborockException(f"Failed to disconnect ({
|
|
160
|
+
raise RoborockException(f"Failed to disconnect ({str(rc)})")
|
|
142
161
|
|
|
143
162
|
return disconnected_future
|
|
144
163
|
|
|
@@ -178,3 +197,63 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
178
197
|
)
|
|
179
198
|
if info.rc != mqtt.MQTT_ERR_SUCCESS:
|
|
180
199
|
raise RoborockException(f"Failed to publish ({mqtt.error_string(info.rc)})")
|
|
200
|
+
|
|
201
|
+
async def _unsubscribe(self) -> MQTTErrorCode:
|
|
202
|
+
"""Unsubscribe from the topic."""
|
|
203
|
+
loop = asyncio.get_running_loop()
|
|
204
|
+
(result, mid) = await loop.run_in_executor(None, self._mqtt_client.unsubscribe, self._topic)
|
|
205
|
+
|
|
206
|
+
if result != 0:
|
|
207
|
+
message = f"Failed to unsubscribe ({mqtt.error_string(result)})"
|
|
208
|
+
self._logger.error(message)
|
|
209
|
+
else:
|
|
210
|
+
self._logger.info(f"Unsubscribed from topic {self._topic}")
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
async def _subscribe(self) -> MQTTErrorCode:
|
|
214
|
+
"""Subscribe to the topic."""
|
|
215
|
+
loop = asyncio.get_running_loop()
|
|
216
|
+
(result, mid) = await loop.run_in_executor(None, self._mqtt_client.subscribe, self._topic)
|
|
217
|
+
|
|
218
|
+
if result != 0:
|
|
219
|
+
message = f"Failed to subscribe ({mqtt.error_string(result)})"
|
|
220
|
+
self._logger.error(message)
|
|
221
|
+
else:
|
|
222
|
+
self._logger.info(f"Subscribed to topic {self._topic}")
|
|
223
|
+
return result
|
|
224
|
+
|
|
225
|
+
async def _reconnect(self) -> None:
|
|
226
|
+
"""Reconnect to the MQTT broker."""
|
|
227
|
+
await self.async_disconnect()
|
|
228
|
+
await self.async_connect()
|
|
229
|
+
|
|
230
|
+
async def _validate_connection(self) -> None:
|
|
231
|
+
"""Override the default validate connection to try to re-subscribe rather than disconnect.
|
|
232
|
+
When something seems to be wrong with our connection, we should follow the following steps:
|
|
233
|
+
1. Try to unsubscribe and resubscribe from the topic.
|
|
234
|
+
2. If we don't end up getting a message, we should completely disconnect and reconnect to the MQTT broker.
|
|
235
|
+
3. We will continue to try to disconnect and reconnect until we get a message.
|
|
236
|
+
4. If we get a message, the next time connection is lost, We will go back to step 1.
|
|
237
|
+
"""
|
|
238
|
+
# If we should no longer keep the current connection alive...
|
|
239
|
+
if not self.should_keepalive():
|
|
240
|
+
self._logger.info("Resetting Roborock connection due to keepalive timeout")
|
|
241
|
+
if not self.received_message_since_last_disconnect:
|
|
242
|
+
# If we have already tried to unsub and resub, and we are still in this state,
|
|
243
|
+
# we should try to reconnect.
|
|
244
|
+
return await self._reconnect()
|
|
245
|
+
try:
|
|
246
|
+
# Mark that we have tried to unsubscribe and resubscribe
|
|
247
|
+
self.received_message_since_last_disconnect = False
|
|
248
|
+
if await self._unsubscribe() != 0:
|
|
249
|
+
# If we fail to unsubscribe, reconnect to the broker
|
|
250
|
+
return await self._reconnect()
|
|
251
|
+
if await self._subscribe() != 0:
|
|
252
|
+
# If we fail to subscribe, reconnected to the broker.
|
|
253
|
+
return await self._reconnect()
|
|
254
|
+
|
|
255
|
+
except Exception: # noqa
|
|
256
|
+
# If we get any errors at all, we should just reconnect.
|
|
257
|
+
return await self._reconnect()
|
|
258
|
+
# Call connect to make sure everything is still in a good state.
|
|
259
|
+
await self.async_connect()
|
|
@@ -138,6 +138,12 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
|
|
|
138
138
|
response_protocol=RoborockMessageProtocol.PING_RESPONSE,
|
|
139
139
|
)
|
|
140
140
|
|
|
141
|
+
async def _validate_connection(self) -> None:
|
|
142
|
+
if not self.should_keepalive():
|
|
143
|
+
self._logger.info("Resetting Roborock connection due to keepalive timeout")
|
|
144
|
+
await self.async_disconnect()
|
|
145
|
+
await self.async_connect()
|
|
146
|
+
|
|
141
147
|
def _send_msg_raw(self, data: bytes):
|
|
142
148
|
try:
|
|
143
149
|
if not self.transport:
|
|
@@ -172,7 +178,7 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
|
|
|
172
178
|
method: str | None = None,
|
|
173
179
|
params: list | dict | int | None = None,
|
|
174
180
|
) -> RoborockMessage:
|
|
175
|
-
await self.
|
|
181
|
+
await self._validate_connection()
|
|
176
182
|
msg = self._encoder(roborock_message)
|
|
177
183
|
if method:
|
|
178
184
|
self._logger.debug(f"id={request_id} Requesting method {method} with {params}")
|
{python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
@@ -51,7 +51,7 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
|
|
|
51
51
|
)
|
|
52
52
|
self._logger.debug("Building message id %s for method %s", request_message.request_id, method)
|
|
53
53
|
|
|
54
|
-
await self.
|
|
54
|
+
await self._validate_connection()
|
|
55
55
|
request_id = request_message.request_id
|
|
56
56
|
response_protocol = (
|
|
57
57
|
RoborockMessageProtocol.MAP_RESPONSE if method in COMMANDS_SECURED else RoborockMessageProtocol.RPC_RESPONSE
|
|
@@ -40,7 +40,7 @@ class RoborockMqttClientA01(RoborockMqttClient, RoborockClientA01):
|
|
|
40
40
|
self._logger = RoborockLoggerAdapter(device_info.device.name, _LOGGER)
|
|
41
41
|
|
|
42
42
|
async def _send_message(self, roborock_message: RoborockMessage):
|
|
43
|
-
await self.
|
|
43
|
+
await self._validate_connection()
|
|
44
44
|
response_protocol = RoborockMessageProtocol.RPC_RESPONSE
|
|
45
45
|
|
|
46
46
|
m = self._encoder(roborock_message)
|
|
@@ -6,6 +6,7 @@ import hmac
|
|
|
6
6
|
import logging
|
|
7
7
|
import math
|
|
8
8
|
import secrets
|
|
9
|
+
import string
|
|
9
10
|
import time
|
|
10
11
|
|
|
11
12
|
import aiohttp
|
|
@@ -190,6 +191,113 @@ class RoborockApiClient:
|
|
|
190
191
|
else:
|
|
191
192
|
raise RoborockException(f"{code_response.get('msg')} - response code: {code_response.get('code')}")
|
|
192
193
|
|
|
194
|
+
async def request_code_v4(self) -> None:
|
|
195
|
+
"""Request a code using the v4 endpoint."""
|
|
196
|
+
try:
|
|
197
|
+
self._login_limiter.try_acquire("login")
|
|
198
|
+
except BucketFullException as ex:
|
|
199
|
+
_LOGGER.info(ex.meta_info)
|
|
200
|
+
raise RoborockRateLimit("Reached maximum requests for login. Please try again later.") from ex
|
|
201
|
+
base_url = await self._get_base_url()
|
|
202
|
+
header_clientid = self._get_header_client_id()
|
|
203
|
+
code_request = PreparedRequest(
|
|
204
|
+
base_url,
|
|
205
|
+
self.session,
|
|
206
|
+
{
|
|
207
|
+
"header_clientid": header_clientid,
|
|
208
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
209
|
+
"header_clientlang": "en",
|
|
210
|
+
},
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
code_response = await code_request.request(
|
|
214
|
+
"post",
|
|
215
|
+
"/api/v4/email/code/send",
|
|
216
|
+
params={"email": self._username, "type": "login", "platform": ""},
|
|
217
|
+
)
|
|
218
|
+
if code_response is None:
|
|
219
|
+
raise RoborockException("Failed to get a response from send email code")
|
|
220
|
+
response_code = code_response.get("code")
|
|
221
|
+
if response_code != 200:
|
|
222
|
+
_LOGGER.info("Request code failed for %s with the following context: %s", self._username, code_response)
|
|
223
|
+
if response_code == 2008:
|
|
224
|
+
raise RoborockAccountDoesNotExist("Account does not exist - check your login and try again.")
|
|
225
|
+
elif response_code == 9002:
|
|
226
|
+
raise RoborockTooFrequentCodeRequests("You have attempted to request too many codes. Try again later")
|
|
227
|
+
else:
|
|
228
|
+
raise RoborockException(f"{code_response.get('msg')} - response code: {code_response.get('code')}")
|
|
229
|
+
|
|
230
|
+
async def sign_key_v3(self, s: str) -> str:
|
|
231
|
+
"""Sign a randomly generated string."""
|
|
232
|
+
base_url = await self._get_base_url()
|
|
233
|
+
header_clientid = self._get_header_client_id()
|
|
234
|
+
code_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
235
|
+
|
|
236
|
+
code_response = await code_request.request(
|
|
237
|
+
"post",
|
|
238
|
+
"/api/v3/key/sign",
|
|
239
|
+
params={"s": s},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if not code_response or "data" not in code_response or "k" not in code_response["data"]:
|
|
243
|
+
raise RoborockException("Failed to get a response from sign key")
|
|
244
|
+
response_code = code_response.get("code")
|
|
245
|
+
|
|
246
|
+
if response_code != 200:
|
|
247
|
+
_LOGGER.info("Request code failed for %s with the following context: %s", self._username, code_response)
|
|
248
|
+
raise RoborockException(f"{code_response.get('msg')} - response code: {code_response.get('code')}")
|
|
249
|
+
|
|
250
|
+
return code_response["data"]["k"]
|
|
251
|
+
|
|
252
|
+
async def code_login_v4(self, code: int | str, country: str, country_code: int) -> UserData:
|
|
253
|
+
"""
|
|
254
|
+
Login via code authentication.
|
|
255
|
+
:param code: The code from the email.
|
|
256
|
+
:param country: The two-character representation of the country, i.e. "US"
|
|
257
|
+
:param country_code: the country phone number code i.e. 1 for US.
|
|
258
|
+
"""
|
|
259
|
+
base_url = await self._get_base_url()
|
|
260
|
+
header_clientid = self._get_header_client_id()
|
|
261
|
+
x_mercy_ks = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
262
|
+
x_mercy_k = await self.sign_key_v3(x_mercy_ks)
|
|
263
|
+
login_request = PreparedRequest(
|
|
264
|
+
base_url,
|
|
265
|
+
self.session,
|
|
266
|
+
{"header_clientid": header_clientid, "x-mercy-ks": x_mercy_ks, "x-mercy-k": x_mercy_k},
|
|
267
|
+
)
|
|
268
|
+
login_response = await login_request.request(
|
|
269
|
+
"post",
|
|
270
|
+
"/api/v4/auth/email/login/code",
|
|
271
|
+
params={
|
|
272
|
+
"country": country,
|
|
273
|
+
"countryCode": country_code,
|
|
274
|
+
"email": self._username,
|
|
275
|
+
"code": code,
|
|
276
|
+
# Major and minor version are the user agreement version, we will need to see if this needs to be
|
|
277
|
+
# dynamic https://usiot.roborock.com/api/v3/app/agreement/latest?country=US
|
|
278
|
+
"majorVersion": 14,
|
|
279
|
+
"minorVersion": 0,
|
|
280
|
+
},
|
|
281
|
+
)
|
|
282
|
+
if login_response is None:
|
|
283
|
+
raise RoborockException("Login request response is None")
|
|
284
|
+
response_code = login_response.get("code")
|
|
285
|
+
if response_code != 200:
|
|
286
|
+
_LOGGER.info("Login failed for %s with the following context: %s", self._username, login_response)
|
|
287
|
+
if response_code == 2018:
|
|
288
|
+
raise RoborockInvalidCode("Invalid code - check your code and try again.")
|
|
289
|
+
if response_code == 3009:
|
|
290
|
+
raise RoborockNoUserAgreement("You must accept the user agreement in the Roborock app to continue.")
|
|
291
|
+
if response_code == 3006:
|
|
292
|
+
raise RoborockInvalidUserAgreement(
|
|
293
|
+
"User agreement must be accepted again - or you are attempting to use the Mi Home app account."
|
|
294
|
+
)
|
|
295
|
+
raise RoborockException(f"{login_response.get('msg')} - response code: {response_code}")
|
|
296
|
+
user_data = login_response.get("data")
|
|
297
|
+
if not isinstance(user_data, dict):
|
|
298
|
+
raise RoborockException("Got unexpected data type for user_data")
|
|
299
|
+
return UserData.from_dict(user_data)
|
|
300
|
+
|
|
193
301
|
async def pass_login(self, password: str) -> UserData:
|
|
194
302
|
try:
|
|
195
303
|
self._login_limiter.try_acquire("login")
|
|
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
|
|
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
|
|
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.44.1 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.44.1 → python_roborock-2.46.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|