python-roborock 3.7.2__tar.gz → 3.8.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.7.2 → python_roborock-3.8.0}/PKG-INFO +25 -26
- {python_roborock-3.7.2 → python_roborock-3.8.0}/README.md +24 -25
- {python_roborock-3.7.2 → python_roborock-3.8.0}/pyproject.toml +2 -2
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/__init__.py +5 -6
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/containers.py +3 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/README.md +5 -3
- python_roborock-3.8.0/roborock/devices/__init__.py +11 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/device.py +47 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/device_manager.py +1 -1
- python_roborock-3.8.0/roborock/devices/file_cache.py +70 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/b01/__init__.py +1 -2
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/__init__.py +20 -20
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/clean_summary.py +2 -3
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/common.py +1 -2
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/device_features.py +5 -7
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/do_not_disturb.py +4 -13
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/home.py +2 -3
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/network_info.py +3 -6
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/valley_electricity_timer.py +3 -10
- python_roborock-3.8.0/roborock/protocols/__init__.py +3 -0
- python_roborock-3.7.2/roborock/devices/__init__.py +0 -7
- {python_roborock-3.7.2 → python_roborock-3.8.0}/.gitignore +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/LICENSE +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/api.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/callbacks.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/cli.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/cloud_api.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/command_cache.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/const.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/code_mappings.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/device_features.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/cache.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/channel.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/v1_rpc_channel.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/exceptions.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/map/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/protocol.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/py.typed +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/roborock_future.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/roborock_message.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/util.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-3.7.2 → python_roborock-3.8.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.8.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,16 +49,14 @@ Install this via pip (or your favourite package manager):
|
|
|
49
49
|
|
|
50
50
|
You can see all of the commands supported [here](https://python-roborock.readthedocs.io/en/latest/api_commands.html)
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Example Usage
|
|
53
53
|
|
|
54
|
-
Here is an example that requires no manual intervention and can be done all automatically. You can skip some steps by
|
|
55
|
-
caching values or looking at them and grabbing them manually.
|
|
56
54
|
```python
|
|
57
55
|
import asyncio
|
|
58
56
|
|
|
59
|
-
from roborock import HomeDataProduct, DeviceData, RoborockCommand
|
|
60
|
-
from roborock.version_1_apis import RoborockMqttClientV1, RoborockLocalClientV1
|
|
61
57
|
from roborock.web_api import RoborockApiClient
|
|
58
|
+
from roborock.devices.device_manager import create_device_manager, UserParams
|
|
59
|
+
|
|
62
60
|
|
|
63
61
|
async def main():
|
|
64
62
|
web_api = RoborockApiClient(username="youremailhere")
|
|
@@ -69,30 +67,31 @@ async def main():
|
|
|
69
67
|
code = input("What is the code?")
|
|
70
68
|
user_data = await web_api.code_login(code)
|
|
71
69
|
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
status = await local_client.send_command(RoborockCommand.GET_STATUS)
|
|
90
|
-
# Or use existing functions that will give you data classes
|
|
91
|
-
status = await local_client.get_status()
|
|
70
|
+
# Create a device manager that can discover devices.
|
|
71
|
+
user_params = UserParams(
|
|
72
|
+
username="youremailhere",
|
|
73
|
+
user_data=user_data,
|
|
74
|
+
)
|
|
75
|
+
device_manager = await create_device_manager(user_params)
|
|
76
|
+
devices = await device_manager.get_devices()
|
|
77
|
+
|
|
78
|
+
# Get all vacuum devices that support the v1 PropertiesApi
|
|
79
|
+
for device in devices:
|
|
80
|
+
if not device.v1_properties:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
# Refresh the current device status
|
|
84
|
+
status_trait = device.v1_properties.status
|
|
85
|
+
await status_trait.refresh()
|
|
86
|
+
print(status_trait)
|
|
92
87
|
|
|
93
88
|
asyncio.run(main())
|
|
94
89
|
```
|
|
95
90
|
|
|
91
|
+
See [examples/example.py](examples/example.py) for a more full featured example
|
|
92
|
+
that has performance improvements to cache cloud information to prefer
|
|
93
|
+
connections over the local network.
|
|
94
|
+
|
|
96
95
|
## Supported devices
|
|
97
96
|
|
|
98
97
|
You can find what devices are supported
|
|
@@ -20,16 +20,14 @@ Install this via pip (or your favourite package manager):
|
|
|
20
20
|
|
|
21
21
|
You can see all of the commands supported [here](https://python-roborock.readthedocs.io/en/latest/api_commands.html)
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Example Usage
|
|
24
24
|
|
|
25
|
-
Here is an example that requires no manual intervention and can be done all automatically. You can skip some steps by
|
|
26
|
-
caching values or looking at them and grabbing them manually.
|
|
27
25
|
```python
|
|
28
26
|
import asyncio
|
|
29
27
|
|
|
30
|
-
from roborock import HomeDataProduct, DeviceData, RoborockCommand
|
|
31
|
-
from roborock.version_1_apis import RoborockMqttClientV1, RoborockLocalClientV1
|
|
32
28
|
from roborock.web_api import RoborockApiClient
|
|
29
|
+
from roborock.devices.device_manager import create_device_manager, UserParams
|
|
30
|
+
|
|
33
31
|
|
|
34
32
|
async def main():
|
|
35
33
|
web_api = RoborockApiClient(username="youremailhere")
|
|
@@ -40,30 +38,31 @@ async def main():
|
|
|
40
38
|
code = input("What is the code?")
|
|
41
39
|
user_data = await web_api.code_login(code)
|
|
42
40
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
status = await local_client.send_command(RoborockCommand.GET_STATUS)
|
|
61
|
-
# Or use existing functions that will give you data classes
|
|
62
|
-
status = await local_client.get_status()
|
|
41
|
+
# Create a device manager that can discover devices.
|
|
42
|
+
user_params = UserParams(
|
|
43
|
+
username="youremailhere",
|
|
44
|
+
user_data=user_data,
|
|
45
|
+
)
|
|
46
|
+
device_manager = await create_device_manager(user_params)
|
|
47
|
+
devices = await device_manager.get_devices()
|
|
48
|
+
|
|
49
|
+
# Get all vacuum devices that support the v1 PropertiesApi
|
|
50
|
+
for device in devices:
|
|
51
|
+
if not device.v1_properties:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
# Refresh the current device status
|
|
55
|
+
status_trait = device.v1_properties.status
|
|
56
|
+
await status_trait.refresh()
|
|
57
|
+
print(status_trait)
|
|
63
58
|
|
|
64
59
|
asyncio.run(main())
|
|
65
60
|
```
|
|
66
61
|
|
|
62
|
+
See [examples/example.py](examples/example.py) for a more full featured example
|
|
63
|
+
that has performance improvements to cache cloud information to prefer
|
|
64
|
+
connections over the local network.
|
|
65
|
+
|
|
67
66
|
## Supported devices
|
|
68
67
|
|
|
69
68
|
You can find what devices are supported
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.8.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"
|
|
@@ -44,7 +44,7 @@ dev = [
|
|
|
44
44
|
"pytest",
|
|
45
45
|
"pre-commit>=3.5,<5.0",
|
|
46
46
|
"mypy",
|
|
47
|
-
"ruff==0.14.
|
|
47
|
+
"ruff==0.14.4",
|
|
48
48
|
"codespell",
|
|
49
49
|
"pyshark>=0.6,<0.7",
|
|
50
50
|
"aioresponses>=0.7.7,<0.8",
|
|
@@ -11,6 +11,7 @@ from . import (
|
|
|
11
11
|
cloud_api,
|
|
12
12
|
const,
|
|
13
13
|
data,
|
|
14
|
+
devices,
|
|
14
15
|
exceptions,
|
|
15
16
|
roborock_typing,
|
|
16
17
|
version_1_apis,
|
|
@@ -19,13 +20,11 @@ from . import (
|
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
__all__ = [
|
|
23
|
+
"devices",
|
|
24
|
+
"data",
|
|
25
|
+
"map",
|
|
22
26
|
"web_api",
|
|
23
|
-
"version_1_apis",
|
|
24
|
-
"version_a01_apis",
|
|
25
|
-
"const",
|
|
26
|
-
"cloud_api",
|
|
27
27
|
"roborock_typing",
|
|
28
28
|
"exceptions",
|
|
29
|
-
"
|
|
30
|
-
# Add new APIs here in the future when they are public e.g. devices/
|
|
29
|
+
"const",
|
|
31
30
|
]
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# Roborock
|
|
1
|
+
# Roborock Devices & Discovery
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
6
|
|
|
5
7
|
## Init account setup
|
|
6
8
|
|
|
@@ -61,7 +63,7 @@ that a newer version of the API should be used.
|
|
|
61
63
|
|
|
62
64
|
## Design
|
|
63
65
|
|
|
64
|
-
###
|
|
66
|
+
### Prior API Issues
|
|
65
67
|
|
|
66
68
|
- Complex Inheritance Hierarchy: Multiple inheritance with classes like RoborockMqttClientV1 inheriting from both RoborockMqttClient and RoborockClientV1
|
|
67
69
|
|
|
@@ -4,12 +4,15 @@ This interface is experimental and subject to breaking changes without notice
|
|
|
4
4
|
until the API is stable.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import asyncio
|
|
8
|
+
import datetime
|
|
7
9
|
import logging
|
|
8
10
|
from abc import ABC
|
|
9
11
|
from collections.abc import Callable, Mapping
|
|
10
12
|
from typing import Any, TypeVar, cast
|
|
11
13
|
|
|
12
14
|
from roborock.data import HomeDataDevice, HomeDataProduct
|
|
15
|
+
from roborock.exceptions import RoborockException
|
|
13
16
|
from roborock.roborock_message import RoborockMessage
|
|
14
17
|
|
|
15
18
|
from .channel import Channel
|
|
@@ -22,6 +25,11 @@ __all__ = [
|
|
|
22
25
|
"RoborockDevice",
|
|
23
26
|
]
|
|
24
27
|
|
|
28
|
+
# Exponential backoff parameters
|
|
29
|
+
MIN_BACKOFF_INTERVAL = datetime.timedelta(seconds=10)
|
|
30
|
+
MAX_BACKOFF_INTERVAL = datetime.timedelta(minutes=30)
|
|
31
|
+
BACKOFF_MULTIPLIER = 1.5
|
|
32
|
+
|
|
25
33
|
|
|
26
34
|
class RoborockDevice(ABC, TraitsMixin):
|
|
27
35
|
"""A generic channel for establishing a connection with a Roborock device.
|
|
@@ -54,6 +62,7 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
54
62
|
self._device_info = device_info
|
|
55
63
|
self._product = product
|
|
56
64
|
self._channel = channel
|
|
65
|
+
self._connect_task: asyncio.Task[None] | None = None
|
|
57
66
|
self._unsub: Callable[[], None] | None = None
|
|
58
67
|
|
|
59
68
|
@property
|
|
@@ -98,6 +107,38 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
98
107
|
"""
|
|
99
108
|
return self._channel.is_local_connected
|
|
100
109
|
|
|
110
|
+
def start_connect(self) -> None:
|
|
111
|
+
"""Start a background task to connect to the device.
|
|
112
|
+
|
|
113
|
+
This will attempt to connect to the device using the appropriate protocol
|
|
114
|
+
channel. If the connection fails, it will retry with exponential backoff.
|
|
115
|
+
|
|
116
|
+
Once connected, the device will remain connected until `close()` is
|
|
117
|
+
called. The device will automatically attempt to reconnect if the connection
|
|
118
|
+
is lost.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
async def connect_loop() -> None:
|
|
122
|
+
backoff = MIN_BACKOFF_INTERVAL
|
|
123
|
+
try:
|
|
124
|
+
while True:
|
|
125
|
+
try:
|
|
126
|
+
await self.connect()
|
|
127
|
+
return
|
|
128
|
+
except RoborockException as e:
|
|
129
|
+
_LOGGER.info("Failed to connect to device %s: %s", self.name, e)
|
|
130
|
+
_LOGGER.info(
|
|
131
|
+
"Retrying connection to device %s in %s seconds", self.name, backoff.total_seconds()
|
|
132
|
+
)
|
|
133
|
+
await asyncio.sleep(backoff.total_seconds())
|
|
134
|
+
backoff = min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_INTERVAL)
|
|
135
|
+
except asyncio.CancelledError:
|
|
136
|
+
_LOGGER.info("connect_loop for device %s was cancelled", self.name)
|
|
137
|
+
# Clean exit on cancellation
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
self._connect_task = asyncio.create_task(connect_loop())
|
|
141
|
+
|
|
101
142
|
async def connect(self) -> None:
|
|
102
143
|
"""Connect to the device using the appropriate protocol channel."""
|
|
103
144
|
if self._unsub:
|
|
@@ -107,6 +148,12 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
107
148
|
|
|
108
149
|
async def close(self) -> None:
|
|
109
150
|
"""Close all connections to the device."""
|
|
151
|
+
if self._connect_task:
|
|
152
|
+
self._connect_task.cancel()
|
|
153
|
+
try:
|
|
154
|
+
await self._connect_task
|
|
155
|
+
except asyncio.CancelledError:
|
|
156
|
+
pass
|
|
110
157
|
if self._unsub:
|
|
111
158
|
self._unsub()
|
|
112
159
|
self._unsub = None
|
|
@@ -86,7 +86,7 @@ class DeviceManager:
|
|
|
86
86
|
if duid in self._devices:
|
|
87
87
|
continue
|
|
88
88
|
new_device = self._device_creator(home_data, device, product)
|
|
89
|
-
|
|
89
|
+
new_device.start_connect()
|
|
90
90
|
new_devices[duid] = new_device
|
|
91
91
|
|
|
92
92
|
self._devices.update(new_devices)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""This module implements a file-backed cache for device information.
|
|
2
|
+
|
|
3
|
+
This module provides a `FileCache` class that implements the `Cache` protocol
|
|
4
|
+
to store and retrieve cached device information from a file on disk. This allows
|
|
5
|
+
persistent caching of device data across application restarts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import pathlib
|
|
10
|
+
import pickle
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .cache import Cache, CacheData
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FileCache(Cache):
|
|
18
|
+
"""File backed cache implementation."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, file_path: pathlib.Path, init_fn: Callable[[], CacheData] = CacheData) -> None:
|
|
21
|
+
"""Initialize the file cache with the given file path."""
|
|
22
|
+
self._init_fn = init_fn
|
|
23
|
+
self._file_path = file_path
|
|
24
|
+
self._cache_data: CacheData | None = None
|
|
25
|
+
|
|
26
|
+
async def get(self) -> CacheData:
|
|
27
|
+
"""Get cached value."""
|
|
28
|
+
if self._cache_data is not None:
|
|
29
|
+
return self._cache_data
|
|
30
|
+
|
|
31
|
+
data = await load_value(self._file_path)
|
|
32
|
+
if data is not None and not isinstance(data, CacheData):
|
|
33
|
+
raise TypeError(f"Invalid cache data loaded from {self._file_path}")
|
|
34
|
+
|
|
35
|
+
self._cache_data = data or self._init_fn()
|
|
36
|
+
return self._cache_data
|
|
37
|
+
|
|
38
|
+
async def set(self, value: CacheData) -> None: # type: ignore[override]
|
|
39
|
+
"""Set value in the cache."""
|
|
40
|
+
self._cache_data = value
|
|
41
|
+
|
|
42
|
+
async def flush(self) -> None:
|
|
43
|
+
"""Flush the cache to disk."""
|
|
44
|
+
if self._cache_data is None:
|
|
45
|
+
return
|
|
46
|
+
await store_value(self._file_path, self._cache_data)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def store_value(file_path: pathlib.Path, value: Any) -> None:
|
|
50
|
+
"""Store a value to the given file path."""
|
|
51
|
+
|
|
52
|
+
def _store_to_disk(file_path: pathlib.Path, value: Any) -> None:
|
|
53
|
+
with open(file_path, "wb") as f:
|
|
54
|
+
data = pickle.dumps(value)
|
|
55
|
+
f.write(data)
|
|
56
|
+
|
|
57
|
+
await asyncio.to_thread(_store_to_disk, file_path, value)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def load_value(file_path: pathlib.Path) -> Any | None:
|
|
61
|
+
"""Load a value from the given file path."""
|
|
62
|
+
|
|
63
|
+
def _load_from_disk(file_path: pathlib.Path) -> Any | None:
|
|
64
|
+
if not file_path.exists():
|
|
65
|
+
return None
|
|
66
|
+
with open(file_path, "rb") as f:
|
|
67
|
+
data = f.read()
|
|
68
|
+
return pickle.loads(data)
|
|
69
|
+
|
|
70
|
+
return await asyncio.to_thread(_load_from_disk, file_path)
|
|
@@ -67,27 +67,27 @@ from .wash_towel_mode import WashTowelModeTrait
|
|
|
67
67
|
_LOGGER = logging.getLogger(__name__)
|
|
68
68
|
|
|
69
69
|
__all__ = [
|
|
70
|
-
"create",
|
|
71
70
|
"PropertiesApi",
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
71
|
+
"child_lock",
|
|
72
|
+
"clean_summary",
|
|
73
|
+
"common",
|
|
74
|
+
"consumeable",
|
|
75
|
+
"device_features",
|
|
76
|
+
"do_not_disturb",
|
|
77
|
+
"dust_collection_mode",
|
|
78
|
+
"flow_led_status",
|
|
79
|
+
"home",
|
|
80
|
+
"led_status",
|
|
81
|
+
"map_content",
|
|
82
|
+
"maps",
|
|
83
|
+
"network_info",
|
|
84
|
+
"rooms",
|
|
85
|
+
"routines",
|
|
86
|
+
"smart_wash_params",
|
|
87
|
+
"status",
|
|
88
|
+
"valley_electricity_timer",
|
|
89
|
+
"volume",
|
|
90
|
+
"wash_towel_mode",
|
|
91
91
|
]
|
|
92
92
|
|
|
93
93
|
|
|
@@ -14,7 +14,7 @@ class CleanSummaryTrait(CleanSummaryWithDetail, common.V1TraitMixin):
|
|
|
14
14
|
|
|
15
15
|
command = RoborockCommand.GET_CLEAN_SUMMARY
|
|
16
16
|
|
|
17
|
-
async def refresh(self) ->
|
|
17
|
+
async def refresh(self) -> None:
|
|
18
18
|
"""Refresh the clean summary data and last clean record.
|
|
19
19
|
|
|
20
20
|
Assumes that the clean summary has already been fetched.
|
|
@@ -23,10 +23,9 @@ class CleanSummaryTrait(CleanSummaryWithDetail, common.V1TraitMixin):
|
|
|
23
23
|
if not self.records:
|
|
24
24
|
_LOGGER.debug("No clean records available in clean summary.")
|
|
25
25
|
self.last_clean_record = None
|
|
26
|
-
return
|
|
26
|
+
return
|
|
27
27
|
last_record_id = self.records[-1]
|
|
28
28
|
self.last_clean_record = await self.get_clean_record(last_record_id)
|
|
29
|
-
return self
|
|
30
29
|
|
|
31
30
|
@classmethod
|
|
32
31
|
def _parse_type_response(cls, response: common.V1ResponseData) -> Self:
|
|
@@ -76,7 +76,7 @@ class V1TraitMixin(ABC):
|
|
|
76
76
|
raise ValueError("Device trait in invalid state")
|
|
77
77
|
return self._rpc_channel
|
|
78
78
|
|
|
79
|
-
async def refresh(self) ->
|
|
79
|
+
async def refresh(self) -> None:
|
|
80
80
|
"""Refresh the contents of this trait."""
|
|
81
81
|
response = await self.rpc_channel.send_command(self.command)
|
|
82
82
|
new_data = self._parse_response(response)
|
|
@@ -84,7 +84,6 @@ class V1TraitMixin(ABC):
|
|
|
84
84
|
raise ValueError(f"Internal error, unexpected response type: {new_data!r}")
|
|
85
85
|
_LOGGER.debug("Refreshed %s: %s", self.__class__.__name__, new_data)
|
|
86
86
|
self._update_trait_values(new_data)
|
|
87
|
-
return self
|
|
88
87
|
|
|
89
88
|
def _update_trait_values(self, new_data: RoborockBase) -> None:
|
|
90
89
|
"""Update the values of this trait from another instance."""
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from dataclasses import fields
|
|
2
|
-
from typing import Self
|
|
3
2
|
|
|
4
3
|
from roborock.data import AppInitStatus, RoborockProductNickname
|
|
5
4
|
from roborock.device_features import DeviceFeatures
|
|
@@ -13,7 +12,7 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
|
|
|
13
12
|
|
|
14
13
|
command = RoborockCommand.APP_GET_INIT_STATUS
|
|
15
14
|
|
|
16
|
-
def __init__(self, product_nickname: RoborockProductNickname, cache: Cache) -> None:
|
|
15
|
+
def __init__(self, product_nickname: RoborockProductNickname, cache: Cache) -> None: # pylint: disable=super-init-not-called
|
|
17
16
|
"""Initialize MapContentTrait."""
|
|
18
17
|
self._nickname = product_nickname
|
|
19
18
|
self._cache = cache
|
|
@@ -22,7 +21,7 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
|
|
|
22
21
|
for field in fields(self):
|
|
23
22
|
setattr(self, field.name, False)
|
|
24
23
|
|
|
25
|
-
async def refresh(self) ->
|
|
24
|
+
async def refresh(self) -> None:
|
|
26
25
|
"""Refresh the contents of this trait.
|
|
27
26
|
|
|
28
27
|
This will use cached device features if available since they do not
|
|
@@ -32,12 +31,11 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
|
|
|
32
31
|
cache_data = await self._cache.get()
|
|
33
32
|
if cache_data.device_features is not None:
|
|
34
33
|
self._update_trait_values(cache_data.device_features)
|
|
35
|
-
return
|
|
34
|
+
return
|
|
36
35
|
# Save cached device features
|
|
37
|
-
|
|
38
|
-
cache_data.device_features =
|
|
36
|
+
await super().refresh()
|
|
37
|
+
cache_data.device_features = self
|
|
39
38
|
await self._cache.set(cache_data)
|
|
40
|
-
return device_features
|
|
41
39
|
|
|
42
40
|
def _parse_response(self, response: common.V1ResponseData) -> DeviceFeatures:
|
|
43
41
|
"""Parse the response from the device into a MapContentTrait instance."""
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
@@ -17,7 +17,7 @@ class DoNotDisturbTrait(DnDTimer, common.V1TraitMixin, common.RoborockSwitchBase
|
|
|
17
17
|
|
|
18
18
|
async def set_dnd_timer(self, dnd_timer: DnDTimer) -> None:
|
|
19
19
|
"""Set the Do Not Disturb (DND) timer settings of the device."""
|
|
20
|
-
await self.rpc_channel.send_command(RoborockCommand.SET_DND_TIMER, params=dnd_timer.
|
|
20
|
+
await self.rpc_channel.send_command(RoborockCommand.SET_DND_TIMER, params=dnd_timer.as_list())
|
|
21
21
|
|
|
22
22
|
async def clear_dnd_timer(self) -> None:
|
|
23
23
|
"""Clear the Do Not Disturb (DND) timer settings of the device."""
|
|
@@ -27,18 +27,9 @@ class DoNotDisturbTrait(DnDTimer, common.V1TraitMixin, common.RoborockSwitchBase
|
|
|
27
27
|
"""Set the Do Not Disturb (DND) timer settings of the device."""
|
|
28
28
|
await self.rpc_channel.send_command(
|
|
29
29
|
RoborockCommand.SET_DND_TIMER,
|
|
30
|
-
params=
|
|
31
|
-
**self.as_dict(),
|
|
32
|
-
_ENABLED_PARAM: 1,
|
|
33
|
-
},
|
|
30
|
+
params=self.as_list(),
|
|
34
31
|
)
|
|
35
32
|
|
|
36
33
|
async def disable(self) -> None:
|
|
37
|
-
"""
|
|
38
|
-
await self.rpc_channel.send_command(
|
|
39
|
-
RoborockCommand.SET_DND_TIMER,
|
|
40
|
-
params={
|
|
41
|
-
**self.as_dict(),
|
|
42
|
-
_ENABLED_PARAM: 0,
|
|
43
|
-
},
|
|
44
|
-
)
|
|
34
|
+
"""Disable the Do Not Disturb (DND) timer settings of the device."""
|
|
35
|
+
await self.rpc_channel.send_command(RoborockCommand.CLOSE_DND_TIMER)
|
|
@@ -158,7 +158,7 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
|
|
|
158
158
|
home_map_info[map_info.map_flag] = combined_map_info
|
|
159
159
|
return home_map_info, home_map_content
|
|
160
160
|
|
|
161
|
-
async def refresh(self) ->
|
|
161
|
+
async def refresh(self) -> None:
|
|
162
162
|
"""Refresh current map's underlying map and room data, updating cache as needed.
|
|
163
163
|
|
|
164
164
|
This will only refresh the current map's data and will not populate non
|
|
@@ -171,7 +171,7 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
|
|
|
171
171
|
# then we'll fall through below to refresh the current map only.
|
|
172
172
|
try:
|
|
173
173
|
await self.discover_home()
|
|
174
|
-
return
|
|
174
|
+
return
|
|
175
175
|
except RoborockDeviceBusy:
|
|
176
176
|
_LOGGER.debug("Cannot refresh home data while device is busy cleaning")
|
|
177
177
|
|
|
@@ -189,7 +189,6 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
|
|
|
189
189
|
await self._update_current_map(
|
|
190
190
|
map_flag, combined_map_info, new_map_content, update_cache=self._discovery_completed
|
|
191
191
|
)
|
|
192
|
-
return self
|
|
193
192
|
|
|
194
193
|
@property
|
|
195
194
|
def home_map_info(self) -> dict[int, CombinedMapInfo] | None:
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
from typing import Self
|
|
7
6
|
|
|
8
7
|
from roborock.data import NetworkInfo
|
|
9
8
|
from roborock.devices.cache import Cache
|
|
@@ -25,20 +24,20 @@ class NetworkInfoTrait(NetworkInfo, common.V1TraitMixin):
|
|
|
25
24
|
|
|
26
25
|
command = RoborockCommand.GET_NETWORK_INFO
|
|
27
26
|
|
|
28
|
-
def __init__(self, device_uid: str, cache: Cache) -> None:
|
|
27
|
+
def __init__(self, device_uid: str, cache: Cache) -> None: # pylint: disable=super-init-not-called
|
|
29
28
|
"""Initialize the trait."""
|
|
30
29
|
self._device_uid = device_uid
|
|
31
30
|
self._cache = cache
|
|
32
31
|
self.ip = ""
|
|
33
32
|
|
|
34
|
-
async def refresh(self) ->
|
|
33
|
+
async def refresh(self) -> None:
|
|
35
34
|
"""Refresh the network info from the cache."""
|
|
36
35
|
|
|
37
36
|
cache_data = await self._cache.get()
|
|
38
37
|
if cache_data.network_info and (network_info := cache_data.network_info.get(self._device_uid)):
|
|
39
38
|
_LOGGER.debug("Using cached network info for device %s", self._device_uid)
|
|
40
39
|
self._update_trait_values(network_info)
|
|
41
|
-
return
|
|
40
|
+
return
|
|
42
41
|
|
|
43
42
|
# Load from device if not in cache
|
|
44
43
|
_LOGGER.debug("No cached network info for device %s, fetching from device", self._device_uid)
|
|
@@ -48,8 +47,6 @@ class NetworkInfoTrait(NetworkInfo, common.V1TraitMixin):
|
|
|
48
47
|
cache_data.network_info[self._device_uid] = self
|
|
49
48
|
await self._cache.set(cache_data)
|
|
50
49
|
|
|
51
|
-
return self
|
|
52
|
-
|
|
53
50
|
def _parse_response(self, response: common.V1ResponseData) -> NetworkInfo:
|
|
54
51
|
"""Parse the response from the device into a NetworkInfo."""
|
|
55
52
|
if not isinstance(response, dict):
|
|
@@ -18,7 +18,7 @@ class ValleyElectricityTimerTrait(ValleyElectricityTimer, common.V1TraitMixin, c
|
|
|
18
18
|
|
|
19
19
|
async def set_timer(self, timer: ValleyElectricityTimer) -> None:
|
|
20
20
|
"""Set the Valley Electricity Timer settings of the device."""
|
|
21
|
-
await self.rpc_channel.send_command(RoborockCommand.SET_VALLEY_ELECTRICITY_TIMER, params=timer.
|
|
21
|
+
await self.rpc_channel.send_command(RoborockCommand.SET_VALLEY_ELECTRICITY_TIMER, params=timer.as_list())
|
|
22
22
|
|
|
23
23
|
async def clear_timer(self) -> None:
|
|
24
24
|
"""Clear the Valley Electricity Timer settings of the device."""
|
|
@@ -28,18 +28,11 @@ class ValleyElectricityTimerTrait(ValleyElectricityTimer, common.V1TraitMixin, c
|
|
|
28
28
|
"""Enable the Valley Electricity Timer settings of the device."""
|
|
29
29
|
await self.rpc_channel.send_command(
|
|
30
30
|
RoborockCommand.SET_VALLEY_ELECTRICITY_TIMER,
|
|
31
|
-
params=
|
|
32
|
-
**self.as_dict(),
|
|
33
|
-
_ENABLED_PARAM: 1,
|
|
34
|
-
},
|
|
31
|
+
params=self.as_list(),
|
|
35
32
|
)
|
|
36
33
|
|
|
37
34
|
async def disable(self) -> None:
|
|
38
35
|
"""Disable the Valley Electricity Timer settings of the device."""
|
|
39
36
|
await self.rpc_channel.send_command(
|
|
40
|
-
RoborockCommand.
|
|
41
|
-
params={
|
|
42
|
-
**self.as_dict(),
|
|
43
|
-
_ENABLED_PARAM: 0,
|
|
44
|
-
},
|
|
37
|
+
RoborockCommand.CLOSE_VALLEY_ELECTRICITY_TIMER,
|
|
45
38
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/data/b01_q7/b01_q7_code_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/flow_led_status.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/smart_wash_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/devices/traits/v1/wash_towel_mode.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_1_apis/roborock_local_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.7.2 → python_roborock-3.8.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|