python-roborock 4.2.0__tar.gz → 4.2.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 (94) hide show
  1. {python_roborock-4.2.0 → python_roborock-4.2.1}/PKG-INFO +2 -2
  2. {python_roborock-4.2.0 → python_roborock-4.2.1}/pyproject.toml +2 -2
  3. python_roborock-4.2.1/roborock/devices/README.md +41 -0
  4. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/device.py +1 -1
  5. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/device_manager.py +3 -3
  6. python_roborock-4.2.1/roborock/devices/rpc/__init__.py +14 -0
  7. {python_roborock-4.2.0/roborock/devices → python_roborock-4.2.1/roborock/devices/rpc}/a01_channel.py +1 -2
  8. {python_roborock-4.2.0/roborock/devices → python_roborock-4.2.1/roborock/devices/rpc}/b01_q10_channel.py +1 -2
  9. {python_roborock-4.2.0/roborock/devices → python_roborock-4.2.1/roborock/devices/rpc}/b01_q7_channel.py +1 -2
  10. {python_roborock-4.2.0/roborock/devices → python_roborock-4.2.1/roborock/devices/rpc}/v1_channel.py +4 -5
  11. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/a01/__init__.py +2 -2
  12. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/b01/q10/__init__.py +2 -2
  13. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/b01/q10/command.py +2 -2
  14. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/b01/q7/__init__.py +2 -2
  15. python_roborock-4.2.1/roborock/devices/transport/__init__.py +8 -0
  16. {python_roborock-4.2.0/roborock/devices → python_roborock-4.2.1/roborock/devices/transport}/local_channel.py +2 -2
  17. python_roborock-4.2.0/roborock/devices/README.md +0 -669
  18. {python_roborock-4.2.0 → python_roborock-4.2.1}/.gitignore +0 -0
  19. {python_roborock-4.2.0 → python_roborock-4.2.1}/LICENSE +0 -0
  20. {python_roborock-4.2.0 → python_roborock-4.2.1}/README.md +0 -0
  21. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/__init__.py +0 -0
  22. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/broadcast_protocol.py +0 -0
  23. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/callbacks.py +0 -0
  24. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/cli.py +0 -0
  25. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/const.py +0 -0
  26. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/__init__.py +0 -0
  27. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/b01_q10/__init__.py +0 -0
  28. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  29. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  30. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/b01_q7/__init__.py +0 -0
  31. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  32. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  33. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/code_mappings.py +0 -0
  34. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/containers.py +0 -0
  35. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/dyad/__init__.py +0 -0
  36. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  37. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/dyad/dyad_containers.py +0 -0
  38. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/v1/__init__.py +0 -0
  39. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/v1/v1_clean_modes.py +0 -0
  40. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/v1/v1_code_mappings.py +0 -0
  41. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/v1/v1_containers.py +0 -0
  42. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/zeo/__init__.py +0 -0
  43. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  44. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/data/zeo/zeo_containers.py +0 -0
  45. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/device_features.py +0 -0
  46. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/__init__.py +0 -0
  47. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/cache.py +0 -0
  48. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/file_cache.py +0 -0
  49. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/__init__.py +0 -0
  50. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/b01/__init__.py +0 -0
  51. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/traits_mixin.py +0 -0
  52. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/__init__.py +0 -0
  53. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/child_lock.py +0 -0
  54. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/clean_summary.py +0 -0
  55. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/command.py +0 -0
  56. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/common.py +0 -0
  57. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/consumeable.py +0 -0
  58. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/device_features.py +0 -0
  59. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  60. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  61. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  62. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/home.py +0 -0
  63. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/led_status.py +0 -0
  64. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/map_content.py +0 -0
  65. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/maps.py +0 -0
  66. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/network_info.py +0 -0
  67. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/rooms.py +0 -0
  68. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/routines.py +0 -0
  69. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  70. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/status.py +0 -0
  71. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  72. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/volume.py +0 -0
  73. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  74. {python_roborock-4.2.0/roborock/devices → python_roborock-4.2.1/roborock/devices/transport}/channel.py +0 -0
  75. {python_roborock-4.2.0/roborock/devices → python_roborock-4.2.1/roborock/devices/transport}/mqtt_channel.py +0 -0
  76. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/diagnostics.py +0 -0
  77. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/exceptions.py +0 -0
  78. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/map/__init__.py +0 -0
  79. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/map/map_parser.py +0 -0
  80. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/mqtt/__init__.py +0 -0
  81. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/mqtt/health_manager.py +0 -0
  82. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/mqtt/roborock_session.py +0 -0
  83. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/mqtt/session.py +0 -0
  84. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/protocol.py +0 -0
  85. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/protocols/__init__.py +0 -0
  86. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/protocols/a01_protocol.py +0 -0
  87. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/protocols/b01_q10_protocol.py +0 -0
  88. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/protocols/b01_q7_protocol.py +0 -0
  89. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/protocols/v1_protocol.py +0 -0
  90. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/py.typed +0 -0
  91. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/roborock_message.py +0 -0
  92. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/roborock_typing.py +0 -0
  93. {python_roborock-4.2.0 → python_roborock-4.2.1}/roborock/util.py +0 -0
  94. {python_roborock-4.2.0 → python_roborock-4.2.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.2.0
3
+ Version: 4.2.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/
@@ -16,7 +16,7 @@ Classifier: Operating System :: OS Independent
16
16
  Classifier: Topic :: Software Development :: Libraries
17
17
  Requires-Python: <4,>=3.11
18
18
  Requires-Dist: aiohttp<4,>=3.8.2
19
- Requires-Dist: aiomqtt<3,>=2.3.2
19
+ Requires-Dist: aiomqtt<3,>=2.5.0
20
20
  Requires-Dist: click-shell~=2.1
21
21
  Requires-Dist: click>=8
22
22
  Requires-Dist: construct<3,>=2.10.57
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-roborock"
3
- version = "4.2.0"
3
+ version = "4.2.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"
@@ -27,7 +27,7 @@ dependencies = [
27
27
  "construct>=2.10.57,<3",
28
28
  "vacuum-map-parser-roborock",
29
29
  "pyrate-limiter>=3.7.0,<4",
30
- "aiomqtt>=2.3.2,<3",
30
+ "aiomqtt>=2.5.0,<3",
31
31
  "click-shell~=2.1",
32
32
  ]
33
33
 
@@ -0,0 +1,41 @@
1
+ # Roborock Device Manager
2
+
3
+ This library provides a high-level interface for discovering and controlling Roborock devices. It abstracts the underlying communication protocols (MQTT, Local TCP) and provides a unified `DeviceManager` for interacting with your devices.
4
+
5
+ For internal architecture details, protocol specifications, and design documentation, please refer to [docs/DEVICES.md](https://github.com/python-roborock/python-roborock/docs/DEVICES.md).
6
+
7
+ ## Getting Started
8
+
9
+ ### Credentials
10
+
11
+ To connect to your devices, you first need to obtain your user data (including the `rriot` token) from the Roborock Cloud. This is handled via the `RoborockApiClient`.
12
+
13
+ ## Usage Guide
14
+
15
+ The core entry point for the library is the `DeviceManager`. It handles:
16
+ 1. **Device Discovery**: Fetching the list of devices associated with your account.
17
+ 2. **Connection Management**: Automatically determining the best connection method (Local vs MQTT) and protocol version (V1 vs A01/B01).
18
+ 3. **Command Execution**: Sending commands and query status.
19
+
20
+ ### Example
21
+
22
+ See [examples/example.py](https://github.com/python-roborock/python-roborock/examples/example.py) for a complete example of how to login, create a device manager, and list the status of your vacuums.
23
+
24
+ ### Device Properties
25
+
26
+ Different devices support different property sets:
27
+
28
+ * **`v1_properties`**: Primarily for Vacuum Robots (S7, S8, Q5, etc.). Supports traits like `status`, `consumables`, `fan_power`, `water_box`.
29
+ * **`a01_properties`**: For Washer/Dryers and handheld Wet/Dry Vacuums (Dyad, Zeo) that use another newer protocol.
30
+ * **`b01_q7_properties`** and **`b01_q10_properties`**: For newer Vacuum/Mop devices using newer protocol instead of v1.
31
+
32
+ You can check if a property set is available by checking if the property on the device object is not `None` (e.g. `if device.v1_properties:`).
33
+
34
+ ### Caching
35
+
36
+ Use `FileCache` or your own `Cache` implementation to persist:
37
+ - `HomeData`: The list of your home's rooms and devices.
38
+ - `NetworkingInfo`: Device IP addresses and tokens.
39
+ - `Device Capabilities`: What features your specific model supports.
40
+
41
+ This speeds up startup time and reduces load on the Roborock cloud APIs.
@@ -18,9 +18,9 @@ from roborock.exceptions import RoborockException
18
18
  from roborock.roborock_message import RoborockMessage
19
19
  from roborock.util import RoborockLoggerAdapter
20
20
 
21
- from .channel import Channel
22
21
  from .traits import Trait
23
22
  from .traits.traits_mixin import TraitsMixin
23
+ from .transport.channel import Channel
24
24
 
25
25
  _LOGGER = logging.getLogger(__name__)
26
26
 
@@ -25,10 +25,10 @@ from roborock.protocol import create_mqtt_params
25
25
  from roborock.web_api import RoborockApiClient, UserWebApiClient
26
26
 
27
27
  from .cache import Cache, DeviceCache, NoCache
28
- from .channel import Channel
29
- from .mqtt_channel import create_mqtt_channel
28
+ from .rpc.v1_channel import create_v1_channel
30
29
  from .traits import Trait, a01, b01, v1
31
- from .v1_channel import create_v1_channel
30
+ from .transport.channel import Channel
31
+ from .transport.mqtt_channel import create_mqtt_channel
32
32
 
33
33
  _LOGGER = logging.getLogger(__name__)
34
34
 
@@ -0,0 +1,14 @@
1
+ """Module for sending device specific commands to Roborock devices.
2
+
3
+ This module provides a application-level interface for sending commands to Roborock
4
+ devices. These modules can be used by traits (higher level APIs) to send commands.
5
+
6
+ Each module may contain details that are common across all traits, and may depend
7
+ on the transport level modules (e.g. MQTT, Local device) for issuing the
8
+ commands.
9
+
10
+ The lowest level protocol encoding is handled in `roborock.protocols` which
11
+ have no dependencies on the transport level modules.
12
+ """
13
+
14
+ __all__: list[str] = []
@@ -5,6 +5,7 @@ import logging
5
5
  from collections.abc import Callable
6
6
  from typing import Any, overload
7
7
 
8
+ from roborock.devices.transport.mqtt_channel import MqttChannel
8
9
  from roborock.exceptions import RoborockException
9
10
  from roborock.protocols.a01_protocol import (
10
11
  decode_rpc_response,
@@ -16,8 +17,6 @@ from roborock.roborock_message import (
16
17
  RoborockZeoProtocol,
17
18
  )
18
19
 
19
- from .mqtt_channel import MqttChannel
20
-
21
20
  _LOGGER = logging.getLogger(__name__)
22
21
  _TIMEOUT = 10.0
23
22
 
@@ -5,14 +5,13 @@ from __future__ import annotations
5
5
  import logging
6
6
 
7
7
  from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
8
+ from roborock.devices.transport.mqtt_channel import MqttChannel
8
9
  from roborock.exceptions import RoborockException
9
10
  from roborock.protocols.b01_q10_protocol import (
10
11
  ParamsType,
11
12
  encode_mqtt_payload,
12
13
  )
13
14
 
14
- from .mqtt_channel import MqttChannel
15
-
16
15
  _LOGGER = logging.getLogger(__name__)
17
16
 
18
17
 
@@ -7,6 +7,7 @@ import json
7
7
  import logging
8
8
  from typing import Any
9
9
 
10
+ from roborock.devices.transport.mqtt_channel import MqttChannel
10
11
  from roborock.exceptions import RoborockException
11
12
  from roborock.protocols.b01_q7_protocol import (
12
13
  Q7RequestMessage,
@@ -15,8 +16,6 @@ from roborock.protocols.b01_q7_protocol import (
15
16
  )
16
17
  from roborock.roborock_message import RoborockMessage
17
18
 
18
- from .mqtt_channel import MqttChannel
19
-
20
19
  _LOGGER = logging.getLogger(__name__)
21
20
  _TIMEOUT = 10.0
22
21
 
@@ -12,6 +12,10 @@ from dataclasses import dataclass
12
12
  from typing import Any, TypeVar
13
13
 
14
14
  from roborock.data import HomeDataDevice, NetworkInfo, RoborockBase, UserData
15
+ from roborock.devices.cache import DeviceCache
16
+ from roborock.devices.transport.channel import Channel
17
+ from roborock.devices.transport.local_channel import LocalChannel, LocalSession, create_local_session
18
+ from roborock.devices.transport.mqtt_channel import MqttChannel
15
19
  from roborock.exceptions import RoborockException
16
20
  from roborock.mqtt.health_manager import HealthManager
17
21
  from roborock.mqtt.session import MqttParams, MqttSession
@@ -32,11 +36,6 @@ from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
32
36
  from roborock.roborock_typing import RoborockCommand
33
37
  from roborock.util import RoborockLoggerAdapter
34
38
 
35
- from .cache import DeviceCache
36
- from .channel import Channel
37
- from .local_channel import LocalChannel, LocalSession, create_local_session
38
- from .mqtt_channel import MqttChannel
39
-
40
39
  _LOGGER = logging.getLogger(__name__)
41
40
 
42
41
  __all__ = [
@@ -48,9 +48,9 @@ from roborock.data.zeo.zeo_code_mappings import (
48
48
  ZeoState,
49
49
  ZeoTemperature,
50
50
  )
51
- from roborock.devices.a01_channel import send_decoded_command
52
- from roborock.devices.mqtt_channel import MqttChannel
51
+ from roborock.devices.rpc.a01_channel import send_decoded_command
53
52
  from roborock.devices.traits import Trait
53
+ from roborock.devices.transport.mqtt_channel import MqttChannel
54
54
  from roborock.roborock_message import RoborockDyadDataProtocol, RoborockZeoProtocol
55
55
 
56
56
  __init__ = [
@@ -2,9 +2,9 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
- from roborock.devices.b01_q7_channel import send_decoded_command
6
- from roborock.devices.mqtt_channel import MqttChannel
5
+ from roborock.devices.rpc.b01_q7_channel import send_decoded_command
7
6
  from roborock.devices.traits import Trait
7
+ from roborock.devices.transport.mqtt_channel import MqttChannel
8
8
 
9
9
  from .command import CommandTrait
10
10
 
@@ -1,8 +1,8 @@
1
1
  from typing import Any
2
2
 
3
3
  from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
4
- from roborock.devices.b01_q10_channel import send_command
5
- from roborock.devices.mqtt_channel import MqttChannel
4
+ from roborock.devices.rpc.b01_q10_channel import send_command
5
+ from roborock.devices.transport.mqtt_channel import MqttChannel
6
6
  from roborock.protocols.b01_q10_protocol import ParamsType
7
7
 
8
8
 
@@ -10,9 +10,9 @@ from roborock.data.b01_q7.b01_q7_code_mappings import (
10
10
  SCWindMapping,
11
11
  WaterLevelMapping,
12
12
  )
13
- from roborock.devices.b01_q7_channel import send_decoded_command
14
- from roborock.devices.mqtt_channel import MqttChannel
13
+ from roborock.devices.rpc.b01_q7_channel import send_decoded_command
15
14
  from roborock.devices.traits import Trait
15
+ from roborock.devices.transport.mqtt_channel import MqttChannel
16
16
  from roborock.protocols.b01_q7_protocol import CommandType, ParamsType, Q7RequestMessage
17
17
  from roborock.roborock_message import RoborockB01Props
18
18
  from roborock.roborock_typing import RoborockB01Q7Methods
@@ -0,0 +1,8 @@
1
+ """Module for handling network connections to Roborock devices.
2
+
3
+ This is used internally by the device manager for creating connections to
4
+ Roborock devices. These modules contain common code, not specific to a
5
+ particular device or application level protocol.
6
+ """
7
+
8
+ __all__: list[str] = []
@@ -8,10 +8,10 @@ from dataclasses import dataclass
8
8
  from roborock.callbacks import CallbackList, decoder_callback
9
9
  from roborock.exceptions import RoborockConnectionException, RoborockException
10
10
  from roborock.protocol import create_local_decoder, create_local_encoder
11
+ from roborock.protocols.v1_protocol import LocalProtocolVersion
11
12
  from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
13
+ from roborock.util import RoborockLoggerAdapter, get_next_int
12
14
 
13
- from ..protocols.v1_protocol import LocalProtocolVersion
14
- from ..util import RoborockLoggerAdapter, get_next_int
15
15
  from .channel import Channel
16
16
 
17
17
  _LOGGER = logging.getLogger(__name__)
@@ -1,669 +0,0 @@
1
- # Roborock Devices & Discovery
2
-
3
- The devices module provides functionality to discover Roborock devices on the
4
- network. This section documents the full lifecycle of device discovery across
5
- Cloud and Network.
6
-
7
- ## Usage TL;DR
8
-
9
- * **Discovery**: Use `roborock.devices.device_manager.DeviceManager` to get device instances.
10
- * Call `create_device_manager(user_params)` then `await device_manager.get_devices()`.
11
- * **Control**:
12
- * **Vacuums (V1)**: Use `device.v1_properties` to access traits like `status` or `consumables`.
13
- * Call `await trait.refresh()` to update state.
14
- * Use `device.v1_properties.command.send()` for raw commands (start/stop).
15
- * **Washers (A01)**: Use `device.a01_properties` for Dyad/Zeo devices.
16
- * Use `await device.a01_properties.query_values([...])` to get state.
17
- * Use `await device.a01_properties.set_value(protocol, value)` to control.
18
-
19
- ## Background: Understanding Device Protocols
20
-
21
- **The library supports three device protocol versions, each with different capabilities:**
22
-
23
- ### Protocol Summary
24
-
25
- | Protocol | Device Examples | MQTT | Local TCP | Channel Type | Notes |
26
- |----------|----------------|------|-----------|--------------|-------|
27
- | **V1** (`pv=1.0`) | Most vacuum robots (S7, S8, Q5, Q7, etc.) | ✅ | ✅ | `V1Channel` with `RpcChannel` | Prefers local, falls back to MQTT |
28
- | **A01** (`pv=A01`) | Dyad, Zeo washers | ✅ | ❌ | `MqttChannel` + helpers | MQTT only, DPS protocol |
29
- | **B01** (`pv=B01`) | Some newer models | ✅ | ❌ | `MqttChannel` + helpers | MQTT only, DPS protocol |
30
-
31
- **Key Point:** The `DeviceManager` automatically detects the protocol version and creates the appropriate channel type. You don't need to handle this manually.
32
-
33
- ## Internal Architecture
34
-
35
- The library is organized into distinct layers, each with a specific responsibility. **Different device protocols use different channel implementations:**
36
-
37
- ```mermaid
38
- graph TB
39
- subgraph "Application Layer"
40
- User[Application Code]
41
- end
42
-
43
- subgraph "Device Management Layer"
44
- DM[DeviceManager<br/>Detects protocol version]
45
- end
46
-
47
- subgraph "Device Types by Protocol"
48
- V1Dev[V1 Devices<br/>pv=1.0<br/>Most vacuums]
49
- A01Dev[A01 Devices<br/>pv=A01<br/>Dyad, Zeo]
50
- B01Dev[B01 Devices<br/>pv=B01<br/>Some models]
51
- end
52
-
53
- subgraph "Traits Layer"
54
- V1Traits[V1 Traits<br/>Clean, Map, etc.]
55
- A01Traits[A01 Traits<br/>DPS-based]
56
- B01Traits[B01 Traits<br/>DPS-based]
57
- end
58
-
59
- subgraph "Channel Layer"
60
- V1C[V1Channel<br/>MQTT + Local]
61
- A01C[A01 send_decoded_command<br/>MQTT only]
62
- B01C[B01 send_decoded_command<br/>MQTT only]
63
- RPC[RpcChannel<br/>Multi-strategy]
64
- MC[MqttChannel<br/>Per-device wrapper]
65
- LC[LocalChannel<br/>TCP :58867]
66
- end
67
-
68
- subgraph "Session Layer"
69
- MS[MqttSession<br/>SHARED by all devices<br/>Idle timeout]
70
- LS[LocalSession<br/>Factory]
71
- end
72
-
73
- subgraph "Protocol Layer"
74
- V1P[V1 Protocol<br/>JSON RPC + AES]
75
- A01P[A01 Protocol<br/>DPS format]
76
- B01P[B01 Protocol<br/>DPS format]
77
- end
78
-
79
- subgraph "Transport Layer"
80
- MQTT[MQTT Broker<br/>Roborock Cloud]
81
- TCP[TCP Socket<br/>Direct to device]
82
- end
83
-
84
- User --> DM
85
- DM -->|pv=1.0| V1Dev
86
- DM -->|pv=A01| A01Dev
87
- DM -->|pv=B01| B01Dev
88
-
89
- V1Dev --> V1Traits
90
- A01Dev --> A01Traits
91
- B01Dev --> B01Traits
92
-
93
- V1Traits --> V1C
94
- A01Traits --> A01C
95
- B01Traits --> B01C
96
-
97
- V1C --> RPC
98
- RPC -->|Strategy 1| LC
99
- RPC -->|Strategy 2| MC
100
- A01C --> MC
101
- B01C --> MC
102
-
103
- MC --> MS
104
- LC --> LS
105
-
106
- MC --> V1P
107
- MC --> A01P
108
- MC --> B01P
109
- LC --> V1P
110
-
111
- MS --> MQTT
112
- LC --> TCP
113
- MQTT <--> TCP
114
-
115
- style User fill:#e1f5ff
116
- style DM fill:#fff4e1
117
- style V1C fill:#ffe1e1
118
- style RPC fill:#ffe1e1
119
- style MS fill:#e1ffe1
120
- style V1P fill:#f0e1ff
121
- style A01P fill:#f0e1ff
122
- style B01P fill:#f0e1ff
123
- ```
124
-
125
- ### Layer Responsibilities
126
-
127
- 1. **Device Management Layer**: Detects protocol version (`pv` field) and creates appropriate channels
128
- 2. **Device Types**: Different devices based on protocol version (V1, A01, B01)
129
- 3. **Traits Layer**: Protocol-specific device capabilities and commands
130
- 4. **Channel Layer**: Protocol-specific communication patterns
131
- - **V1**: Full RPC channel with local + MQTT fallback
132
- - **A01/B01**: Helper functions wrapping MqttChannel (MQTT only)
133
- - **MqttChannel**: Per-device wrapper that uses shared `MqttSession`
134
- 5. **Session Layer**: Connection pooling and subscription management
135
- - **MqttSession**: **Shared single connection** for all devices
136
- - **LocalSession**: Factory for creating device-specific local connections
137
- 6. **Protocol Layer**: Message encoding/decoding for different device versions
138
- 7. **Transport Layer**: Low-level MQTT and TCP communication
139
-
140
- **Important:** All `MqttChannel` instances share the same `MqttSession`, which maintains a single MQTT connection to the broker. This means:
141
- - Only one TCP connection to the MQTT broker regardless of device count
142
- - Subscription management is centralized with idle timeout optimization
143
- - All devices communicate through device-specific MQTT topics on the shared connection
144
-
145
- ### Protocol-Specific Architecture
146
-
147
- | Protocol | Channel Type | Local Support | RPC Strategy | Use Case |
148
- |----------|-------------|---------------|--------------|----------|
149
- | **V1** (`pv=1.0`) | `V1Channel` with `RpcChannel` | ✅ Yes | Multi-strategy (Local → MQTT) | Most vacuum robots |
150
- | **A01** (`pv=A01`) | `MqttChannel` + helpers | ❌ No | Direct MQTT | Dyad, Zeo washers |
151
- | **B01** (`pv=B01`) | `MqttChannel` + helpers | ❌ No | Direct MQTT | Some newer models |
152
-
153
- ## Account Setup Internals
154
-
155
- ### Login
156
-
157
- - Login can happen with either email and password or email and sending a code. We
158
- currently prefer email with sending a code -- however the roborock no longer
159
- supports this method of login. In the future we may want to migrate to password
160
- if this login method is no longer supported.
161
- - The Login API provides a `userData` object with information on connecting to the cloud APIs
162
- - This `rriot` data contains per-session information, unique each time you login.
163
- - This contains information used to connect to MQTT
164
- - You get an `-eu` suffix in the API URLs if you are in the eu and `-us` if you are in the us
165
-
166
- ## Home Data Internals
167
-
168
- The `HomeData` includes information about the various devices in the home. We use `v3`
169
- and it is notable that if devices don't show up in the `home_data` response it is likely
170
- that a newer version of the API should be used.
171
-
172
- - `products`: This is a list of all of the products you have on your account. These objects are always the same (i.e. a s7 maxv is always the exact same.)
173
- - It only shows the products for devices available on your account
174
- - `devices` and `received_devices`:
175
- - These both share the same objects, but one is for devices that have been shared with you and one is those that are on your account.
176
- - The big things here are (MOST are static):
177
- - `duid`: A unique identifier for your device (this is always the same i think)
178
- - `name`: The name of the device in your app
179
- - `local_key`: The local key that is needed for encoding and decoding messages for the device. This stays the same unless someone sets their vacuum back up.
180
- - `pv`: the protocol version (i.e. 1.0 or A1 or B1)
181
- - `product_id`: The id of the product from the above products list.
182
- - `device_status`: An initial status for some of the data we care about, though this changes on each update.
183
- - `rooms`: The rooms in the home.
184
- - This changes if the user adds a new room or changes its name.
185
- - We have to combine this with the room numbers from `GET_ROOM_MAPPING` on the device
186
- - There is another REST request `get_rooms` that will do the same thing.
187
- - Note: If we cache home_data, we likely need to use `get_rooms` to get rooms fresh
188
-
189
- ## Connection Implementation
190
-
191
- ### Connection Flow by Protocol
192
-
193
- The connection flow differs based on the device protocol version:
194
-
195
- #### V1 Devices (Most Vacuums) - MQTT + Local
196
-
197
- ```mermaid
198
- sequenceDiagram
199
- participant App as Application
200
- participant DM as DeviceManager
201
- participant V1C as V1Channel
202
- participant RPC as RpcChannel
203
- participant MC as MqttChannel
204
- participant LC as LocalChannel
205
- participant MS as MqttSession
206
- participant Broker as MQTT Broker
207
- participant Device as V1 Vacuum
208
-
209
- App->>DM: create_device_manager()
210
- DM->>MS: Create MQTT Session
211
- MS->>Broker: Connect
212
- Broker-->>MS: Connected
213
-
214
- App->>DM: get_devices()
215
- Note over DM: Detect pv=1.0
216
- DM->>V1C: Create V1Channel
217
- V1C->>MC: Create MqttChannel
218
- V1C->>LC: Create LocalChannel (deferred)
219
-
220
- Note over V1C: Subscribe to device topics
221
- V1C->>MC: subscribe()
222
- MC->>MS: subscribe(topic, callback)
223
- MS->>Broker: SUBSCRIBE
224
-
225
- Note over V1C: Fetch network info via MQTT
226
- V1C->>RPC: send_command(GET_NETWORK_INFO)
227
- RPC->>MC: publish(request)
228
- MC->>MS: publish(topic, message)
229
- MS->>Broker: PUBLISH
230
- Broker->>Device: Command
231
- Device->>Broker: Response
232
- Broker->>MS: Message
233
- MS->>MC: callback(message)
234
- MC->>RPC: decoded message
235
- RPC-->>V1C: NetworkInfo
236
-
237
- Note over V1C: Connect locally using IP
238
- V1C->>LC: connect()
239
- LC->>Device: TCP Connect :58867
240
- Device-->>LC: Connected
241
-
242
- Note over App: Commands prefer local
243
- App->>V1C: send_command(GET_STATUS)
244
- V1C->>RPC: send_command()
245
- RPC->>LC: publish(request) [Try local first]
246
- LC->>Device: Command via TCP
247
- Device->>LC: Response
248
- LC->>RPC: decoded message
249
- RPC-->>App: Status
250
- ```
251
-
252
- #### A01/B01 Devices (Dyad, Zeo) - MQTT Only
253
-
254
- ```mermaid
255
- sequenceDiagram
256
- participant App as Application
257
- participant DM as DeviceManager
258
- participant A01 as A01 Traits
259
- participant Helper as send_decoded_command
260
- participant MC as MqttChannel
261
- participant MS as MqttSession
262
- participant Broker as MQTT Broker
263
- participant Device as A01 Device
264
-
265
- App->>DM: create_device_manager()
266
- DM->>MS: Create MQTT Session
267
- MS->>Broker: Connect
268
- Broker-->>MS: Connected
269
-
270
- App->>DM: get_devices()
271
- Note over DM: Detect pv=A01
272
- DM->>MC: Create MqttChannel
273
- DM->>A01: Create A01 Traits
274
-
275
- Note over A01: Subscribe to device topics
276
- A01->>MC: subscribe()
277
- MC->>MS: subscribe(topic, callback)
278
- MS->>Broker: SUBSCRIBE
279
-
280
- Note over App: All commands via MQTT
281
- App->>A01: set_power(True)
282
- A01->>Helper: send_decoded_command()
283
- Helper->>MC: subscribe(find_response)
284
- Helper->>MC: publish(request)
285
- MC->>MS: publish(topic, message)
286
- MS->>Broker: PUBLISH
287
- Broker->>Device: Command
288
- Device->>Broker: Response
289
- Broker->>MS: Message
290
- MS->>MC: callback(message)
291
- MC->>Helper: decoded message
292
- Helper-->>App: Result
293
- ```
294
-
295
- ### Key Differences
296
-
297
- | Aspect | V1 Devices | A01/B01 Devices |
298
- |--------|------------|-----------------|
299
- | **Protocols** | V1 Protocol (JSON RPC) | DPS Protocol |
300
- | **Transports** | MQTT + Local TCP | MQTT only |
301
- | **Channel Type** | `V1Channel` with `RpcChannel` | `MqttChannel` with helpers |
302
- | **Local Support** | ✅ Yes, preferred | ❌ No |
303
- | **Fallback** | Local → MQTT | N/A |
304
- | **Connection** | Requires network info fetch | Direct MQTT |
305
- | **Examples** | Most vacuum robots | Dyad washers, Zeo models |
306
-
307
- ### MQTT Connection (All Devices)
308
-
309
- - Initial device information must be obtained from MQTT
310
- - For V1 devices, we set up the MQTT device connection before the local device connection
311
- - The `NetworkingInfo` needs to be fetched to get additional information about connecting to the device (e.g., Local IP Address)
312
- - This networking info can be cached to reduce network calls
313
- - MQTT is also the only way to get the device Map
314
- - Incoming and outgoing messages are decoded/encoded using the device `local_key`
315
- - For A01/B01 devices, MQTT is the only transport
316
-
317
- ### Local Connection (V1 Devices Only)
318
-
319
- - We use the `ip` from the `NetworkingInfo` to find the device
320
- - The local connection is preferred for improved latency and reducing load on the cloud servers to avoid rate limiting
321
- - Connections are made using a normal TCP socket on port `58867`
322
- - Incoming and outgoing messages are decoded/encoded using the device `local_key`
323
- - Messages received on the stream may be partially received, so we keep a running buffer as messages are partially decoded
324
- - **Not available for A01/B01 devices**
325
-
326
- ### RPC Pattern (V1 Devices)
327
-
328
- V1 devices use a publish/subscribe model for both MQTT and local connections, with an RPC abstraction on top:
329
-
330
- ```mermaid
331
- graph LR
332
- subgraph "RPC Layer"
333
- A[send_command] -->|1. Create request| B[Encoder]
334
- B -->|2. Subscribe for response| C[Channel.subscribe]
335
- B -->|3. Publish request| D[Channel.publish]
336
- C -->|4. Wait for match| E[find_response callback]
337
- E -->|5. Match request_id| F[Future.set_result]
338
- F -->|6. Return| G[Command Result]
339
- end
340
-
341
- subgraph "Channel Layer"
342
- C --> H[Subscription Map]
343
- D --> I[Transport]
344
- I --> J[Device]
345
- J --> K[Incoming Messages]
346
- K --> H
347
- H --> E
348
- end
349
- ```
350
-
351
- **Key Design Points:**
352
-
353
- 1. **Temporary Subscriptions**: Each RPC creates a temporary subscription that matches the request ID
354
- 2. **Subscription Reuse**: `MqttSession` keeps subscriptions alive for 60 seconds (or idle timeout) to enable reuse during command bursts
355
- 3. **Timeout Handling**: Commands timeout after 10 seconds if no response is received
356
- 4. **Multiple Strategies**: `V1Channel` tries local first, then falls back to MQTT if local fails
357
-
358
- ## Class Design & Components
359
-
360
- ### Current Architecture
361
-
362
- The current design separates concerns into distinct layers:
363
-
364
- ```mermaid
365
- classDiagram
366
- class Channel {
367
- <<abstract>>
368
- +subscribe(callback) Callable
369
- +publish(message)
370
- +is_connected() bool
371
- }
372
-
373
- class MqttChannel {
374
- -MqttSession session
375
- -duid: str
376
- -local_key: str
377
- +subscribe(callback)
378
- +publish(message)
379
- }
380
-
381
- class LocalChannel {
382
- -host: str
383
- -transport: Transport
384
- -local_key: str
385
- +connect()
386
- +subscribe(callback)
387
- +publish(message)
388
- +close()
389
- }
390
-
391
- class V1Channel {
392
- -MqttChannel mqtt_channel
393
- -LocalChannel local_channel
394
- -RpcChannel rpc_channel
395
- +send_command(method, params)
396
- +subscribe(callback)
397
- }
398
-
399
- class RpcChannel {
400
- -List~RpcStrategy~ strategies
401
- +send_command(method, params)
402
- }
403
-
404
- class RpcStrategy {
405
- +name: str
406
- +channel: Channel
407
- +encoder: Callable
408
- +decoder: Callable
409
- +health_manager: HealthManager
410
- }
411
-
412
- class MqttSession {
413
- -Client client
414
- -dict listeners
415
- -dict idle_timers
416
- +subscribe(topic, callback)
417
- +publish(topic, payload)
418
- +close()
419
- }
420
-
421
- Channel <|-- MqttChannel
422
- Channel <|-- LocalChannel
423
- Channel <|-- V1Channel
424
- MqttChannel --> MqttSession
425
- V1Channel --> MqttChannel
426
- V1Channel --> LocalChannel
427
- V1Channel --> RpcChannel
428
- RpcChannel --> RpcStrategy
429
- RpcStrategy --> Channel
430
- ```
431
-
432
- ### Key Components
433
-
434
- #### Channel Interface
435
-
436
- The `Channel` abstraction provides a uniform interface for both MQTT and local connections:
437
-
438
- - **`subscribe(callback)`**: Register a callback for incoming messages
439
- - **`publish(message)`**: Send a message to the device
440
- - **`is_connected`**: Check connection status
441
-
442
- This abstraction allows the RPC layer to work identically over both transports.
443
-
444
- #### MqttSession (Shared Across All Devices)
445
-
446
- The `MqttSession` manages a **single shared MQTT connection** for all devices:
447
-
448
- - **Single Connection**: Only one TCP connection to the MQTT broker, regardless of device count
449
- - **Per-Device Topics**: Each device communicates via its own MQTT topics (e.g., `rr/m/i/{user}/{username}/{duid}`)
450
- - **Subscription Pooling**: Multiple callbacks can subscribe to the same topic
451
- - **Idle Timeout**: Keeps subscriptions alive for 10 seconds after the last callback unsubscribes (enables reuse during command bursts)
452
- - **Reconnection**: Automatically reconnects and re-establishes all subscriptions on connection loss
453
- - **Thread-Safe**: Uses asyncio primitives for safe concurrent access
454
-
455
- **Efficiency**: Creating 5 devices means 5 `MqttChannel` instances but only 1 `MqttSession` and 1 MQTT broker connection.
456
-
457
- #### MqttChannel (Per-Device Wrapper)
458
-
459
- Each device gets its own `MqttChannel` instance that:
460
- - Wraps the shared `MqttSession`
461
- - Manages device-specific topics (publish to `rr/m/i/.../duid`, subscribe to `rr/m/o/.../duid`)
462
- - Handles protocol-specific encoding/decoding with the device's `local_key`
463
- - Provides the same `Channel` interface as `LocalChannel`
464
-
465
- #### RpcChannel with Multiple Strategies (V1 Only)
466
-
467
- The `RpcChannel` implements the request/response pattern over pub/sub channels and is **only used by V1 devices**:
468
-
469
- ```python
470
- # Example: V1Channel tries local first, then MQTT
471
- strategies = [
472
- RpcStrategy(name="local", channel=local_channel, ...),
473
- RpcStrategy(name="mqtt", channel=mqtt_channel, ...),
474
- ]
475
- rpc_channel = RpcChannel(strategies)
476
- ```
477
-
478
- For each V1 command:
479
- 1. Try the first strategy (local)
480
- 2. If it fails, try the next strategy (MQTT)
481
- 3. Return the first successful result
482
-
483
- **A01/B01 devices** don't use `RpcChannel`. Instead, they use helper functions (`send_decoded_command`) that directly wrap `MqttChannel`.
484
-
485
- #### Protocol-Specific Channel Architecture
486
-
487
- | Component | V1 Devices | A01/B01 Devices |
488
- |-----------|------------|-----------------|
489
- | **Channel Class** | `V1Channel` | `MqttChannel` directly |
490
- | **RPC Abstraction** | `RpcChannel` with strategies | Helper functions |
491
- | **Strategy Pattern** | ✅ Multi-strategy (Local → MQTT) | ❌ Direct MQTT only |
492
- | **Health Manager** | ✅ Tracks local/MQTT health | ❌ Not needed |
493
- | **Code Location** | `v1_channel.py` | `a01_channel.py`, `b01_q7_channel.py` |
494
-
495
- #### Health Management (V1 Only)
496
-
497
- Each V1 RPC strategy can have a `HealthManager` that tracks success/failure:
498
-
499
- - **Exponential Backoff**: After failures, wait before retrying
500
- - **Automatic Recovery**: Periodically attempt to restore failed connections
501
- - **Network Info Refresh**: Refresh local IP addresses after extended periods
502
-
503
- A01/B01 devices don't need health management since they only use MQTT (no fallback).
504
-
505
- ### Protocol Versions
506
-
507
- Different device models use different protocol versions:
508
-
509
- | Protocol | Devices | Encoding |
510
- |----------|---------|----------|
511
- | V1 | Most vacuum robots | JSON RPC with AES encryption |
512
- | A01 | Dyad, Zeo | DPS-based protocol |
513
- | B01 | Some newer models | DPS-based protocol |
514
- | L01 | Local protocol variant | Binary protocol negotiation |
515
-
516
- The protocol layer handles encoding/decoding transparently based on the device's `pv` field.
517
-
518
- ### Prior API Issues
519
-
520
- - Complex Inheritance Hierarchy: Multiple inheritance with classes like RoborockMqttClientV1 inheriting from both RoborockMqttClient and RoborockClientV1
521
-
522
- - Callback-Heavy Design: Heavy reliance on callbacks and listeners in RoborockClientV1.on_message_received and the ListenerModel system
523
-
524
- - Version Fragmentation: Separate v1 and A01 APIs with different patterns and abstractions
525
-
526
- - Mixed Concerns: Classes handle both communication protocols (MQTT/local) and device-specific logic
527
-
528
- - Complex Caching: The AttributeCache system with RepeatableTask adds complexity
529
-
530
- - Manual Connection Management: Users need to manually set up both MQTT and local clients as shown in the README example
531
-
532
- ### Design Goals
533
-
534
- - Prefer a single unified client that handles both MQTT and local connections internally.
535
-
536
- - Home and device discovery (fetching home data and device setup) will be behind a single API.
537
-
538
- - Asyncio First: Everything should be asyncio as much as possible, with fewer callbacks.
539
-
540
- - The clients should be working in terms of devices. We need to detect capabilities for each device and not expose details about API versions.
541
-
542
- - Reliability issues: The current Home Assistant integration has issues with reliability and needs to be simplified. It may be that there are bugs with the exception handling and it's too heavy on the cloud APIs and could benefit from more seamless caching.
543
-
544
- ### Migration from Legacy APIs
545
-
546
- The library previously had:
547
- - Separate `RoborockMqttClientV1` and `RoborockLocalClientV1` classes
548
- - Manual connection management
549
- - Callback-heavy design with `on_message_received`
550
- - Complex inheritance hierarchies
551
-
552
- The new design:
553
- - `DeviceManager` handles all connection management
554
- - `V1Channel` automatically manages both MQTT and local
555
- - Asyncio-first with minimal callbacks
556
- - Clear separation of concerns through layers
557
- - Users work with devices, not raw clients
558
-
559
-
560
- ## Implementation Details
561
-
562
- ### Code Organization
563
-
564
- ```
565
- roborock/
566
- ├── devices/ # Device management and channels
567
- │ ├── device_manager.py # High-level device lifecycle
568
- │ ├── channel.py # Base Channel interface
569
- │ ├── mqtt_channel.py # MQTT channel implementation
570
- │ ├── local_channel.py # Local TCP channel implementation
571
- │ ├── v1_channel.py # V1 protocol channel with RPC strategies
572
- │ ├── a01_channel.py # A01 protocol helpers
573
- │ ├── b01_q7_channel.py # B01 Q7 protocol helpers
574
- │ └── traits/ # Device-specific command traits
575
- │ └── v1/ # V1 device traits
576
- │ ├── __init__.py # Trait initialization
577
- │ ├── clean.py # Cleaning commands
578
- │ ├── map.py # Map management
579
- │ └── ...
580
- ├── mqtt/ # MQTT session management
581
- │ ├── session.py # Base session interface
582
- │ └── roborock_session.py # MQTT session with idle timeout
583
- ├── protocols/ # Protocol encoders/decoders
584
- │ ├── v1_protocol.py # V1 JSON RPC protocol
585
- │ ├── a01_protocol.py # A01 protocol
586
- │ ├── b01_q7_protocol.py # B01 Q7 protocol
587
- │ └── ...
588
- └── data/ # Data containers and mappings
589
- ├── containers.py # Status, HomeData, etc.
590
- └── v1/ # V1-specific data structures
591
- ```
592
-
593
- ### Threading Model
594
-
595
- The library is **asyncio-only** with no threads:
596
-
597
- - All I/O is non-blocking using `asyncio`
598
- - No thread synchronization needed (single event loop)
599
- - Callbacks are executed in the event loop
600
- - Use `asyncio.create_task()` for background work
601
-
602
- ### Error Handling
603
-
604
- ```mermaid
605
- graph TD
606
- A[send_command] --> B{Local Available?}
607
- B -->|Yes| C[Try Local]
608
- B -->|No| D[Try MQTT]
609
- C --> E{Success?}
610
- E -->|Yes| F[Return Result]
611
- E -->|No| G{Timeout?}
612
- G -->|Yes| H[Update Health Manager]
613
- H --> D
614
- G -->|No| I{Connection Error?}
615
- I -->|Yes| J[Mark Connection Failed]
616
- J --> D
617
- I -->|No| D
618
- D --> K{Success?}
619
- K -->|Yes| F
620
- K -->|No| L[Raise RoborockException]
621
- ```
622
-
623
- **Exception Types:**
624
-
625
- - `RoborockException`: Base exception for all library errors
626
- - `RoborockConnectionException`: Connection-related failures
627
- - `RoborockTimeout`: Command timeout (10 seconds)
628
-
629
- ### Caching Strategy
630
-
631
- To reduce API calls and improve reliability:
632
-
633
- 1. **Home Data**: Cached on disk, refreshed periodically
634
- 2. **Network Info**: Cached for 12 hours
635
- 3. **Device Capabilities**: Detected once and cached
636
- 4. **MQTT Subscriptions**: Kept alive for 60 seconds (idle timeout)
637
-
638
- ### Testing
639
-
640
- Test structure mirrors the python module structure. For example,
641
- the module `roborock.devices.traits.v1.maps` is tested in the file
642
- `tests/devices/traits/v1/test_maps.py`. Each test file corresponds to a python
643
- module.
644
-
645
- The test suite uses mocking extensively to avoid real devices:
646
-
647
- - `Mock` and `AsyncMock` for channels and sessions
648
- - Fake message generators (`mqtt_packet.gen_publish()`)
649
- - Snapshot testing for complex data structures
650
- - Time-based tests use small timeouts (10-50ms) for speed
651
-
652
-
653
- Example test structure:
654
- ```python
655
- @pytest.fixture
656
- def mock_mqtt_channel():
657
- """Mock MQTT channel that simulates responses."""
658
- channel = AsyncMock(spec=MqttChannel)
659
- channel.response_queue = []
660
-
661
- async def publish_side_effect(message):
662
- # Simulate device response
663
- if channel.response_queue:
664
- response = channel.response_queue.pop(0)
665
- await callback(response)
666
-
667
- channel.publish.side_effect = publish_side_effect
668
- return channel
669
- ```
File without changes