python-roborock 5.28.0__tar.gz → 5.29.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 (124) hide show
  1. {python_roborock-5.28.0 → python_roborock-5.29.0}/PKG-INFO +1 -1
  2. {python_roborock-5.28.0 → python_roborock-5.29.0}/pyproject.toml +1 -1
  3. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/v1/v1_code_mappings.py +1 -0
  4. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/device_features.py +3 -1
  5. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/roborock_message.py +18 -0
  6. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/testing/channel.py +27 -5
  7. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/testing/cloud.py +44 -1
  8. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/testing/simulator.py +2 -8
  9. {python_roborock-5.28.0 → python_roborock-5.29.0}/.gitignore +0 -0
  10. {python_roborock-5.28.0 → python_roborock-5.29.0}/LICENSE +0 -0
  11. {python_roborock-5.28.0 → python_roborock-5.29.0}/README.md +0 -0
  12. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/__init__.py +0 -0
  13. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/broadcast_protocol.py +0 -0
  14. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/callbacks.py +0 -0
  15. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/cli.py +0 -0
  16. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/const.py +0 -0
  17. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/__init__.py +0 -0
  18. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/b01_q10/__init__.py +0 -0
  19. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  20. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  21. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/b01_q7/__init__.py +0 -0
  22. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  23. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  24. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/code_mappings.py +0 -0
  25. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/containers.py +0 -0
  26. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/dyad/__init__.py +0 -0
  27. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  28. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/dyad/dyad_containers.py +0 -0
  29. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/mower/__init__.py +0 -0
  30. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/mower/mower_containers.py +0 -0
  31. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/v1/__init__.py +0 -0
  32. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/v1/v1_clean_modes.py +0 -0
  33. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/v1/v1_containers.py +0 -0
  34. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/zeo/__init__.py +0 -0
  35. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  36. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/data/zeo/zeo_containers.py +0 -0
  37. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/README.md +0 -0
  38. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/__init__.py +0 -0
  39. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/cache.py +0 -0
  40. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/device.py +0 -0
  41. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/device_manager.py +0 -0
  42. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/file_cache.py +0 -0
  43. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/rpc/__init__.py +0 -0
  44. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/rpc/a01_channel.py +0 -0
  45. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
  46. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/rpc/b01_q7_channel.py +0 -0
  47. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/rpc/v1_channel.py +0 -0
  48. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/__init__.py +0 -0
  49. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/a01/__init__.py +0 -0
  50. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/__init__.py +0 -0
  51. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
  52. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/button_light.py +0 -0
  53. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
  54. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
  55. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/command.py +0 -0
  56. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/common.py +0 -0
  57. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/consumable.py +0 -0
  58. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
  59. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
  60. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/map.py +0 -0
  61. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/network_info.py +0 -0
  62. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/remote.py +0 -0
  63. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/status.py +0 -0
  64. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
  65. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q10/volume.py +0 -0
  66. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
  67. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
  68. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q7/map.py +0 -0
  69. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/b01/q7/map_content.py +0 -0
  70. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/common.py +0 -0
  71. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/traits_mixin.py +0 -0
  72. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/__init__.py +0 -0
  73. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/child_lock.py +0 -0
  74. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
  75. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/command.py +0 -0
  76. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/common.py +0 -0
  77. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/consumeable.py +0 -0
  78. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/device_features.py +0 -0
  79. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  80. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  81. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  82. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/home.py +0 -0
  83. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/led_status.py +0 -0
  84. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/map_content.py +0 -0
  85. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/maps.py +0 -0
  86. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/network_info.py +0 -0
  87. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/rooms.py +0 -0
  88. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/routines.py +0 -0
  89. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  90. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/status.py +0 -0
  91. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  92. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/volume.py +0 -0
  93. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  94. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/transport/__init__.py +0 -0
  95. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/transport/channel.py +0 -0
  96. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/transport/local_channel.py +0 -0
  97. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/devices/transport/mqtt_channel.py +0 -0
  98. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/diagnostics.py +0 -0
  99. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/exceptions.py +0 -0
  100. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/__init__.py +0 -0
  101. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/b01_grid_layers.py +0 -0
  102. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/b01_map_parser.py +0 -0
  103. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/b01_q10_map_parser.py +0 -0
  104. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/b01_q10_overlays.py +0 -0
  105. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/map_parser.py +0 -0
  106. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/proto/__init__.py +0 -0
  107. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/proto/b01_scmap.proto +0 -0
  108. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/map/proto/b01_scmap_pb2.py +0 -0
  109. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/mqtt/__init__.py +0 -0
  110. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/mqtt/health_manager.py +0 -0
  111. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/mqtt/roborock_session.py +0 -0
  112. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/mqtt/session.py +0 -0
  113. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/protocol.py +0 -0
  114. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/protocols/__init__.py +0 -0
  115. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/protocols/a01_protocol.py +0 -0
  116. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/protocols/b01_q10_protocol.py +0 -0
  117. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/protocols/b01_q7_protocol.py +0 -0
  118. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/protocols/v1_protocol.py +0 -0
  119. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/py.typed +0 -0
  120. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/roborock_typing.py +0 -0
  121. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/testing/__init__.py +0 -0
  122. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/testing/v1_simulator.py +0 -0
  123. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/util.py +0 -0
  124. {python_roborock-5.28.0 → python_roborock-5.29.0}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 5.28.0
3
+ Version: 5.29.0
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Project-URL: Repository, https://github.com/python-roborock/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 = "5.28.0"
3
+ version = "5.29.0"
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"
@@ -675,6 +675,7 @@ class RoborockDockTypeCode(RoborockEnum):
675
675
  type_27_dock = 27
676
676
  k1c_lite_dock = 28
677
677
  shell_2e_lite_dock = 30
678
+ shell_2e_heat_dock = 40
678
679
 
679
680
 
680
681
  class RoborockDockDustCollectionModeCode(RoborockEnum):
@@ -674,6 +674,7 @@ _ONLY_X_SERIES_DOCK_TYPES = {
674
674
 
675
675
  _CLEAN_FLUID_AUTO_DELIVERY_DOCK_TYPES = {
676
676
  RoborockDockTypeCode.o4_dock,
677
+ RoborockDockTypeCode.o5_dock,
677
678
  RoborockDockTypeCode.pearl_plus_dock,
678
679
  RoborockDockTypeCode.shell_3s_dock,
679
680
  RoborockDockTypeCode.o6_dock,
@@ -681,7 +682,6 @@ _CLEAN_FLUID_AUTO_DELIVERY_DOCK_TYPES = {
681
682
  }
682
683
 
683
684
  _CLEAN_FLUID_AUTO_DELIVERY_AM_DOCK_TYPES = {
684
- RoborockDockTypeCode.o5_dock,
685
685
  RoborockDockTypeCode.shell_2s_dock,
686
686
  RoborockDockTypeCode.shell_3_dock,
687
687
  RoborockDockTypeCode.shell_2c_dock,
@@ -734,9 +734,11 @@ _WATER_UPDOWN_DRAIN_AM_DOCK_TYPES = {
734
734
  RoborockDockTypeCode.shell_2c_dock,
735
735
  RoborockDockTypeCode.shell_3s_dock,
736
736
  RoborockDockTypeCode.k1_dock,
737
+ RoborockDockTypeCode.k1s_dock,
737
738
  RoborockDockTypeCode.o6_dock,
738
739
  RoborockDockTypeCode.shell_e_dock,
739
740
  RoborockDockTypeCode.shell_2e_dock,
741
+ RoborockDockTypeCode.shell_3c_dock,
740
742
  }
741
743
 
742
744
  _HATCH_DOOR_DOCK_COOL_FAN_DOCK_TYPES = {
@@ -274,6 +274,9 @@ ROBOROCK_DATA_CONSUMABLE_PROTOCOL = [
274
274
  ]
275
275
 
276
276
 
277
+ MAX_PAYLOAD_REPR_LEN = 1024
278
+
279
+
277
280
  @dataclass
278
281
  class RoborockMessage:
279
282
  protocol: RoborockMessageProtocol
@@ -282,3 +285,18 @@ class RoborockMessage:
282
285
  version: bytes = b"1.0"
283
286
  random: int = field(default_factory=lambda: get_next_int(10000, 99999))
284
287
  timestamp: int = field(default_factory=lambda: get_timestamp())
288
+
289
+ def __repr__(self) -> str:
290
+ payload_repr = "None"
291
+ if self.payload is not None:
292
+ if isinstance(self.payload, (bytes, bytearray, str)) and len(self.payload) > MAX_PAYLOAD_REPR_LEN:
293
+ r = repr(self.payload[:MAX_PAYLOAD_REPR_LEN])
294
+ quote = r[-1]
295
+ payload_repr = f"{r[:-1]}...{quote} (length: {len(self.payload)})"
296
+ else:
297
+ payload_repr = repr(self.payload)
298
+ return (
299
+ f"RoborockMessage(protocol={self.protocol}, payload={payload_repr}, "
300
+ f"seq={self.seq}, version={self.version!r}, random={self.random}, "
301
+ f"timestamp={self.timestamp})"
302
+ )
@@ -5,7 +5,7 @@ subscription, and publishing logic at the message boundary. It acts as an
5
5
  in-memory replacement for `MqttChannel` and `LocalChannel` during testing.
6
6
  """
7
7
 
8
- from collections.abc import Callable
8
+ from collections.abc import Awaitable, Callable
9
9
  from typing import Any
10
10
  from unittest.mock import AsyncMock, MagicMock
11
11
 
@@ -37,6 +37,9 @@ class FakeChannel(Channel):
37
37
  publish (useful for low-level RPC request/response testing).
38
38
  - **Push unsolicited messages**: Call ``channel.notify_subscribers(msg)``
39
39
  to simulate the device broadcasting a state change.
40
+ - **Intercept published messages**: Register a handler/callback via
41
+ ``channel.publish_handler = my_handler`` (e.g. stateful simulator)
42
+ to reactively process commands.
40
43
  """
41
44
 
42
45
  subscribe: Any
@@ -49,6 +52,11 @@ class FakeChannel(Channel):
49
52
  self._is_connected = False
50
53
  self._is_local = is_local
51
54
 
55
+ # A callback to intercept published messages (e.g., bound simulator handler).
56
+ # Can be either synchronous or asynchronous: Callable[[RoborockMessage], Awaitable[Any]].
57
+ # By default, routes to self._default_publish_handler to handle the response_queue.
58
+ self.publish_handler: Callable[[RoborockMessage], Awaitable[Any]] | None = self._default_publish_handler
59
+
52
60
  # Set this to an exception instance to make the next publish raise it.
53
61
  # This is a convenience shortcut; callers can also replace
54
62
  # ``publish.side_effect`` directly for more control.
@@ -96,14 +104,16 @@ class FakeChannel(Channel):
96
104
  async def _publish(self, message: RoborockMessage) -> None:
97
105
  """Default publish implementation.
98
106
 
99
- Records the message in ``published_messages`` and, if
100
- ``response_queue`` is non-empty, pops the first response and
101
- delivers it to all current subscribers (simulating a
102
- request/response round-trip).
107
+ Records the message in ``published_messages`` and executes ``publish_handler``.
103
108
  """
104
109
  self.published_messages.append(message)
105
110
  if self.publish_side_effect:
106
111
  raise self.publish_side_effect
112
+ if self.publish_handler:
113
+ await self.publish_handler(message)
114
+
115
+ async def _default_publish_handler(self, message: RoborockMessage) -> None:
116
+ """Default handler that pops canned responses from response_queue."""
107
117
  if self.response_queue:
108
118
  response = self.response_queue.pop(0)
109
119
  self.notify_subscribers(response)
@@ -124,3 +134,15 @@ class FakeChannel(Channel):
124
134
  """
125
135
  for subscriber in list(self.subscribers):
126
136
  subscriber(message)
137
+
138
+ def inject_error(self, exception: Exception) -> None:
139
+ """Inject a transient failure into all channel operations (publish, subscribe, connect)."""
140
+ self.publish.side_effect = exception
141
+ self.subscribe.side_effect = exception
142
+ self.connect.side_effect = exception
143
+
144
+ def clear_error(self) -> None:
145
+ """Restore default success behaviors on all channel operations."""
146
+ self.publish.side_effect = self._publish
147
+ self.subscribe.side_effect = self._subscribe
148
+ self.connect.side_effect = self._connect
@@ -6,6 +6,7 @@ to simulate physical devices connected to the Roborock Cloud.
6
6
  """
7
7
 
8
8
  import contextlib
9
+ import datetime
9
10
  import re
10
11
  from typing import Any
11
12
  from unittest.mock import AsyncMock, patch
@@ -173,6 +174,44 @@ class FakeWebApiClient:
173
174
  callback=get_homes_callback,
174
175
  )
175
176
 
177
+ def get_default_home_data(self) -> HomeData:
178
+ """Construct the default HomeData using simulated devices registered in the cloud."""
179
+ devices = [d.device_info for d in self.cloud.simulated_devices.values()]
180
+ products = [d.product for d in self.cloud.simulated_devices.values()]
181
+ return HomeData(
182
+ id=self.cloud.home_id,
183
+ name=self.cloud.home_name,
184
+ devices=devices,
185
+ products=products,
186
+ received_devices=[],
187
+ rooms=[],
188
+ )
189
+
190
+ def set_homes_response(
191
+ self,
192
+ home_data: HomeData | None = None,
193
+ status: int = 200,
194
+ ) -> None:
195
+ """Easily set the faked response payload for the homes endpoint using a HomeData dataclass.
196
+
197
+ If home_data is None, it defaults to the active get_default_home_data() state.
198
+ """
199
+ self.homes_status = status
200
+ if status != 200:
201
+ self.homes_payload_override = None
202
+ return
203
+
204
+ if home_data is None:
205
+ home_data = self.get_default_home_data()
206
+
207
+ self.homes_payload_override = {
208
+ "api": None,
209
+ "code": 200,
210
+ "result": home_data.as_dict(),
211
+ "status": "ok",
212
+ "success": True,
213
+ }
214
+
176
215
 
177
216
  class FakeRoborockCloud:
178
217
  """A central state object representing the Roborock Cloud environment under test."""
@@ -234,7 +273,9 @@ class FakeRoborockCloud:
234
273
  with aioresponses() as mocked:
235
274
  self.web_api.mock_requests(mocked)
236
275
 
237
- # Patch Channel factories and rate limiters
276
+ # Mock sleep logic to speed up tests.
277
+ sleep_time = datetime.timedelta(seconds=0.001)
278
+ # Patch Channel factories, rate limiters, and backoff sleep intervals
238
279
  with (
239
280
  patch(
240
281
  "roborock.web_api.RoborockApiClient._login_limiter.try_acquire_async",
@@ -243,5 +284,7 @@ class FakeRoborockCloud:
243
284
  patch("roborock.web_api.RoborockApiClient._home_data_limiter.try_acquire", return_value=True),
244
285
  patch("roborock.devices.device_manager.create_v1_channel", side_effect=mock_create_v1_channel),
245
286
  patch("roborock.devices.device_manager.create_mqtt_channel", side_effect=mock_create_mqtt_channel),
287
+ patch("roborock.devices.device.MIN_BACKOFF_INTERVAL", sleep_time),
288
+ patch("roborock.devices.device.MAX_BACKOFF_INTERVAL", sleep_time),
246
289
  ):
247
290
  yield
@@ -73,26 +73,20 @@ class RoborockDeviceSimulator:
73
73
 
74
74
  # MQTT channel is always present — all protocols use it.
75
75
  self.mqtt_channel = FakeChannel(is_local=False)
76
- self.mqtt_channel.publish.side_effect = self._handle_mqtt_publish
76
+ self.mqtt_channel.publish_handler = self._handle_mqtt_publish
77
77
 
78
78
  # Local channel is only used by V1 devices. A01/B01 (MQTT-only)
79
79
  # simulators should pass has_local_channel=False.
80
80
  self.local_channel: FakeChannel | None = None
81
81
  if has_local_channel:
82
82
  self.local_channel = FakeChannel(is_local=True)
83
- self.local_channel.publish.side_effect = self._handle_local_publish
83
+ self.local_channel.publish_handler = self._handle_local_publish
84
84
 
85
85
  async def _handle_local_publish(self, message: RoborockMessage) -> None:
86
86
  assert self.local_channel is not None
87
- self.local_channel.published_messages.append(message)
88
- if self.local_channel.publish_side_effect:
89
- raise self.local_channel.publish_side_effect
90
87
  await self._handle_publish(message, self.local_channel)
91
88
 
92
89
  async def _handle_mqtt_publish(self, message: RoborockMessage) -> None:
93
- self.mqtt_channel.published_messages.append(message)
94
- if self.mqtt_channel.publish_side_effect:
95
- raise self.mqtt_channel.publish_side_effect
96
90
  await self._handle_publish(message, self.mqtt_channel)
97
91
 
98
92
  async def _handle_publish(self, message: RoborockMessage, channel: FakeChannel) -> None: