Skip to content

Add support for Roborock Qrevo MaxV code mappings #385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 30, 2025
2 changes: 1 addition & 1 deletion roborock/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def should_keepalive(self) -> bool:

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

Expand Down
33 changes: 33 additions & 0 deletions roborock/code_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,17 @@ class RoborockFanSpeedQRevoCurv(RoborockFanPowerCode):
smart_mode = 110


class RoborockFanSpeedQRevoMaxV(RoborockFanPowerCode):
off = 105
quiet = 101
balanced = 102
turbo = 103
max = 104
custom = 106
max_plus = 108
smart_mode = 110


class RoborockFanSpeedP10(RoborockFanPowerCode):
off = 105
quiet = 101
Expand All @@ -275,6 +286,7 @@ class RoborockFanSpeedP10(RoborockFanPowerCode):
max = 104
custom = 106
max_plus = 108
smart_mode = 110


class RoborockFanSpeedS8MaxVUltra(RoborockFanPowerCode):
Expand Down Expand Up @@ -316,6 +328,7 @@ class RoborockMopModeS8ProUltra(RoborockMopModeCode):
deep_plus = 303
fast = 304
custom = 302
smart_mode = 306


class RoborockMopModeS8MaxVUltra(RoborockMopModeCode):
Expand All @@ -337,6 +350,15 @@ class RoborockMopModeQRevoMaster(RoborockMopModeCode):
smart_mode = 306


class RoborockMopModeQRevoMaxV(RoborockMopModeCode):
standard = 300
deep = 301
custom = 302
deep_plus = 303
fast = 304
smart_mode = 306


class RoborockMopIntensityCode(RoborockEnum):
"""Describes the mop intensity of the vacuum cleaner."""

Expand Down Expand Up @@ -383,6 +405,16 @@ class RoborockMopIntensityQRevoCurv(RoborockMopIntensityCode):
smart_mode = 209


class RoborockMopIntensityQRevoMaxV(RoborockMopIntensityCode):
off = 200
low = 201
medium = 202
high = 203
custom = 204
custom_water_flow = 207
smart_mode = 209


class RoborockMopIntensityP10(RoborockMopIntensityCode):
"""Describes the mop intensity of the vacuum cleaner."""

Expand All @@ -392,6 +424,7 @@ class RoborockMopIntensityP10(RoborockMopIntensityCode):
high = 203
custom = 204
custom_water_flow = 207
smart_mode = 209


class RoborockMopIntensityS8MaxVUltra(RoborockMopIntensityCode):
Expand Down
12 changes: 11 additions & 1 deletion roborock/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
RoborockFanSpeedQ7Max,
RoborockFanSpeedQRevoCurv,
RoborockFanSpeedQRevoMaster,
RoborockFanSpeedQRevoMaxV,
RoborockFanSpeedS6Pure,
RoborockFanSpeedS7,
RoborockFanSpeedS7MaxV,
Expand All @@ -33,13 +34,15 @@
RoborockMopIntensityQ7Max,
RoborockMopIntensityQRevoCurv,
RoborockMopIntensityQRevoMaster,
RoborockMopIntensityQRevoMaxV,
RoborockMopIntensityS5Max,
RoborockMopIntensityS6MaxV,
RoborockMopIntensityS7,
RoborockMopIntensityS8MaxVUltra,
RoborockMopModeCode,
RoborockMopModeQRevoCurv,
RoborockMopModeQRevoMaster,
RoborockMopModeQRevoMaxV,
RoborockMopModeS7,
RoborockMopModeS8MaxVUltra,
RoborockMopModeS8ProUltra,
Expand Down Expand Up @@ -599,6 +602,13 @@ class QRevoCurvStatus(Status):
mop_mode: RoborockMopModeQRevoCurv | None = None


@dataclass
class QRevoMaxVStatus(Status):
fan_power: RoborockFanSpeedQRevoMaxV | None = None
water_box_mode: RoborockMopIntensityQRevoMaxV | None = None
mop_mode: RoborockMopModeQRevoMaxV | None = None


@dataclass
class S6MaxVStatus(Status):
fan_power: RoborockFanSpeedS7MaxV | None = None
Expand Down Expand Up @@ -672,7 +682,7 @@ class S8MaxvUltraStatus(Status):
# but i am currently unable to do my typical reverse engineering/ get any data from users on this,
# so this will be here in the mean time.
ROBOROCK_QREVO_S: P10Status,
ROBOROCK_QREVO_MAXV: P10Status,
ROBOROCK_QREVO_MAXV: QRevoMaxVStatus,
ROBOROCK_QREVO_PRO: P10Status,
ROBOROCK_S8_MAXV_ULTRA: S8MaxvUltraStatus,
}
Expand Down
4 changes: 2 additions & 2 deletions roborock/mqtt/roborock_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ async def _run_task(self, start_future: asyncio.Future[None] | None) -> None:
if start_future:
_LOGGER.debug("MQTT loop was cancelled")
start_future.set_exception(err)
_LOGGER.debug("MQTT loop was cancelled whiel starting")
_LOGGER.debug("MQTT loop was cancelled while starting")
return
# Catch exceptions to avoid crashing the loop
# and to allow the loop to retry.
Expand Down Expand Up @@ -160,7 +160,7 @@ async def _mqtt_client(self, params: MqttParams) -> aiomqtt.Client:
async with self._client_lock:
self._client = client
for topic in self._listeners:
_LOGGER.debug("Re-establising subscription to topic %s", topic)
_LOGGER.debug("Re-establishing subscription to topic %s", topic)
# TODO: If this fails it will break the whole connection. Make
# this retry again in the background with backoff.
await client.subscribe(topic)
Expand Down
2 changes: 1 addition & 1 deletion tests/mqtt_packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def gen_publish(
properties = prop_finalise(properties)
rl += len(properties)
# This will break if len(properties) > 127
pack_format = pack_format + "%ds" % (len(properties))
pack_format = f"{pack_format}{len(properties)}s"

if payload is not None:
# payload = payload.encode("utf-8")
Expand Down
2 changes: 1 addition & 1 deletion tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_start_date_lower_than_now_lower_than_end_date():


# start_date > now > end_date
def test_start_date_greater_than_now_greater_tat_end_date():
def test_start_date_greater_than_now_greater_than_end_date():
start, end = parse_time_to_datetime(
(datetime.datetime.now() + datetime.timedelta(hours=1)).time(),
(datetime.datetime.now() + datetime.timedelta(hours=2)).time(),
Expand Down