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.
- {python_roborock-3.17.0 → python_roborock-3.19.0}/PKG-INFO +29 -19
- python_roborock-3.19.0/README.md +89 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/pyproject.toml +1 -1
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q7/b01_q7_containers.py +4 -4
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/code_mappings.py +2 -2
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/v1/v1_containers.py +1 -1
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/README.md +18 -6
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/device_manager.py +25 -6
- python_roborock-3.19.0/roborock/devices/traits/__init__.py +28 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/a01/__init__.py +21 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/__init__.py +51 -11
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/command.py +11 -1
- python_roborock-3.19.0/roborock/diagnostics.py +84 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/mqtt/roborock_session.py +37 -16
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/mqtt/session.py +10 -1
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocols/v1_protocol.py +2 -4
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/roborock_message.py +2 -4
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/roborock_typing.py +6 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/util.py +10 -0
- python_roborock-3.17.0/README.md +0 -79
- python_roborock-3.17.0/roborock/devices/traits/__init__.py +0 -15
- {python_roborock-3.17.0 → python_roborock-3.19.0}/.gitignore +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/LICENSE +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/api.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/callbacks.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/cli.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/cloud_api.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/command_cache.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/const.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/containers.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/device_features.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/cache.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/channel.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/device.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/file_cache.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/exceptions.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/map/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocol.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocols/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/py.typed +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/roborock_future.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-3.17.0 → python_roborock-3.19.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {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.
|
|
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
|
-
##
|
|
52
|
+
## Example Usage
|
|
53
53
|
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
##
|
|
115
|
+
## Acknowledgements
|
|
107
116
|
|
|
108
|
-
Thanks @rovo89
|
|
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&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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
76
|
-
return [member.
|
|
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
|
-
##
|
|
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
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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("
|
|
76
|
-
|
|
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.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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()
|