python-roborock 4.17.2__tar.gz → 4.18.1__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 (97) hide show
  1. {python_roborock-4.17.2 → python_roborock-4.18.1}/PKG-INFO +1 -1
  2. {python_roborock-4.17.2 → python_roborock-4.18.1}/pyproject.toml +1 -1
  3. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/v1/v1_clean_modes.py +25 -10
  4. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/status.py +11 -2
  5. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/mqtt/roborock_session.py +22 -5
  6. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/mqtt/session.py +3 -0
  7. {python_roborock-4.17.2 → python_roborock-4.18.1}/.gitignore +0 -0
  8. {python_roborock-4.17.2 → python_roborock-4.18.1}/LICENSE +0 -0
  9. {python_roborock-4.17.2 → python_roborock-4.18.1}/README.md +0 -0
  10. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/__init__.py +0 -0
  11. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/broadcast_protocol.py +0 -0
  12. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/callbacks.py +0 -0
  13. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/cli.py +0 -0
  14. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/const.py +0 -0
  15. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/__init__.py +0 -0
  16. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/b01_q10/__init__.py +0 -0
  17. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  18. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  19. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/b01_q7/__init__.py +0 -0
  20. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  21. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  22. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/code_mappings.py +0 -0
  23. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/containers.py +0 -0
  24. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/dyad/__init__.py +0 -0
  25. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  26. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/dyad/dyad_containers.py +0 -0
  27. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/v1/__init__.py +0 -0
  28. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/v1/v1_code_mappings.py +0 -0
  29. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/v1/v1_containers.py +0 -0
  30. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/zeo/__init__.py +0 -0
  31. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  32. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/data/zeo/zeo_containers.py +0 -0
  33. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/device_features.py +0 -0
  34. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/README.md +0 -0
  35. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/__init__.py +0 -0
  36. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/cache.py +0 -0
  37. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/device.py +0 -0
  38. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/device_manager.py +0 -0
  39. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/file_cache.py +0 -0
  40. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/rpc/__init__.py +0 -0
  41. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/rpc/a01_channel.py +0 -0
  42. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/rpc/b01_q10_channel.py +0 -0
  43. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/rpc/b01_q7_channel.py +0 -0
  44. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/rpc/v1_channel.py +0 -0
  45. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/__init__.py +0 -0
  46. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/a01/__init__.py +0 -0
  47. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/b01/__init__.py +0 -0
  48. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/b01/q10/__init__.py +0 -0
  49. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/b01/q10/command.py +0 -0
  50. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/b01/q10/common.py +0 -0
  51. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/b01/q10/status.py +0 -0
  52. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
  53. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/b01/q7/__init__.py +0 -0
  54. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
  55. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/traits_mixin.py +0 -0
  56. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/__init__.py +0 -0
  57. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/child_lock.py +0 -0
  58. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/clean_summary.py +0 -0
  59. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/command.py +0 -0
  60. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/common.py +0 -0
  61. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/consumeable.py +0 -0
  62. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/device_features.py +0 -0
  63. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  64. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  65. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  66. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/home.py +0 -0
  67. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/led_status.py +0 -0
  68. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/map_content.py +0 -0
  69. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/maps.py +0 -0
  70. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/network_info.py +0 -0
  71. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/rooms.py +0 -0
  72. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/routines.py +0 -0
  73. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  74. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  75. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/volume.py +0 -0
  76. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  77. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/transport/__init__.py +0 -0
  78. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/transport/channel.py +0 -0
  79. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/transport/local_channel.py +0 -0
  80. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/devices/transport/mqtt_channel.py +0 -0
  81. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/diagnostics.py +0 -0
  82. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/exceptions.py +0 -0
  83. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/map/__init__.py +0 -0
  84. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/map/map_parser.py +0 -0
  85. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/mqtt/__init__.py +0 -0
  86. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/mqtt/health_manager.py +0 -0
  87. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/protocol.py +0 -0
  88. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/protocols/__init__.py +0 -0
  89. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/protocols/a01_protocol.py +0 -0
  90. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/protocols/b01_q10_protocol.py +0 -0
  91. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/protocols/b01_q7_protocol.py +0 -0
  92. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/protocols/v1_protocol.py +0 -0
  93. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/py.typed +0 -0
  94. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/roborock_message.py +0 -0
  95. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/roborock_typing.py +0 -0
  96. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/util.py +0 -0
  97. {python_roborock-4.17.2 → python_roborock-4.18.1}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 4.17.2
3
+ Version: 4.18.1
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Project-URL: Repository, https://github.com/humbertogontijo/python-roborock
6
6
  Project-URL: Documentation, https://python-roborock.readthedocs.io/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-roborock"
3
- version = "4.17.2"
3
+ version = "4.18.1"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
6
6
  requires-python = ">=3.11, <4"
@@ -68,6 +68,17 @@ class WashTowelModes(RoborockModeEnum):
68
68
  SUPER_DEEP = ("super_deep", 8)
69
69
 
70
70
 
71
+ WATER_SLIDE_MODE_MAPPING: dict[int, WaterModes] = {
72
+ 200: WaterModes.OFF,
73
+ 221: WaterModes.PURE_WATER_FLOW_START,
74
+ 225: WaterModes.PURE_WATER_FLOW_SMALL,
75
+ 235: WaterModes.PURE_WATER_FLOW_MIDDLE,
76
+ 245: WaterModes.PURE_WATER_FLOW_LARGE,
77
+ 248: WaterModes.PURE_WATER_SUPER_BEGIN,
78
+ 250: WaterModes.PURE_WATER_FLOW_END,
79
+ }
80
+
81
+
71
82
  def get_wash_towel_modes(features: DeviceFeatures) -> list[WashTowelModes]:
72
83
  """Get the valid wash towel modes for the device"""
73
84
  modes = [WashTowelModes.LIGHT, WashTowelModes.BALANCED, WashTowelModes.DEEP]
@@ -128,17 +139,9 @@ def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]
128
139
 
129
140
  def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
130
141
  """Get the valid water modes for the device - also known as 'water flow' or 'water level'"""
131
- # If the device supports water slide mode, it uses a completely different set of modes. Technically, it can even
132
- # support values in between. But for now we will just support the main values.
142
+ # Water slide mode supports a separate set of water flow codes.
133
143
  if features.is_water_slide_mode_supported:
134
- return [
135
- WaterModes.PURE_WATER_FLOW_START,
136
- WaterModes.PURE_WATER_FLOW_SMALL,
137
- WaterModes.PURE_WATER_FLOW_MIDDLE,
138
- WaterModes.PURE_WATER_FLOW_LARGE,
139
- WaterModes.PURE_WATER_SUPER_BEGIN,
140
- WaterModes.PURE_WATER_FLOW_END,
141
- ]
144
+ return list(WATER_SLIDE_MODE_MAPPING.values())
142
145
 
143
146
  supported_modes = [WaterModes.OFF]
144
147
  if features.is_mop_shake_module_supported:
@@ -159,6 +162,18 @@ def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
159
162
  return supported_modes
160
163
 
161
164
 
165
+ def get_water_mode_mapping(features: DeviceFeatures) -> dict[int, str]:
166
+ """Get water mode mapping by supported feature set.
167
+
168
+ WaterModes contains aliases for multiple codes that share the same value
169
+ string (e.g. low can be 201 or 225). For water slide mode devices we need
170
+ explicit code mapping to preserve those slide-specific codes.
171
+ """
172
+ if features.is_water_slide_mode_supported:
173
+ return {code: mode.value for code, mode in WATER_SLIDE_MODE_MAPPING.items()}
174
+ return {mode.code: mode.value for mode in get_water_modes(features)}
175
+
176
+
162
177
  def is_mode_customized(clean_mode: VacuumModes, water_mode: WaterModes, mop_mode: CleanRoutes) -> bool:
163
178
  """Check if any of the cleaning modes are set to a custom value."""
164
179
  return (
@@ -1,7 +1,16 @@
1
1
  from functools import cached_property
2
2
  from typing import Self
3
3
 
4
- from roborock import CleanRoutes, StatusV2, VacuumModes, WaterModes, get_clean_modes, get_clean_routes, get_water_modes
4
+ from roborock import (
5
+ CleanRoutes,
6
+ StatusV2,
7
+ VacuumModes,
8
+ WaterModes,
9
+ get_clean_modes,
10
+ get_clean_routes,
11
+ get_water_mode_mapping,
12
+ get_water_modes,
13
+ )
5
14
  from roborock.roborock_typing import RoborockCommand
6
15
 
7
16
  from . import common
@@ -55,7 +64,7 @@ class StatusTrait(StatusV2, common.V1TraitMixin):
55
64
 
56
65
  @cached_property
57
66
  def water_mode_mapping(self) -> dict[int, str]:
58
- return {mop.code: mop.value for mop in self.water_mode_options}
67
+ return get_water_mode_mapping(self._device_features_trait)
59
68
 
60
69
  @cached_property
61
70
  def mop_route_options(self) -> list[CleanRoutes]:
@@ -11,6 +11,7 @@ receiving messages from the vacuum cleaner.
11
11
  import asyncio
12
12
  import datetime
13
13
  import logging
14
+ import ssl
14
15
  from collections.abc import Callable
15
16
  from contextlib import asynccontextmanager
16
17
 
@@ -26,7 +27,7 @@ from .session import MqttParams, MqttSession, MqttSessionException, MqttSessionU
26
27
  _LOGGER = logging.getLogger(__name__)
27
28
  _MQTT_LOGGER = logging.getLogger(f"{__name__}.aiomqtt")
28
29
 
29
- CLIENT_KEEPALIVE = datetime.timedelta(seconds=60)
30
+ CLIENT_KEEPALIVE = datetime.timedelta(seconds=45)
30
31
  TOPIC_KEEPALIVE = datetime.timedelta(seconds=60)
31
32
 
32
33
  # Exponential backoff parameters
@@ -55,9 +56,12 @@ class RoborockMqttSession(MqttSession):
55
56
  The client is run as a background task that will run until shutdown. Once
56
57
  connected, the client will wait for messages to be received in a loop. If
57
58
  the connection is lost, the client will be re-created and reconnected. There
58
- is backoff to avoid spamming the broker with connection attempts. The client
59
- will automatically re-establish any subscriptions when the connection is
60
- re-established.
59
+ is backoff to avoid spamming the broker with connection attempts.
60
+
61
+ Reconnect attempts are deferred while there are no active subscriptions,
62
+ which avoids unnecessary reconnect churn for idle sessions. Reconnects
63
+ resume as soon as a subscription is added again. The client automatically
64
+ re-establishes any existing subscriptions when the connection returns.
61
65
  """
62
66
 
63
67
  def __init__(
@@ -174,6 +178,16 @@ class RoborockMqttSession(MqttSession):
174
178
  if self._stop:
175
179
  _LOGGER.debug("MQTT session closed, stopping retry loop")
176
180
  return
181
+ if not self._client_subscribed_topics and not self._listeners.keys():
182
+ _LOGGER.debug("MQTT session disconnected with no active subscriptions, deferring reconnect")
183
+ self._diagnostics.increment("reconnect_deferred")
184
+ while not self._stop and not self._client_subscribed_topics and not self._listeners.keys():
185
+ await asyncio.sleep(0.1)
186
+ if self._stop:
187
+ _LOGGER.debug("MQTT session closed while waiting for active subscriptions")
188
+ return
189
+ self._backoff = MIN_BACKOFF_INTERVAL
190
+ continue
177
191
  _LOGGER.info("MQTT session disconnected, retrying in %s seconds", self._backoff.total_seconds())
178
192
  self._diagnostics.increment("reconnect_wait")
179
193
  await asyncio.sleep(self._backoff.total_seconds())
@@ -238,6 +252,9 @@ class RoborockMqttSession(MqttSession):
238
252
  async def _mqtt_client(self, params: MqttParams) -> aiomqtt.Client:
239
253
  """Connect to the MQTT broker and listen for messages."""
240
254
  _LOGGER.debug("Connecting to %s:%s for %s", params.host, params.port, params.username)
255
+ tls_params = None
256
+ if params.tls:
257
+ tls_params = TLSParameters(cert_reqs=ssl.CERT_REQUIRED if params.verify_tls else ssl.CERT_NONE)
241
258
  try:
242
259
  async with aiomqtt.Client(
243
260
  hostname=params.host,
@@ -246,7 +263,7 @@ class RoborockMqttSession(MqttSession):
246
263
  password=params.password,
247
264
  keepalive=int(CLIENT_KEEPALIVE.total_seconds()),
248
265
  protocol=aiomqtt.ProtocolVersion.V5,
249
- tls_params=TLSParameters() if params.tls else None,
266
+ tls_params=tls_params,
250
267
  timeout=params.timeout,
251
268
  logger=_MQTT_LOGGER,
252
269
  ) as client:
@@ -32,6 +32,9 @@ class MqttParams:
32
32
  password: str
33
33
  """MQTT password to use for authentication."""
34
34
 
35
+ verify_tls: bool = True
36
+ """Verify the TLS certificate."""
37
+
35
38
  timeout: float = DEFAULT_TIMEOUT
36
39
  """Timeout for communications with the broker in seconds."""
37
40