Skip to content

Commit 4cae2d7

Browse files
authored
Merge branch 'main' into get_home_data_v3
2 parents 6d8272a + 5a2dac0 commit 4cae2d7

File tree

11 files changed

+437
-102
lines changed

11 files changed

+437
-102
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
persist-credentials: false
7676
- name: Python Semantic Release
7777
id: release
78-
uses: python-semantic-release/python-semantic-release@v10.1.0
78+
uses: python-semantic-release/python-semantic-release@v10.2.0
7979
with:
8080
github_token: ${{ secrets.GH_TOKEN }}
8181

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.25.0"
3+
version = "2.25.1"
44
description = "A package to control Roborock vacuums."
55
authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
66
license = "GPL-3.0-only"

roborock/code_mappings.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,17 @@ class RoborockFanSpeedS8MaxVUltra(RoborockFanPowerCode):
300300
smart_mode = 110
301301

302302

303+
class RoborockFanSpeedSaros10R(RoborockFanPowerCode):
304+
off = 105
305+
quiet = 101
306+
balanced = 102
307+
turbo = 103
308+
max = 104
309+
custom = 106
310+
max_plus = 108
311+
smart_mode = 110
312+
313+
303314
class RoborockMopModeCode(RoborockEnum):
304315
"""Describes the mop mode of the vacuum cleaner."""
305316

@@ -341,6 +352,15 @@ class RoborockMopModeS8MaxVUltra(RoborockMopModeCode):
341352
smart_mode = 306
342353

343354

355+
class RoborockMopModeSaros10R(RoborockMopModeCode):
356+
standard = 300
357+
deep = 301
358+
custom = 302
359+
deep_plus = 303
360+
fast = 304
361+
smart_mode = 306
362+
363+
344364
class RoborockMopModeQRevoMaster(RoborockMopModeCode):
345365
standard = 300
346366
deep = 301
@@ -438,6 +458,17 @@ class RoborockMopIntensityS8MaxVUltra(RoborockMopIntensityCode):
438458
custom_water_flow = 207
439459

440460

461+
class RoborockMopIntensitySaros10R(RoborockMopIntensityCode):
462+
off = 200
463+
low = 201
464+
medium = 202
465+
high = 203
466+
custom = 204
467+
extreme = 250
468+
vac_followed_by_mop = 235
469+
smart_mode = 209
470+
471+
441472
class RoborockMopIntensityS5Max(RoborockMopIntensityCode):
442473
"""Describes the mop intensity of the vacuum cleaner."""
443474

roborock/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
ROBOROCK_QREVO_S = "roborock.vacuum.a104"
5151
ROBOROCK_QREVO_PRO = "roborock.vacuum.a101"
5252
ROBOROCK_QREVO_MAXV = "roborock.vacuum.a87"
53+
ROBOROCK_SAROS_10R = "roborock.vacuum.a144"
5354

5455
ROBOROCK_DYAD_AIR = "roborock.wetdryvac.a107"
5556
ROBOROCK_DYAD_PRO_COMBO = "roborock.wetdryvac.a83"

roborock/containers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
RoborockFanSpeedS7,
2929
RoborockFanSpeedS7MaxV,
3030
RoborockFanSpeedS8MaxVUltra,
31+
RoborockFanSpeedSaros10R,
3132
RoborockFinishReason,
3233
RoborockInCleaning,
3334
RoborockMopIntensityCode,
@@ -40,13 +41,15 @@
4041
RoborockMopIntensityS6MaxV,
4142
RoborockMopIntensityS7,
4243
RoborockMopIntensityS8MaxVUltra,
44+
RoborockMopIntensitySaros10R,
4345
RoborockMopModeCode,
4446
RoborockMopModeQRevoCurv,
4547
RoborockMopModeQRevoMaster,
4648
RoborockMopModeQRevoMaxV,
4749
RoborockMopModeS7,
4850
RoborockMopModeS8MaxVUltra,
4951
RoborockMopModeS8ProUltra,
52+
RoborockMopModeSaros10R,
5053
RoborockStartType,
5154
RoborockStateCode,
5255
)
@@ -74,6 +77,7 @@
7477
ROBOROCK_S8,
7578
ROBOROCK_S8_MAXV_ULTRA,
7679
ROBOROCK_S8_PRO_ULTRA,
80+
ROBOROCK_SAROS_10R,
7781
SENSOR_DIRTY_REPLACE_TIME,
7882
SIDE_BRUSH_REPLACE_TIME,
7983
STRAINER_REPLACE_TIME,
@@ -678,6 +682,13 @@ class S8MaxvUltraStatus(Status):
678682
mop_mode: RoborockMopModeS8MaxVUltra | None = None
679683

680684

685+
@dataclass
686+
class Saros10RStatus(Status):
687+
fan_power: RoborockFanSpeedSaros10R | None = None
688+
water_box_mode: RoborockMopIntensitySaros10R | None = None
689+
mop_mode: RoborockMopModeSaros10R | None = None
690+
691+
681692
ModelStatus: dict[str, type[Status]] = {
682693
ROBOROCK_S4_MAX: S4MaxStatus,
683694
ROBOROCK_S5_MAX: S5MaxStatus,
@@ -701,6 +712,7 @@ class S8MaxvUltraStatus(Status):
701712
ROBOROCK_QREVO_MAXV: QRevoMaxVStatus,
702713
ROBOROCK_QREVO_PRO: P10Status,
703714
ROBOROCK_S8_MAXV_ULTRA: S8MaxvUltraStatus,
715+
ROBOROCK_SAROS_10R: Saros10RStatus,
704716
}
705717

706718

roborock/protocols/a01_protocol.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""Roborock A01 Protocol encoding and decoding."""
2+
3+
import json
4+
import logging
5+
from typing import Any
6+
7+
from Crypto.Cipher import AES
8+
from Crypto.Util.Padding import pad, unpad
9+
10+
from roborock.exceptions import RoborockException
11+
from roborock.roborock_message import (
12+
RoborockDyadDataProtocol,
13+
RoborockMessage,
14+
RoborockMessageProtocol,
15+
RoborockZeoProtocol,
16+
)
17+
18+
_LOGGER = logging.getLogger(__name__)
19+
20+
A01_VERSION = b"A01"
21+
22+
23+
def encode_mqtt_payload(data: dict[RoborockDyadDataProtocol | RoborockZeoProtocol, Any]) -> RoborockMessage:
24+
"""Encode payload for A01 commands over MQTT."""
25+
dps_data = {"dps": data}
26+
payload = pad(json.dumps(dps_data).encode("utf-8"), AES.block_size)
27+
return RoborockMessage(
28+
protocol=RoborockMessageProtocol.RPC_REQUEST,
29+
version=A01_VERSION,
30+
payload=payload,
31+
)
32+
33+
34+
def decode_rpc_response(message: RoborockMessage) -> dict[int, Any]:
35+
"""Decode a V1 RPC_RESPONSE message."""
36+
if not message.payload:
37+
raise RoborockException("Invalid A01 message format: missing payload")
38+
try:
39+
unpadded = unpad(message.payload, AES.block_size)
40+
except ValueError as err:
41+
raise RoborockException(f"Unable to unpad A01 payload: {err}")
42+
43+
try:
44+
payload = json.loads(unpadded.decode())
45+
except (json.JSONDecodeError, TypeError) as e:
46+
raise RoborockException(f"Invalid A01 message payload: {e} for {message.payload!r}") from e
47+
48+
datapoints = payload.get("dps", {})
49+
if not isinstance(datapoints, dict):
50+
raise RoborockException(f"Invalid A01 message format: 'dps' should be a dictionary for {message.payload!r}")
51+
try:
52+
return {int(key): value for key, value in datapoints.items()}
53+
except ValueError:
54+
raise RoborockException(f"Invalid A01 message format: 'dps' key should be an integer for {message.payload!r}")

0 commit comments

Comments
 (0)