Skip to content

Commit 7759dc6

Browse files
authored
Merge branch 'main' into session-device-manager
2 parents 76c1001 + b19dbaa commit 7759dc6

File tree

15 files changed

+164
-74
lines changed

15 files changed

+164
-74
lines changed

poetry.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "python-roborock"
3-
version = "2.19.0"
3+
version = "2.20.0"
44
description = "A package to control Roborock vacuums."
55
authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
66
license = "GPL-3.0-only"

roborock/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def should_keepalive(self) -> bool:
8585

8686
async def validate_connection(self) -> None:
8787
if not self.should_keepalive():
88-
self._logger.info("Resetting Roborock connection due to kepalive timeout")
88+
self._logger.info("Resetting Roborock connection due to keepalive timeout")
8989
await self.async_disconnect()
9090
await self.async_connect()
9191

roborock/cloud_api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .api import KEEPALIVE, RoborockClient
1414
from .containers import DeviceData, UserData
1515
from .exceptions import RoborockException, VacuumError
16-
from .protocol import MessageParser, md5hex
16+
from .protocol import Decoder, Encoder, create_mqtt_decoder, create_mqtt_encoder, md5hex
1717
from .roborock_future import RoborockFuture
1818

1919
_LOGGER = logging.getLogger(__name__)
@@ -74,6 +74,8 @@ def __init__(self, user_data: UserData, device_info: DeviceData) -> None:
7474
self._mqtt_client.username_pw_set(self._hashed_user, self._hashed_password)
7575
self._waiting_queue: dict[int, RoborockFuture] = {}
7676
self._mutex = Lock()
77+
self._decoder: Decoder = create_mqtt_decoder(device_info.device.local_key)
78+
self._encoder: Encoder = create_mqtt_encoder(device_info.device.local_key)
7779

7880
def _mqtt_on_connect(self, *args, **kwargs):
7981
_, __, ___, rc, ____ = args
@@ -102,7 +104,7 @@ def _mqtt_on_connect(self, *args, **kwargs):
102104
def _mqtt_on_message(self, *args, **kwargs):
103105
client, __, msg = args
104106
try:
105-
messages, _ = MessageParser.parse(msg.payload, local_key=self.device_info.device.local_key)
107+
messages = self._decoder(msg.payload)
106108
super().on_message_received(messages)
107109
except Exception as ex:
108110
self._logger.exception(ex)

roborock/code_mappings.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,17 @@ class RoborockFanSpeedQRevoCurv(RoborockFanPowerCode):
267267
smart_mode = 110
268268

269269

270+
class RoborockFanSpeedQRevoMaxV(RoborockFanPowerCode):
271+
off = 105
272+
quiet = 101
273+
balanced = 102
274+
turbo = 103
275+
max = 104
276+
custom = 106
277+
max_plus = 108
278+
smart_mode = 110
279+
280+
270281
class RoborockFanSpeedP10(RoborockFanPowerCode):
271282
off = 105
272283
quiet = 101
@@ -275,6 +286,7 @@ class RoborockFanSpeedP10(RoborockFanPowerCode):
275286
max = 104
276287
custom = 106
277288
max_plus = 108
289+
smart_mode = 110
278290

279291

280292
class RoborockFanSpeedS8MaxVUltra(RoborockFanPowerCode):
@@ -316,6 +328,7 @@ class RoborockMopModeS8ProUltra(RoborockMopModeCode):
316328
deep_plus = 303
317329
fast = 304
318330
custom = 302
331+
smart_mode = 306
319332

320333

321334
class RoborockMopModeS8MaxVUltra(RoborockMopModeCode):
@@ -337,6 +350,15 @@ class RoborockMopModeQRevoMaster(RoborockMopModeCode):
337350
smart_mode = 306
338351

339352

353+
class RoborockMopModeQRevoMaxV(RoborockMopModeCode):
354+
standard = 300
355+
deep = 301
356+
custom = 302
357+
deep_plus = 303
358+
fast = 304
359+
smart_mode = 306
360+
361+
340362
class RoborockMopIntensityCode(RoborockEnum):
341363
"""Describes the mop intensity of the vacuum cleaner."""
342364

@@ -383,6 +405,16 @@ class RoborockMopIntensityQRevoCurv(RoborockMopIntensityCode):
383405
smart_mode = 209
384406

385407

408+
class RoborockMopIntensityQRevoMaxV(RoborockMopIntensityCode):
409+
off = 200
410+
low = 201
411+
medium = 202
412+
high = 203
413+
custom = 204
414+
custom_water_flow = 207
415+
smart_mode = 209
416+
417+
386418
class RoborockMopIntensityP10(RoborockMopIntensityCode):
387419
"""Describes the mop intensity of the vacuum cleaner."""
388420

@@ -392,6 +424,7 @@ class RoborockMopIntensityP10(RoborockMopIntensityCode):
392424
high = 203
393425
custom = 204
394426
custom_water_flow = 207
427+
smart_mode = 209
395428

396429

397430
class RoborockMopIntensityS8MaxVUltra(RoborockMopIntensityCode):

roborock/containers.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
RoborockFanSpeedQ7Max,
2424
RoborockFanSpeedQRevoCurv,
2525
RoborockFanSpeedQRevoMaster,
26+
RoborockFanSpeedQRevoMaxV,
2627
RoborockFanSpeedS6Pure,
2728
RoborockFanSpeedS7,
2829
RoborockFanSpeedS7MaxV,
@@ -34,13 +35,15 @@
3435
RoborockMopIntensityQ7Max,
3536
RoborockMopIntensityQRevoCurv,
3637
RoborockMopIntensityQRevoMaster,
38+
RoborockMopIntensityQRevoMaxV,
3739
RoborockMopIntensityS5Max,
3840
RoborockMopIntensityS6MaxV,
3941
RoborockMopIntensityS7,
4042
RoborockMopIntensityS8MaxVUltra,
4143
RoborockMopModeCode,
4244
RoborockMopModeQRevoCurv,
4345
RoborockMopModeQRevoMaster,
46+
RoborockMopModeQRevoMaxV,
4447
RoborockMopModeS7,
4548
RoborockMopModeS8MaxVUltra,
4649
RoborockMopModeS8ProUltra,
@@ -615,6 +618,13 @@ class QRevoCurvStatus(Status):
615618
mop_mode: RoborockMopModeQRevoCurv | None = None
616619

617620

621+
@dataclass
622+
class QRevoMaxVStatus(Status):
623+
fan_power: RoborockFanSpeedQRevoMaxV | None = None
624+
water_box_mode: RoborockMopIntensityQRevoMaxV | None = None
625+
mop_mode: RoborockMopModeQRevoMaxV | None = None
626+
627+
618628
@dataclass
619629
class S6MaxVStatus(Status):
620630
fan_power: RoborockFanSpeedS7MaxV | None = None
@@ -688,7 +698,7 @@ class S8MaxvUltraStatus(Status):
688698
# but i am currently unable to do my typical reverse engineering/ get any data from users on this,
689699
# so this will be here in the mean time.
690700
ROBOROCK_QREVO_S: P10Status,
691-
ROBOROCK_QREVO_MAXV: P10Status,
701+
ROBOROCK_QREVO_MAXV: QRevoMaxVStatus,
692702
ROBOROCK_QREVO_PRO: P10Status,
693703
ROBOROCK_S8_MAXV_ULTRA: S8MaxvUltraStatus,
694704
}

roborock/local_api.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from . import DeviceData
1313
from .api import RoborockClient
1414
from .exceptions import RoborockConnectionException, RoborockException
15-
from .protocol import MessageParser
15+
from .protocol import Decoder, Encoder, create_local_decoder, create_local_encoder
1616
from .roborock_message import RoborockMessage, RoborockMessageProtocol
1717

1818
_LOGGER = logging.getLogger(__name__)
@@ -44,20 +44,18 @@ def __init__(self, device_data: DeviceData):
4444
self.host = device_data.host
4545
self._batch_structs: list[RoborockMessage] = []
4646
self._executing = False
47-
self.remaining = b""
4847
self.transport: Transport | None = None
4948
self._mutex = Lock()
5049
self.keep_alive_task: TimerHandle | None = None
5150
RoborockClient.__init__(self, device_data)
5251
self._local_protocol = _LocalProtocol(self._data_received, self._connection_lost)
52+
self._encoder: Encoder = create_local_encoder(device_data.device.local_key)
53+
self._decoder: Decoder = create_local_decoder(device_data.device.local_key)
5354

5455
def _data_received(self, message):
5556
"""Called when data is received from the transport."""
56-
if self.remaining:
57-
message = self.remaining + message
58-
self.remaining = b""
59-
parser_msg, self.remaining = MessageParser.parse(message, local_key=self.device_info.device.local_key)
60-
self.on_message_received(parser_msg)
57+
parsed_msg = self._decoder(message)
58+
self.on_message_received(parsed_msg)
6159

6260
def _connection_lost(self, exc: Exception | None):
6361
"""Called when the transport connection is lost."""

roborock/mqtt/roborock_session.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ async def _run_task(self, start_future: asyncio.Future[None] | None) -> None:
118118
if start_future:
119119
_LOGGER.debug("MQTT loop was cancelled")
120120
start_future.set_exception(err)
121-
_LOGGER.debug("MQTT loop was cancelled whiel starting")
121+
_LOGGER.debug("MQTT loop was cancelled while starting")
122122
return
123123
# Catch exceptions to avoid crashing the loop
124124
# and to allow the loop to retry.
@@ -160,7 +160,7 @@ async def _mqtt_client(self, params: MqttParams) -> aiomqtt.Client:
160160
async with self._client_lock:
161161
self._client = client
162162
for topic in self._listeners:
163-
_LOGGER.debug("Re-establising subscription to topic %s", topic)
163+
_LOGGER.debug("Re-establishing subscription to topic %s", topic)
164164
# TODO: If this fails it will break the whole connection. Make
165165
# this retry again in the background with backoff.
166166
await client.subscribe(topic)

roborock/protocol.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,56 @@ def build(
359359

360360
MessageParser: _Parser = _Parser(_Messages, True)
361361
BroadcastParser: _Parser = _Parser(_BroadcastMessage, False)
362+
363+
364+
Decoder = Callable[[bytes], list[RoborockMessage]]
365+
Encoder = Callable[[RoborockMessage], bytes]
366+
367+
368+
def create_mqtt_decoder(local_key: str) -> Decoder:
369+
"""Create a decoder for MQTT messages."""
370+
371+
def decode(data: bytes) -> list[RoborockMessage]:
372+
"""Parse the given data into Roborock messages."""
373+
messages, _ = MessageParser.parse(data, local_key)
374+
return messages
375+
376+
return decode
377+
378+
379+
def create_mqtt_encoder(local_key: str) -> Encoder:
380+
"""Create an encoder for MQTT messages."""
381+
382+
def encode(messages: RoborockMessage) -> bytes:
383+
"""Build the given Roborock messages into a byte string."""
384+
return MessageParser.build(messages, local_key, prefixed=False)
385+
386+
return encode
387+
388+
389+
def create_local_decoder(local_key: str) -> Decoder:
390+
"""Create a decoder for local API messages."""
391+
392+
# This buffer is used to accumulate bytes until a complete message can be parsed.
393+
# It is defined outside the decode function to maintain state across calls.
394+
buffer: bytes = b""
395+
396+
def decode(bytes: bytes) -> list[RoborockMessage]:
397+
"""Parse the given data into Roborock messages."""
398+
nonlocal buffer
399+
buffer += bytes
400+
parsed_messages, remaining = MessageParser.parse(buffer, local_key=local_key)
401+
buffer = remaining
402+
return parsed_messages
403+
404+
return decode
405+
406+
407+
def create_local_encoder(local_key: str) -> Encoder:
408+
"""Create an encoder for local API messages."""
409+
410+
def encode(message: RoborockMessage) -> bytes:
411+
"""Called when data is sent to the transport."""
412+
return MessageParser.build(message, local_key=local_key)
413+
414+
return encode

roborock/version_1_apis/roborock_local_client_v1.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from .. import CommandVacuumError, DeviceData, RoborockCommand, RoborockException
66
from ..exceptions import VacuumError
7-
from ..protocol import MessageParser
87
from ..roborock_message import MessageRetry, RoborockMessage, RoborockMessageProtocol
98
from ..util import RoborockLoggerAdapter
109
from .roborock_client_v1 import COMMANDS_SECURED, RoborockClientV1
@@ -57,8 +56,7 @@ async def send_message(self, roborock_message: RoborockMessage):
5756
response_protocol = RoborockMessageProtocol.GENERAL_REQUEST
5857
if request_id is None:
5958
raise RoborockException(f"Failed build message {roborock_message}")
60-
local_key = self.device_info.device.local_key
61-
msg = MessageParser.build(roborock_message, local_key=local_key)
59+
msg = self._encoder(roborock_message)
6260
if method:
6361
self._logger.debug(f"id={request_id} Requesting method {method} with {params}")
6462
# Send the command to the Roborock device

0 commit comments

Comments
 (0)