python-roborock 2.34.2__tar.gz → 2.35.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.
Files changed (53) hide show
  1. {python_roborock-2.34.2 → python_roborock-2.35.0}/PKG-INFO +1 -1
  2. {python_roborock-2.34.2 → python_roborock-2.35.0}/pyproject.toml +1 -1
  3. python_roborock-2.35.0/roborock/clean_modes.py +113 -0
  4. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/device_features.py +1 -0
  5. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/roborock_message.py +0 -20
  6. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/version_1_apis/roborock_local_client_v1.py +25 -25
  7. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +14 -19
  8. {python_roborock-2.34.2 → python_roborock-2.35.0}/LICENSE +0 -0
  9. {python_roborock-2.34.2 → python_roborock-2.35.0}/README.md +0 -0
  10. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/__init__.py +0 -0
  11. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/api.py +0 -0
  12. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/cli.py +0 -0
  13. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/cloud_api.py +0 -0
  14. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/code_mappings.py +0 -0
  15. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/command_cache.py +0 -0
  16. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/const.py +0 -0
  17. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/containers.py +0 -0
  18. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/README.md +0 -0
  19. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/__init__.py +0 -0
  20. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/a01_channel.py +0 -0
  21. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/b01_channel.py +0 -0
  22. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/cache.py +0 -0
  23. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/channel.py +0 -0
  24. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/device.py +0 -0
  25. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/device_manager.py +0 -0
  26. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/local_channel.py +0 -0
  27. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/mqtt_channel.py +0 -0
  28. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/traits/b01/__init__.py +0 -0
  29. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/traits/b01/props.py +0 -0
  30. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/traits/dyad.py +0 -0
  31. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/traits/status.py +0 -0
  32. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/traits/trait.py +0 -0
  33. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/traits/zeo.py +0 -0
  34. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/v1_channel.py +0 -0
  35. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/devices/v1_rpc_channel.py +0 -0
  36. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/exceptions.py +0 -0
  37. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/mqtt/__init__.py +0 -0
  38. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/mqtt/roborock_session.py +0 -0
  39. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/mqtt/session.py +0 -0
  40. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/protocol.py +0 -0
  41. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/protocols/a01_protocol.py +0 -0
  42. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/protocols/b01_protocol.py +0 -0
  43. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/protocols/v1_protocol.py +0 -0
  44. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/py.typed +0 -0
  45. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/roborock_future.py +0 -0
  46. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/roborock_typing.py +0 -0
  47. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/util.py +0 -0
  48. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/version_1_apis/__init__.py +0 -0
  49. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  50. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/version_a01_apis/__init__.py +0 -0
  51. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  52. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
  53. {python_roborock-2.34.2 → python_roborock-2.35.0}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-roborock
3
- Version: 2.34.2
3
+ Version: 2.35.0
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Home-page: https://github.com/humbertogontijo/python-roborock
6
6
  License: GPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-roborock"
3
- version = "2.34.2"
3
+ version = "2.35.0"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
6
6
  license = "GPL-3.0-only"
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import StrEnum
4
+
5
+ from roborock import DeviceFeatures
6
+
7
+
8
+ class RoborockModeEnum(StrEnum):
9
+ """A custom StrEnum that also stores an integer code for each member."""
10
+
11
+ code: int
12
+
13
+ def __new__(cls, value: str, code: int) -> RoborockModeEnum:
14
+ """Creates a new enum member."""
15
+ member = str.__new__(cls, value)
16
+ member._value_ = value
17
+ member.code = code
18
+ return member
19
+
20
+
21
+ class CleanModes(RoborockModeEnum):
22
+ GENTLE = ("gentle", 105)
23
+ OFF = ("off", 105)
24
+ QUIET = ("quiet", 101)
25
+ BALANCED = ("balanced", 102)
26
+ TURBO = ("turbo", 103)
27
+ MAX = ("max", 104)
28
+ MAX_PLUS = ("max_plus", 108)
29
+ CUSTOMIZED = ("custom", 106)
30
+ SMART_MODE = ("smart_mode", 110)
31
+
32
+
33
+ class CleanRoutes(RoborockModeEnum):
34
+ STANDARD = ("standard", 300)
35
+ DEEP = ("deep", 301)
36
+ DEEP_PLUS = ("deep_plus", 303)
37
+ FAST = ("fast", 304)
38
+ DEEP_PLUS_CN = ("deep_plus", 305)
39
+ SMART_MODE = ("smart_mode", 306)
40
+ CUSTOMIZED = ("custom", 302)
41
+
42
+
43
+ class CleanModesOld(RoborockModeEnum):
44
+ QUIET = ("quiet", 38)
45
+ BALANCED = ("balanced", 60)
46
+ TURBO = ("turbo", 75)
47
+ MAX = ("max", 100)
48
+
49
+
50
+ class WaterModes(RoborockModeEnum):
51
+ OFF = ("off", 200)
52
+ LOW = ("low", 201)
53
+ MILD = ("mild", 201)
54
+ MEDIUM = ("medium", 202)
55
+ STANDARD = ("standard", 202)
56
+ HIGH = ("high", 203)
57
+ INTENSE = ("intense", 203)
58
+ CUSTOMIZED = ("custom", 204)
59
+ CUSTOM = ("custom_water_flow", 207)
60
+ EXTREME = ("extreme", 208)
61
+ SMART_MODE = ("smart_mode", 209)
62
+
63
+
64
+ def get_clean_modes(features: DeviceFeatures) -> list[CleanModes]:
65
+ """Get the valid clean modes for the device - also known as 'fan power' or 'suction mode'"""
66
+ modes = [CleanModes.QUIET, CleanModes.BALANCED, CleanModes.TURBO, CleanModes.MAX]
67
+ if features.is_max_plus_mode_supported or features.is_none_pure_clean_mop_with_max_plus:
68
+ # If the vacuum has max plus mode supported
69
+ modes.append(CleanModes.MAX_PLUS)
70
+ if features.is_pure_clean_mop_supported:
71
+ # If the vacuum is capable of 'pure mop clean' aka no vacuum
72
+ modes.append(CleanModes.OFF)
73
+ else:
74
+ # If not, we can add gentle
75
+ modes.append(CleanModes.GENTLE)
76
+ return modes
77
+
78
+
79
+ def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]:
80
+ """The routes that the vacuum will take while mopping"""
81
+ if features.is_none_pure_clean_mop_with_max_plus:
82
+ return [CleanRoutes.FAST, CleanRoutes.STANDARD]
83
+ supported = [CleanRoutes.STANDARD, CleanRoutes.DEEP]
84
+ if features.is_careful_slow_mop_supported:
85
+ if not (
86
+ features.is_corner_clean_mode_supported
87
+ and features.is_clean_route_deep_slow_plus_supported
88
+ and region == "CN"
89
+ ):
90
+ # for some reason there is a china specific deep plus mode
91
+ supported.append(CleanRoutes.DEEP_PLUS_CN)
92
+ else:
93
+ supported.append(CleanRoutes.DEEP_PLUS)
94
+
95
+ if features.is_clean_route_fast_mode_supported:
96
+ supported.append(CleanRoutes.FAST)
97
+ return supported
98
+
99
+
100
+ def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
101
+ """Get the valid water modes for the device - also known as 'water flow' or 'water level'"""
102
+ supported_modes = [WaterModes.OFF]
103
+ if features.is_mop_shake_module_supported:
104
+ # For mops that have the vibrating mop pad, they do mild standard intense
105
+ supported_modes.extend([WaterModes.MILD, WaterModes.STANDARD, WaterModes.INTENSE])
106
+ else:
107
+ supported_modes.extend([WaterModes.LOW, WaterModes.MEDIUM, WaterModes.HIGH])
108
+ if features.is_custom_water_box_distance_supported:
109
+ # This is for devices that allow you to set a custom water flow from 0-100
110
+ supported_modes.append(WaterModes.CUSTOM)
111
+ if features.is_mop_shake_module_supported and features.is_mop_shake_water_max_supported:
112
+ supported_modes.append(WaterModes.EXTREME)
113
+ return supported_modes
@@ -423,6 +423,7 @@ class DeviceFeatures:
423
423
  is_clean_route_setting_supported: bool = field(
424
424
  metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE, ProductFeatures.MOP_SPIN_MODULE]}
425
425
  )
426
+ is_mop_shake_module_supported: bool = field(metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE]})
426
427
 
427
428
  @classmethod
428
429
  def from_feature_flags(
@@ -256,23 +256,3 @@ class RoborockMessage:
256
256
  data_point_response = json.loads(data_point)
257
257
  return data_point_response.get("id")
258
258
  return None
259
-
260
- def get_method(self) -> str | None:
261
- protocol = self.protocol
262
- if self.payload and protocol in [4, 5, 101, 102]:
263
- payload = json.loads(self.payload.decode())
264
- for data_point_number, data_point in payload.get("dps").items():
265
- if data_point_number in ["101", "102"]:
266
- data_point_response = json.loads(data_point)
267
- return data_point_response.get("method")
268
- return None
269
-
270
- def get_params(self) -> list | dict | None:
271
- protocol = self.protocol
272
- if self.payload and protocol in [4, 101, 102]:
273
- payload = json.loads(self.payload.decode())
274
- for data_point_number, data_point in payload.get("dps").items():
275
- if data_point_number in ["101", "102"]:
276
- data_point_response = json.loads(data_point)
277
- return data_point_response.get("params")
278
- return None
@@ -18,6 +18,19 @@ from .roborock_client_v1 import CLOUD_REQUIRED, RoborockClientV1
18
18
  _LOGGER = logging.getLogger(__name__)
19
19
 
20
20
 
21
+ _HELLO_REQUEST_MESSAGE = RoborockMessage(
22
+ protocol=RoborockMessageProtocol.HELLO_REQUEST,
23
+ seq=1,
24
+ random=22,
25
+ )
26
+
27
+ _PING_REQUEST_MESSAGE = RoborockMessage(
28
+ protocol=RoborockMessageProtocol.PING_REQUEST,
29
+ seq=2,
30
+ random=23,
31
+ )
32
+
33
+
21
34
  @dataclass
22
35
  class _LocalProtocol(asyncio.Protocol):
23
36
  """Callbacks for the Roborock local client transport."""
@@ -109,29 +122,13 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
109
122
  self._sync_disconnect()
110
123
 
111
124
  async def hello(self):
112
- request_id = 1
113
- protocol = RoborockMessageProtocol.HELLO_REQUEST
114
125
  try:
115
- return await self._send_message(
116
- RoborockMessage(
117
- protocol=protocol,
118
- seq=request_id,
119
- random=22,
120
- )
121
- )
126
+ return await self._send_message(_HELLO_REQUEST_MESSAGE)
122
127
  except Exception as e:
123
128
  self._logger.error(e)
124
129
 
125
130
  async def ping(self) -> None:
126
- request_id = 2
127
- protocol = RoborockMessageProtocol.PING_REQUEST
128
- return await self._send_message(
129
- RoborockMessage(
130
- protocol=protocol,
131
- seq=request_id,
132
- random=23,
133
- )
134
- )
131
+ await self._send_message(_PING_REQUEST_MESSAGE)
135
132
 
136
133
  def _send_msg_raw(self, data: bytes):
137
134
  try:
@@ -151,12 +148,15 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
151
148
 
152
149
  roborock_message = encode_local_payload(method, params)
153
150
  self._logger.debug("Building message id %s for method %s", roborock_message.get_request_id(), method)
154
- return await self._send_message(roborock_message)
151
+ return await self._send_message(roborock_message, method, params)
155
152
 
156
- async def _send_message(self, roborock_message: RoborockMessage):
153
+ async def _send_message(
154
+ self,
155
+ roborock_message: RoborockMessage,
156
+ method: str | None = None,
157
+ params: list | dict | int | None = None,
158
+ ) -> RoborockMessage:
157
159
  await self.validate_connection()
158
- method = roborock_message.get_method()
159
- params = roborock_message.get_params()
160
160
  request_id: int | None
161
161
  if not method or not method.startswith("get"):
162
162
  request_id = roborock_message.seq
@@ -177,16 +177,16 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
177
177
  response = await async_response
178
178
  except VacuumError as err:
179
179
  self._diagnostic_data[diagnostic_key] = {
180
- "params": roborock_message.get_params(),
180
+ "params": params,
181
181
  "error": err,
182
182
  }
183
183
  raise CommandVacuumError(method, err) from err
184
184
  self._diagnostic_data[diagnostic_key] = {
185
- "params": roborock_message.get_params(),
185
+ "params": params,
186
186
  "response": response,
187
187
  }
188
188
  if roborock_message.protocol == RoborockMessageProtocol.GENERAL_REQUEST:
189
- self._logger.debug(f"id={request_id} Response from method {roborock_message.get_method()}: {response}")
189
+ self._logger.debug(f"id={request_id} Response from method {method}: {response}")
190
190
  if response == "retry":
191
191
  raise RoborockException(f"Command {method} failed with 'retry' message; Device is busy, try again later")
192
192
  return response
@@ -13,7 +13,6 @@ from ..exceptions import CommandVacuumError, RoborockException, VacuumError
13
13
  from ..protocol import Utils
14
14
  from ..protocols.v1_protocol import SecurityData, create_mqtt_payload_encoder
15
15
  from ..roborock_message import (
16
- RoborockMessage,
17
16
  RoborockMessageProtocol,
18
17
  )
19
18
  from ..roborock_typing import RoborockCommand
@@ -41,10 +40,19 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
41
40
  SecurityData(endpoint=self._endpoint, nonce=self._nonce),
42
41
  )
43
42
 
44
- async def send_message(self, roborock_message: RoborockMessage):
43
+ async def _send_command(
44
+ self,
45
+ method: RoborockCommand | str,
46
+ params: list | dict | int | None = None,
47
+ ):
48
+ if method in CUSTOM_COMMANDS:
49
+ # When we have more custom commands do something more complicated here
50
+ return await self._get_calibration_points()
51
+
52
+ roborock_message = self._payload_encoder(method, params)
53
+ self._logger.debug("Building message id %s for method %s", roborock_message.get_request_id, method)
54
+
45
55
  await self.validate_connection()
46
- method = roborock_message.get_method()
47
- params = roborock_message.get_params()
48
56
  request_id = roborock_message.get_request_id()
49
57
  if request_id is None:
50
58
  raise RoborockException(f"Failed build message {roborock_message}")
@@ -60,12 +68,12 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
60
68
  response = await async_response
61
69
  except VacuumError as err:
62
70
  self._diagnostic_data[diagnostic_key] = {
63
- "params": roborock_message.get_params(),
71
+ "params": params,
64
72
  "error": err,
65
73
  }
66
74
  raise CommandVacuumError(method, err) from err
67
75
  self._diagnostic_data[diagnostic_key] = {
68
- "params": roborock_message.get_params(),
76
+ "params": params,
69
77
  "response": response,
70
78
  }
71
79
  if response_protocol == RoborockMessageProtocol.MAP_RESPONSE:
@@ -74,19 +82,6 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
74
82
  self._logger.debug(f"id={request_id} Response from {method}: {response}")
75
83
  return response
76
84
 
77
- async def _send_command(
78
- self,
79
- method: RoborockCommand | str,
80
- params: list | dict | int | None = None,
81
- ):
82
- if method in CUSTOM_COMMANDS:
83
- # When we have more custom commands do something more complicated here
84
- return await self._get_calibration_points()
85
-
86
- roborock_message = self._payload_encoder(method, params)
87
- self._logger.debug("Building message id %s for method %s", roborock_message.get_request_id, method)
88
- return await self.send_message(roborock_message)
89
-
90
85
  async def _get_calibration_points(self):
91
86
  map: bytes = await self.send_command(RoborockCommand.GET_MAP_V1)
92
87
  parser = RoborockMapDataParser(ColorsPalette(), Sizes(), [], ImageConfig(), [])