python-roborock 3.17.0__tar.gz → 3.19.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 (101) hide show
  1. {python_roborock-3.17.0 → python_roborock-3.19.0}/PKG-INFO +29 -19
  2. python_roborock-3.19.0/README.md +89 -0
  3. {python_roborock-3.17.0 → python_roborock-3.19.0}/pyproject.toml +1 -1
  4. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q7/b01_q7_containers.py +4 -4
  5. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/code_mappings.py +2 -2
  6. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/v1/v1_containers.py +1 -1
  7. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/README.md +18 -6
  8. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/device_manager.py +25 -6
  9. python_roborock-3.19.0/roborock/devices/traits/__init__.py +28 -0
  10. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/a01/__init__.py +21 -0
  11. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/__init__.py +51 -11
  12. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/command.py +11 -1
  13. python_roborock-3.19.0/roborock/diagnostics.py +84 -0
  14. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/mqtt/roborock_session.py +37 -16
  15. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/mqtt/session.py +10 -1
  16. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocols/v1_protocol.py +2 -4
  17. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/roborock_message.py +2 -4
  18. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/roborock_typing.py +6 -0
  19. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/util.py +10 -0
  20. python_roborock-3.17.0/README.md +0 -79
  21. python_roborock-3.17.0/roborock/devices/traits/__init__.py +0 -15
  22. {python_roborock-3.17.0 → python_roborock-3.19.0}/.gitignore +0 -0
  23. {python_roborock-3.17.0 → python_roborock-3.19.0}/LICENSE +0 -0
  24. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/__init__.py +0 -0
  25. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/api.py +0 -0
  26. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/broadcast_protocol.py +0 -0
  27. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/callbacks.py +0 -0
  28. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/cli.py +0 -0
  29. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/cloud_api.py +0 -0
  30. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/command_cache.py +0 -0
  31. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/const.py +0 -0
  32. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/__init__.py +0 -0
  33. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q10/__init__.py +0 -0
  34. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  35. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  36. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q7/__init__.py +0 -0
  37. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  38. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/containers.py +0 -0
  39. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/dyad/__init__.py +0 -0
  40. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  41. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/dyad/dyad_containers.py +0 -0
  42. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/v1/__init__.py +0 -0
  43. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/v1/v1_clean_modes.py +0 -0
  44. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/v1/v1_code_mappings.py +0 -0
  45. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/zeo/__init__.py +0 -0
  46. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  47. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/zeo/zeo_containers.py +0 -0
  48. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/device_features.py +0 -0
  49. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/__init__.py +0 -0
  50. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/a01_channel.py +0 -0
  51. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/b01_channel.py +0 -0
  52. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/cache.py +0 -0
  53. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/channel.py +0 -0
  54. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/device.py +0 -0
  55. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/file_cache.py +0 -0
  56. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/local_channel.py +0 -0
  57. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/mqtt_channel.py +0 -0
  58. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/b01/__init__.py +0 -0
  59. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
  60. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
  61. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/traits_mixin.py +0 -0
  62. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/child_lock.py +0 -0
  63. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
  64. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/common.py +0 -0
  65. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/consumeable.py +0 -0
  66. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/device_features.py +0 -0
  67. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  68. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  69. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  70. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/home.py +0 -0
  71. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/led_status.py +0 -0
  72. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/map_content.py +0 -0
  73. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/maps.py +0 -0
  74. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/network_info.py +0 -0
  75. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/rooms.py +0 -0
  76. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/routines.py +0 -0
  77. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  78. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/status.py +0 -0
  79. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  80. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/volume.py +0 -0
  81. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  82. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/v1_channel.py +0 -0
  83. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/exceptions.py +0 -0
  84. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/map/__init__.py +0 -0
  85. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/map/map_parser.py +0 -0
  86. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/mqtt/__init__.py +0 -0
  87. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/mqtt/health_manager.py +0 -0
  88. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocol.py +0 -0
  89. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocols/__init__.py +0 -0
  90. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocols/a01_protocol.py +0 -0
  91. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocols/b01_protocol.py +0 -0
  92. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/py.typed +0 -0
  93. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/roborock_future.py +0 -0
  94. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_1_apis/__init__.py +0 -0
  95. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  96. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
  97. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  98. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_a01_apis/__init__.py +0 -0
  99. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  100. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
  101. {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 3.17.0
3
+ Version: 3.19.0
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/
@@ -49,11 +49,13 @@ Install this via pip (or your favourite package manager):
49
49
 
50
50
  `pip install python-roborock`
51
51
 
52
- ## Functionality
52
+ ## Example Usage
53
53
 
54
- You can see all of the commands supported [here](https://python-roborock.readthedocs.io/en/latest/api_commands.html)
54
+ See [examples/example.py](examples/example.py) for a more full featured example,
55
+ or the [API documentation](https://python-roborock.github.io/python-roborock/)
56
+ for more details.
55
57
 
56
- ## Example Usage
58
+ Here is a basic example:
57
59
 
58
60
  ```python
59
61
  import asyncio
@@ -63,28 +65,28 @@ from roborock.devices.device_manager import create_device_manager, UserParams
63
65
 
64
66
 
65
67
  async def main():
66
- web_api = RoborockApiClient(username="youremailhere")
67
- # Login via your password
68
- user_data = await web_api.pass_login(password="pass_here")
69
- # Or login via a code
68
+ email_address = "youremailhere@example.com"
69
+ web_api = RoborockApiClient(username=email_address)
70
+ # Send a login code to the above email address
70
71
  await web_api.request_code()
72
+ # Prompt the user to enter the code
71
73
  code = input("What is the code?")
72
74
  user_data = await web_api.code_login(code)
73
75
 
74
76
  # Create a device manager that can discover devices.
75
- user_params = UserParams(
76
- username="youremailhere",
77
- user_data=user_data,
78
- )
77
+ user_params = UserParams(username=email_address, user_data=user_data)
79
78
  device_manager = await create_device_manager(user_params)
80
79
  devices = await device_manager.get_devices()
81
80
 
82
- # Get all vacuum devices that support the v1 PropertiesApi
81
+ # Get all vacuum devices. Each device generation has different capabilities
82
+ # and APIs available so to find vacuums we filter by the v1 PropertiesApi.
83
83
  for device in devices:
84
84
  if not device.v1_properties:
85
85
  continue
86
86
 
87
- # Refresh the current device status
87
+ # The PropertiesAPI has traits different device commands such as getting
88
+ # status, sending clean commands, etc. For this example we send a
89
+ # command to refresh the current device status.
88
90
  status_trait = device.v1_properties.status
89
91
  await status_trait.refresh()
90
92
  print(status_trait)
@@ -92,9 +94,16 @@ async def main():
92
94
  asyncio.run(main())
93
95
  ```
94
96
 
95
- See [examples/example.py](examples/example.py) for a more full featured example
96
- that has performance improvements to cache cloud information to prefer
97
- connections over the local network.
97
+
98
+ ## Functionality
99
+
100
+ The library interacts with devices through specific API properties based on the device protocol:
101
+
102
+ * **Standard Vacuums (V1 Protocol)**: Most robot vacuums use this. Interaction is done through `device.v1_properties`, which contains traits like `status`, `consumables`, and `maps`. Use the `command` trait for actions like starting or stopping cleaning.
103
+ * **Wet/Dry Vacuums & Washing Machines (A01 Protocol)**: Devices like the Dyad and Zeo use this. Interaction is done through `device.a01_properties` using `query_values()` and `set_value()`.
104
+
105
+ You can find detailed documentation for [Devices](https://python-roborock.github.io/python-roborock/roborock/devices/device.html) and [Traits](https://python-roborock.github.io/python-roborock/roborock/devices/traits.html).
106
+
98
107
 
99
108
  ## Supported devices
100
109
 
@@ -103,6 +112,7 @@ You can find what devices are supported
103
112
  Please note this may not immediately contain the latest devices.
104
113
 
105
114
 
106
- ## Credits
115
+ ## Acknowledgements
107
116
 
108
- Thanks @rovo89 for https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7 And thanks @PiotrMachowski for https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor
117
+ * Thanks to [@rovo89](https://github.com/rovo89) for [Login APIs gist](https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7).
118
+ * Thanks to [@PiotrMachowski](https://github.com/PiotrMachowski) for [Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor](https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor).
@@ -0,0 +1,89 @@
1
+ # Roborock
2
+
3
+ <p align="center">
4
+ <a href="https://pypi.org/project/python-roborock/">
5
+ <img src="https://img.shields.io/pypi/v/python-roborock.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
6
+ </a>
7
+ <img src="https://img.shields.io/pypi/pyversions/python-roborock.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
8
+ <img src="https://img.shields.io/pypi/l/python-roborock.svg?style=flat-square" alt="License">
9
+ <a href="https://codecov.io/github/Python-roborock/python-roborock" >
10
+ <img src="https://codecov.io/github/Python-roborock/python-roborock/graph/badge.svg?token=KEK4S3FPSZ" alt="Code Coverage"/>
11
+ </a>
12
+ </p>
13
+
14
+
15
+ Roborock library for online and offline control of your vacuums.
16
+
17
+ ## Installation
18
+
19
+ Install this via pip (or your favourite package manager):
20
+
21
+ `pip install python-roborock`
22
+
23
+ ## Example Usage
24
+
25
+ See [examples/example.py](examples/example.py) for a more full featured example,
26
+ or the [API documentation](https://python-roborock.github.io/python-roborock/)
27
+ for more details.
28
+
29
+ Here is a basic example:
30
+
31
+ ```python
32
+ import asyncio
33
+
34
+ from roborock.web_api import RoborockApiClient
35
+ from roborock.devices.device_manager import create_device_manager, UserParams
36
+
37
+
38
+ async def main():
39
+ email_address = "youremailhere@example.com"
40
+ web_api = RoborockApiClient(username=email_address)
41
+ # Send a login code to the above email address
42
+ await web_api.request_code()
43
+ # Prompt the user to enter the code
44
+ code = input("What is the code?")
45
+ user_data = await web_api.code_login(code)
46
+
47
+ # Create a device manager that can discover devices.
48
+ user_params = UserParams(username=email_address, user_data=user_data)
49
+ device_manager = await create_device_manager(user_params)
50
+ devices = await device_manager.get_devices()
51
+
52
+ # Get all vacuum devices. Each device generation has different capabilities
53
+ # and APIs available so to find vacuums we filter by the v1 PropertiesApi.
54
+ for device in devices:
55
+ if not device.v1_properties:
56
+ continue
57
+
58
+ # The PropertiesAPI has traits different device commands such as getting
59
+ # status, sending clean commands, etc. For this example we send a
60
+ # command to refresh the current device status.
61
+ status_trait = device.v1_properties.status
62
+ await status_trait.refresh()
63
+ print(status_trait)
64
+
65
+ asyncio.run(main())
66
+ ```
67
+
68
+
69
+ ## Functionality
70
+
71
+ The library interacts with devices through specific API properties based on the device protocol:
72
+
73
+ * **Standard Vacuums (V1 Protocol)**: Most robot vacuums use this. Interaction is done through `device.v1_properties`, which contains traits like `status`, `consumables`, and `maps`. Use the `command` trait for actions like starting or stopping cleaning.
74
+ * **Wet/Dry Vacuums & Washing Machines (A01 Protocol)**: Devices like the Dyad and Zeo use this. Interaction is done through `device.a01_properties` using `query_values()` and `set_value()`.
75
+
76
+ You can find detailed documentation for [Devices](https://python-roborock.github.io/python-roborock/roborock/devices/device.html) and [Traits](https://python-roborock.github.io/python-roborock/roborock/devices/traits.html).
77
+
78
+
79
+ ## Supported devices
80
+
81
+ You can find what devices are supported
82
+ [here](https://python-roborock.readthedocs.io/en/latest/supported_devices.html).
83
+ Please note this may not immediately contain the latest devices.
84
+
85
+
86
+ ## Acknowledgements
87
+
88
+ * Thanks to [@rovo89](https://github.com/rovo89) for [Login APIs gist](https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7).
89
+ * Thanks to [@PiotrMachowski](https://github.com/PiotrMachowski) for [Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor](https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-roborock"
3
- version = "3.17.0"
3
+ version = "3.19.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"
@@ -187,19 +187,19 @@ class B01Props(RoborockBase):
187
187
  @property
188
188
  def status_name(self) -> str | None:
189
189
  """Returns the name of the current status."""
190
- return self.status.name if self.status is not None else None
190
+ return self.status.value if self.status is not None else None
191
191
 
192
192
  @property
193
193
  def fault_name(self) -> str | None:
194
194
  """Returns the name of the current fault."""
195
- return self.fault.name if self.fault is not None else None
195
+ return self.fault.value if self.fault is not None else None
196
196
 
197
197
  @property
198
198
  def wind_name(self) -> str | None:
199
199
  """Returns the name of the current fan speed (wind)."""
200
- return self.wind.name if self.wind is not None else None
200
+ return self.wind.value if self.wind is not None else None
201
201
 
202
202
  @property
203
203
  def work_mode_name(self) -> str | None:
204
204
  """Returns the name of the current work mode."""
205
- return self.work_mode.name if self.work_mode is not None else None
205
+ return self.work_mode.value if self.work_mode is not None else None
@@ -72,8 +72,8 @@ class RoborockModeEnum(StrEnum):
72
72
 
73
73
  @classmethod
74
74
  def keys(cls) -> list[str]:
75
- """Returns a list of all member names."""
76
- return [member.name for member in cls]
75
+ """Returns a list of all member values."""
76
+ return [member.value for member in cls]
77
77
 
78
78
 
79
79
  ProductInfo = namedtuple("ProductInfo", ["nickname", "short_models"])
@@ -585,7 +585,7 @@ class AppInitStatus(RoborockBase):
585
585
  local_info: AppInitStatusLocalInfo
586
586
  feature_info: list[int]
587
587
  new_feature_info: int
588
- new_feature_info_str: str
588
+ new_feature_info_str: str = ""
589
589
  new_feature_info_2: int | None = None
590
590
  carriage_type: int | None = None
591
591
  dsp_version: str | None = None
@@ -4,7 +4,19 @@ The devices module provides functionality to discover Roborock devices on the
4
4
  network. This section documents the full lifecycle of device discovery across
5
5
  Cloud and Network.
6
6
 
7
- ## Quick Start: Understanding Device Protocols
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
8
20
 
9
21
  **The library supports three device protocol versions, each with different capabilities:**
10
22
 
@@ -18,7 +30,7 @@ Cloud and Network.
18
30
 
19
31
  **Key Point:** The `DeviceManager` automatically detects the protocol version and creates the appropriate channel type. You don't need to handle this manually.
20
32
 
21
- ## Architecture Overview
33
+ ## Internal Architecture
22
34
 
23
35
  The library is organized into distinct layers, each with a specific responsibility. **Different device protocols use different channel implementations:**
24
36
 
@@ -138,7 +150,7 @@ graph TB
138
150
  | **A01** (`pv=A01`) | `MqttChannel` + helpers | ❌ No | Direct MQTT | Dyad, Zeo washers |
139
151
  | **B01** (`pv=B01`) | `MqttChannel` + helpers | ❌ No | Direct MQTT | Some newer models |
140
152
 
141
- ## Init account setup
153
+ ## Account Setup Internals
142
154
 
143
155
  ### Login
144
156
 
@@ -151,7 +163,7 @@ graph TB
151
163
  - This contains information used to connect to MQTT
152
164
  - You get an `-eu` suffix in the API URLs if you are in the eu and `-us` if you are in the us
153
165
 
154
- ## Home Data
166
+ ## Home Data Internals
155
167
 
156
168
  The `HomeData` includes information about the various devices in the home. We use `v3`
157
169
  and it is notable that if devices don't show up in the `home_data` response it is likely
@@ -174,7 +186,7 @@ that a newer version of the API should be used.
174
186
  - There is another REST request `get_rooms` that will do the same thing.
175
187
  - Note: If we cache home_data, we likely need to use `get_rooms` to get rooms fresh
176
188
 
177
- ## Device Connections
189
+ ## Connection Implementation
178
190
 
179
191
  ### Connection Flow by Protocol
180
192
 
@@ -343,7 +355,7 @@ graph LR
343
355
  3. **Timeout Handling**: Commands timeout after 10 seconds if no response is received
344
356
  4. **Multiple Strategies**: `V1Channel` tries local first, then falls back to MQTT if local fails
345
357
 
346
- ## Design
358
+ ## Class Design & Components
347
359
 
348
360
  ### Current Architecture
349
361
 
@@ -3,8 +3,9 @@
3
3
  import asyncio
4
4
  import enum
5
5
  import logging
6
- from collections.abc import Callable
6
+ from collections.abc import Callable, Mapping
7
7
  from dataclasses import dataclass
8
+ from typing import Any
8
9
 
9
10
  import aiohttp
10
11
 
@@ -15,6 +16,8 @@ from roborock.data import (
15
16
  UserData,
16
17
  )
17
18
  from roborock.devices.device import DeviceReadyCallback, RoborockDevice
19
+ from roborock.diagnostics import Diagnostics
20
+ from roborock.exceptions import RoborockException
18
21
  from roborock.map.map_parser import MapParserConfig
19
22
  from roborock.mqtt.roborock_session import create_lazy_mqtt_session
20
23
  from roborock.mqtt.session import MqttSession
@@ -57,6 +60,7 @@ class DeviceManager:
57
60
  device_creator: DeviceCreator,
58
61
  mqtt_session: MqttSession,
59
62
  cache: Cache,
63
+ diagnostics: Diagnostics,
60
64
  ) -> None:
61
65
  """Initialize the DeviceManager with user data and optional cache storage.
62
66
 
@@ -67,13 +71,21 @@ class DeviceManager:
67
71
  self._device_creator = device_creator
68
72
  self._devices: dict[str, RoborockDevice] = {}
69
73
  self._mqtt_session = mqtt_session
74
+ self._diagnostics = diagnostics
70
75
 
71
- async def discover_devices(self) -> list[RoborockDevice]:
76
+ async def discover_devices(self, prefer_cache: bool = True) -> list[RoborockDevice]:
72
77
  """Discover all devices for the logged-in user."""
78
+ self._diagnostics.increment("discover_devices")
73
79
  cache_data = await self._cache.get()
74
- if not cache_data.home_data:
75
- _LOGGER.debug("No cached home data found, fetching from API")
76
- cache_data.home_data = await self._web_api.get_home_data()
80
+ if not cache_data.home_data or not prefer_cache:
81
+ _LOGGER.debug("Fetching home data (prefer_cache=%s)", prefer_cache)
82
+ self._diagnostics.increment("fetch_home_data")
83
+ try:
84
+ cache_data.home_data = await self._web_api.get_home_data()
85
+ except RoborockException as ex:
86
+ if not cache_data.home_data:
87
+ raise
88
+ _LOGGER.debug("Failed to fetch home data, using cached data: %s", ex)
77
89
  await self._cache.set(cache_data)
78
90
  home_data = cache_data.home_data
79
91
 
@@ -110,6 +122,10 @@ class DeviceManager:
110
122
  tasks.append(self._mqtt_session.close())
111
123
  await asyncio.gather(*tasks)
112
124
 
125
+ def diagnostic_data(self) -> Mapping[str, Any]:
126
+ """Return diagnostics information about the device manager."""
127
+ return self._diagnostics.as_dict()
128
+
113
129
 
114
130
  @dataclass
115
131
  class UserParams:
@@ -176,7 +192,10 @@ async def create_device_manager(
176
192
  web_api = create_web_api_wrapper(user_params, session=session, cache=cache)
177
193
  user_data = user_params.user_data
178
194
 
195
+ diagnostics = Diagnostics()
196
+
179
197
  mqtt_params = create_mqtt_params(user_data.rriot)
198
+ mqtt_params.diagnostics = diagnostics.subkey("mqtt_session")
180
199
  mqtt_session = await create_lazy_mqtt_session(mqtt_params)
181
200
 
182
201
  def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDataProduct) -> RoborockDevice:
@@ -220,6 +239,6 @@ async def create_device_manager(
220
239
  dev.add_ready_callback(ready_callback)
221
240
  return dev
222
241
 
223
- manager = DeviceManager(web_api, device_creator, mqtt_session=mqtt_session, cache=cache)
242
+ manager = DeviceManager(web_api, device_creator, mqtt_session=mqtt_session, cache=cache, diagnostics=diagnostics)
224
243
  await manager.discover_devices()
225
244
  return manager
@@ -0,0 +1,28 @@
1
+ """Module for device traits.
2
+
3
+ This package contains the trait definitions for different device protocols supported
4
+ by Roborock devices.
5
+
6
+ Submodules
7
+ ----------
8
+ * `v1`: Contains traits for standard Roborock vacuums (e.g., S-series, Q-series).
9
+ These devices use the V1 protocol and have rich feature sets split into
10
+ granular traits (e.g., `StatusTrait`, `ConsumableTrait`).
11
+ * `a01`: Contains APIs for A01 protocol devices, such as the Dyad (wet/dry vacuum)
12
+ and Zeo (washing machine). These devices use a different communication structure.
13
+ * `b01`: Contains APIs for B01 protocol devices.
14
+ """
15
+
16
+ from abc import ABC
17
+
18
+ __all__ = [
19
+ "Trait",
20
+ "traits_mixin",
21
+ "v1",
22
+ "a01",
23
+ "b01",
24
+ ]
25
+
26
+
27
+ class Trait(ABC):
28
+ """Base class for all traits."""
@@ -1,3 +1,24 @@
1
+ """Create traits for A01 devices.
2
+
3
+ This module provides the API implementations for A01 protocol devices, which include
4
+ Dyad (Wet/Dry Vacuums) and Zeo (Washing Machines).
5
+
6
+ Using A01 APIs
7
+ --------------
8
+ A01 devices expose a single API object that handles all device interactions. This API is
9
+ available on the device instance (typically via `device.a01_properties`).
10
+
11
+ The API provides two main methods:
12
+ 1. **query_values(protocols)**: Fetches current state for specific data points.
13
+ You must pass a list of protocol enums (e.g. `RoborockDyadDataProtocol` or
14
+ `RoborockZeoProtocol`) to request specific data.
15
+ 2. **set_value(protocol, value)**: Sends a command to the device to change a setting
16
+ or perform an action.
17
+
18
+ Note that these APIs fetch data directly from the device upon request and do not
19
+ cache state internally.
20
+ """
21
+
1
22
  import json
2
23
  from collections.abc import Callable
3
24
  from datetime import time
@@ -2,17 +2,33 @@
2
2
 
3
3
  Traits are modular components that encapsulate specific features of a Roborock
4
4
  device. This module provides a factory function to create and initialize the
5
- appropriate traits for V1 devices based on their capabilities. They can also
6
- be considered groups of commands and parsing logic for that command.
7
-
8
- Traits have a `refresh()` method that can be called to update their state
9
- from the device. Some traits may also provide additional methods for modifying
10
- the device state.
11
-
12
- The most common pattern for a trait is to subclass `V1TraitMixin` and a `RoborockBase`
13
- dataclass, and define a `command` class variable that specifies the `RoborockCommand`
14
- used to fetch the trait data from the device. See `common.py` for more details
15
- on common patterns used across traits.
5
+ appropriate traits for V1 devices based on their capabilities.
6
+
7
+ Using Traits
8
+ ------------
9
+ Traits are accessed via the `v1_properties` attribute on a device. Each trait
10
+ represents a specific capability, such as `status`, `consumables`, or `rooms`.
11
+
12
+ Traits serve two main purposes:
13
+ 1. **State**: Traits are dataclasses that hold the current state of the device
14
+ feature. You can access attributes directly (e.g., `device.v1_properties.status.battery`).
15
+ 2. **Commands**: Traits provide methods to control the device. For example,
16
+ `device.v1_properties.volume.set_volume()`.
17
+
18
+ Additionally, the `command` trait provides a generic way to send any command to the
19
+ device (e.g. `device.v1_properties.command.send("app_start")`). This is often used
20
+ for basic cleaning operations like starting, stopping, or docking the vacuum.
21
+
22
+ Most traits have a `refresh()` method that must be called to update their state
23
+ from the device. The state is not updated automatically in real-time unless
24
+ specifically implemented by the trait or via polling.
25
+
26
+ Adding New Traits
27
+ -----------------
28
+ When adding a new trait, the most common pattern is to subclass `V1TraitMixin`
29
+ and a `RoborockBase` dataclass. You must define a `command` class variable that
30
+ specifies the `RoborockCommand` used to fetch the trait data from the device.
31
+ See `common.py` for more details on common patterns used across traits.
16
32
 
17
33
  There are some additional decorators in `common.py` that can be used to specify which
18
34
  RPC channel to use for the trait (standard, MQTT/cloud, or map-specific).
@@ -43,6 +59,29 @@ from roborock.map.map_parser import MapParserConfig
43
59
  from roborock.protocols.v1_protocol import V1RpcChannel
44
60
  from roborock.web_api import UserWebApiClient
45
61
 
62
+ from . import (
63
+ child_lock,
64
+ clean_summary,
65
+ command,
66
+ common,
67
+ consumeable,
68
+ device_features,
69
+ do_not_disturb,
70
+ dust_collection_mode,
71
+ flow_led_status,
72
+ home,
73
+ led_status,
74
+ map_content,
75
+ maps,
76
+ network_info,
77
+ rooms,
78
+ routines,
79
+ smart_wash_params,
80
+ status,
81
+ valley_electricity_timer,
82
+ volume,
83
+ wash_towel_mode,
84
+ )
46
85
  from .child_lock import ChildLockTrait
47
86
  from .clean_summary import CleanSummaryTrait
48
87
  from .command import CommandTrait
@@ -71,6 +110,7 @@ __all__ = [
71
110
  "PropertiesApi",
72
111
  "child_lock",
73
112
  "clean_summary",
113
+ "command",
74
114
  "common",
75
115
  "consumeable",
76
116
  "device_features",
@@ -5,7 +5,17 @@ from roborock.protocols.v1_protocol import ParamsType
5
5
 
6
6
 
7
7
  class CommandTrait:
8
- """Trait for sending commands to Roborock devices."""
8
+ """Trait for sending commands to Roborock devices.
9
+
10
+ This trait allows sending raw commands directly to the device. It is particularly
11
+ useful for:
12
+ 1. **Cleaning Control**: Sending commands like `app_start`, `app_stop`, `app_pause`,
13
+ or `app_charge` which don't belong to a specific state trait.
14
+ 2. **Unsupported Features**: Accessing device functionality that hasn't been
15
+ mapped to a specific trait yet.
16
+
17
+ See `roborock.roborock_typing.RoborockCommand` for a list of available commands.
18
+ """
9
19
 
10
20
  def __post_init__(self) -> None:
11
21
  """Post-initialization to set up the RPC channel.
@@ -0,0 +1,84 @@
1
+ """Diagnostics for debugging.
2
+
3
+ A Diagnostics object can be used to track counts and latencies of various
4
+ operations within a module. This can be useful for debugging performance issues
5
+ or understanding usage patterns.
6
+
7
+ This is an internal facing module and is not intended for public use. Diagnostics
8
+ data is collected and exposed to clients via higher level APIs like the
9
+ DeviceManager.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import time
15
+ from collections import Counter
16
+ from collections.abc import Generator, Mapping
17
+ from contextlib import contextmanager
18
+ from typing import Any
19
+
20
+
21
+ class Diagnostics:
22
+ """A class that holds diagnostics information for a module.
23
+
24
+ You can use this class to hold counter or for recording timing information
25
+ that can be exported as a dictionary for debugging purposes.
26
+ """
27
+
28
+ def __init__(self) -> None:
29
+ """Initialize Diagnostics."""
30
+ self._counter: Counter = Counter()
31
+ self._subkeys: dict[str, Diagnostics] = {}
32
+
33
+ def increment(self, key: str, count: int = 1) -> None:
34
+ """Increment a counter for the specified key/event."""
35
+ self._counter.update(Counter({key: count}))
36
+
37
+ def elapsed(self, key_prefix: str, elapsed_ms: int = 1) -> None:
38
+ """Track a latency event for the specified key/event prefix."""
39
+ self.increment(f"{key_prefix}_count", 1)
40
+ self.increment(f"{key_prefix}_sum", elapsed_ms)
41
+
42
+ def as_dict(self) -> Mapping[str, Any]:
43
+ """Return diagnostics as a debug dictionary."""
44
+ data: dict[str, Any] = {k: self._counter[k] for k in self._counter}
45
+ for k, d in self._subkeys.items():
46
+ v = d.as_dict()
47
+ if not v:
48
+ continue
49
+ data[k] = v
50
+ return data
51
+
52
+ def subkey(self, key: str) -> Diagnostics:
53
+ """Return sub-Diagnostics object with the specified subkey.
54
+
55
+ This will create a new Diagnostics object if one does not already exist
56
+ for the specified subkey. Stats from the sub-Diagnostics will be included
57
+ in the parent Diagnostics when exported as a dictionary.
58
+
59
+ Args:
60
+ key: The subkey for the diagnostics.
61
+
62
+ Returns:
63
+ The Diagnostics object for the specified subkey.
64
+ """
65
+ if key not in self._subkeys:
66
+ self._subkeys[key] = Diagnostics()
67
+ return self._subkeys[key]
68
+
69
+ @contextmanager
70
+ def timer(self, key_prefix: str) -> Generator[None, None, None]:
71
+ """A context manager that records the timing of operations as a diagnostic."""
72
+ start = time.perf_counter()
73
+ try:
74
+ yield
75
+ finally:
76
+ end = time.perf_counter()
77
+ ms = int((end - start) * 1000)
78
+ self.elapsed(key_prefix, ms)
79
+
80
+ def reset(self) -> None:
81
+ """Clear all diagnostics, for testing."""
82
+ self._counter = Counter()
83
+ for d in self._subkeys.values():
84
+ d.reset()