python-roborock 5.30.0__tar.gz → 5.31.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-5.30.0 → python_roborock-5.31.0}/PKG-INFO +1 -1
- {python_roborock-5.30.0 → python_roborock-5.31.0}/pyproject.toml +1 -1
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/testing/__init__.py +6 -0
- python_roborock-5.31.0/roborock/testing/b01_q10_simulator.py +213 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/testing/channel.py +17 -2
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/testing/cloud.py +16 -9
- {python_roborock-5.30.0 → python_roborock-5.31.0}/.gitignore +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/LICENSE +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/README.md +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/callbacks.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/cli.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/const.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/code_mappings.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/containers.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/mower/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/mower/mower_containers.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/device_features.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/README.md +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/cache.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/device.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/file_cache.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/rpc/a01_channel.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/rpc/b01_q7_channel.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/button_light.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/common.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/consumable.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/map.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/network_info.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/remote.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/status.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/volume.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/map.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/map_content.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/common.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/diagnostics.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/exceptions.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/b01_grid_layers.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/b01_map_parser.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/b01_q10_map_parser.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/b01_q10_overlays.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/proto/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/proto/b01_scmap.proto +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/map/proto/b01_scmap_pb2.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/protocol.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/protocols/__init__.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/protocols/b01_q7_protocol.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/py.typed +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/roborock_message.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/testing/simulator.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/testing/v1_simulator.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/util.py +0 -0
- {python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.31.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
Project-URL: Repository, https://github.com/python-roborock/python-roborock
|
|
6
6
|
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "5.
|
|
3
|
+
version = "5.31.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"
|
|
@@ -63,6 +63,10 @@ async def test_start_vacuum_service():
|
|
|
63
63
|
```
|
|
64
64
|
"""
|
|
65
65
|
|
|
66
|
+
from roborock.testing.b01_q10_simulator import (
|
|
67
|
+
DEFAULT_Q10_STATUS,
|
|
68
|
+
Q10VacuumSimulator,
|
|
69
|
+
)
|
|
66
70
|
from roborock.testing.channel import FakeChannel
|
|
67
71
|
from roborock.testing.cloud import FakeRoborockCloud, FakeWebApiClient
|
|
68
72
|
from roborock.testing.simulator import (
|
|
@@ -92,10 +96,12 @@ __all__ = [
|
|
|
92
96
|
"DEFAULT_LOCAL_KEY",
|
|
93
97
|
"DEFAULT_NETWORK_INFO",
|
|
94
98
|
"DEFAULT_PRODUCT_ID",
|
|
99
|
+
"DEFAULT_Q10_STATUS",
|
|
95
100
|
"DEFAULT_STATUS",
|
|
96
101
|
"FakeChannel",
|
|
97
102
|
"FakeRoborockCloud",
|
|
98
103
|
"FakeWebApiClient",
|
|
104
|
+
"Q10VacuumSimulator",
|
|
99
105
|
"RoborockDeviceSimulator",
|
|
100
106
|
"V1VacuumSimulator",
|
|
101
107
|
]
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Stateful simulator for Roborock Q10 (B01) devices."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from roborock.data import HomeDataDevice, HomeDataProduct, RoborockCategory
|
|
9
|
+
from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
|
|
10
|
+
from roborock.testing.simulator import RoborockDeviceSimulator
|
|
11
|
+
|
|
12
|
+
_LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
B01_VERSION = b"B01"
|
|
15
|
+
DEFAULT_PRODUCT_ID = "product-id-q10"
|
|
16
|
+
|
|
17
|
+
DEFAULT_Q10_STATUS = {
|
|
18
|
+
101: {
|
|
19
|
+
104: 0,
|
|
20
|
+
105: False,
|
|
21
|
+
109: "us",
|
|
22
|
+
207: 0,
|
|
23
|
+
25: 1,
|
|
24
|
+
26: 74,
|
|
25
|
+
29: 0,
|
|
26
|
+
30: 0,
|
|
27
|
+
31: 0,
|
|
28
|
+
37: 1,
|
|
29
|
+
40: 1,
|
|
30
|
+
45: 0,
|
|
31
|
+
47: 0,
|
|
32
|
+
50: 0,
|
|
33
|
+
51: True,
|
|
34
|
+
53: False,
|
|
35
|
+
6: 0,
|
|
36
|
+
60: 1,
|
|
37
|
+
67: 0,
|
|
38
|
+
7: 0,
|
|
39
|
+
76: 0,
|
|
40
|
+
78: 0,
|
|
41
|
+
79: {"timeZoneCity": "America/Los_Angeles", "timeZoneSec": -28800},
|
|
42
|
+
80: 0,
|
|
43
|
+
81: {"ipAdress": "1.1.1.2", "mac": "99:AA:88:BB:77:CC", "signal": -50, "wifiName": "wifi-network-name"},
|
|
44
|
+
83: 1,
|
|
45
|
+
86: 1,
|
|
46
|
+
87: 100,
|
|
47
|
+
88: 0,
|
|
48
|
+
90: 0,
|
|
49
|
+
92: {"disturb_dust_enable": 1, "disturb_light": 1, "disturb_resume_clean": 1, "disturb_voice": 1},
|
|
50
|
+
93: 1,
|
|
51
|
+
96: 0,
|
|
52
|
+
},
|
|
53
|
+
121: 8,
|
|
54
|
+
122: 100,
|
|
55
|
+
123: 2,
|
|
56
|
+
124: 1,
|
|
57
|
+
125: 0,
|
|
58
|
+
126: 0,
|
|
59
|
+
127: 0,
|
|
60
|
+
136: 1,
|
|
61
|
+
137: 1,
|
|
62
|
+
138: 0,
|
|
63
|
+
139: 5,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Q10VacuumSimulator(RoborockDeviceSimulator):
|
|
68
|
+
"""Stateful firmware simulator for Roborock Q10 (B01 ss07) devices."""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
duid: str,
|
|
73
|
+
status: dict[int, Any] | None = None,
|
|
74
|
+
device_info: HomeDataDevice | None = None,
|
|
75
|
+
product: HomeDataProduct | None = None,
|
|
76
|
+
):
|
|
77
|
+
product = product or HomeDataProduct(
|
|
78
|
+
id=DEFAULT_PRODUCT_ID,
|
|
79
|
+
name="Roborock Q10",
|
|
80
|
+
model="roborock.vacuum.ss07",
|
|
81
|
+
category=RoborockCategory.VACUUM,
|
|
82
|
+
)
|
|
83
|
+
device_info = device_info or HomeDataDevice(
|
|
84
|
+
duid=duid,
|
|
85
|
+
name="Roborock Q10",
|
|
86
|
+
local_key="fake_localkey_16bytes",
|
|
87
|
+
product_id=product.id,
|
|
88
|
+
sn="fake_serial_number",
|
|
89
|
+
pv="B01",
|
|
90
|
+
)
|
|
91
|
+
super().__init__(duid, device_info, product, has_local_channel=False)
|
|
92
|
+
self.status = copy.deepcopy(status or DEFAULT_Q10_STATUS)
|
|
93
|
+
|
|
94
|
+
async def _handle_publish(self, message: RoborockMessage, channel: Any) -> None:
|
|
95
|
+
"""Process incoming Q10 RPC command payload."""
|
|
96
|
+
if not message.payload:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
payload = json.loads(message.payload.decode())
|
|
101
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as ex:
|
|
102
|
+
_LOGGER.warning("Simulator failed to parse incoming B01 payload: %s", ex)
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
datapoints = payload.get("dps", {})
|
|
106
|
+
if not isinstance(datapoints, dict):
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
updated_dps: dict[int, Any] = {}
|
|
110
|
+
|
|
111
|
+
for code_str, params in datapoints.items():
|
|
112
|
+
try:
|
|
113
|
+
code = int(code_str)
|
|
114
|
+
except ValueError:
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
# Command: REQUEST_DPS (102) -> trigger status push
|
|
118
|
+
if code == 102:
|
|
119
|
+
# REQUEST_DPS triggers a dump of all status properties
|
|
120
|
+
# (both root properties and nested properties under 101)
|
|
121
|
+
self.trigger_push_update()
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Command: START_CLEAN (201)
|
|
125
|
+
elif code == 201:
|
|
126
|
+
# 1 = sweep/mop, cmd inside dict = segment clean
|
|
127
|
+
self.status[121] = 5 # YXDeviceState.CLEANING
|
|
128
|
+
if isinstance(params, dict) and "cmd" in params:
|
|
129
|
+
self.status[138] = params.get("cmd", 0) # clean_task_type
|
|
130
|
+
else:
|
|
131
|
+
self.status[138] = 1 # CLEANING
|
|
132
|
+
updated_dps[121] = self.status[121]
|
|
133
|
+
updated_dps[138] = self.status[138]
|
|
134
|
+
|
|
135
|
+
# Command: START_BACK (202)
|
|
136
|
+
elif code == 202:
|
|
137
|
+
# 5 = returning to charge
|
|
138
|
+
self.status[121] = 6 # YXDeviceState.RETURNING_HOME
|
|
139
|
+
self.status[139] = 5 # BACK_CHARGING
|
|
140
|
+
updated_dps[121] = self.status[121]
|
|
141
|
+
updated_dps[139] = self.status[139]
|
|
142
|
+
|
|
143
|
+
# Command: START_DOCK_TASK (203)
|
|
144
|
+
elif code == 203:
|
|
145
|
+
# Empty dustbin
|
|
146
|
+
self.status[121] = 22 # EMPTYING_THE_BIN
|
|
147
|
+
updated_dps[121] = self.status[121]
|
|
148
|
+
|
|
149
|
+
# Command: PAUSE (204)
|
|
150
|
+
elif code == 204:
|
|
151
|
+
self.status[121] = 10 # YXDeviceState.PAUSED
|
|
152
|
+
updated_dps[121] = self.status[121]
|
|
153
|
+
|
|
154
|
+
# Command: RESUME (205)
|
|
155
|
+
elif code == 205:
|
|
156
|
+
self.status[121] = 5 # YXDeviceState.CLEANING
|
|
157
|
+
updated_dps[121] = self.status[121]
|
|
158
|
+
|
|
159
|
+
# Command: STOP (206)
|
|
160
|
+
elif code == 206:
|
|
161
|
+
self.status[121] = 3 # YXDeviceState.IDLE
|
|
162
|
+
self.status[138] = 0 # YXDeviceCleanTask.UNKNOWN/IDLE
|
|
163
|
+
updated_dps[121] = self.status[121]
|
|
164
|
+
updated_dps[138] = self.status[138]
|
|
165
|
+
|
|
166
|
+
# Command: FAN_LEVEL (123)
|
|
167
|
+
elif code == 123:
|
|
168
|
+
self.status[123] = params
|
|
169
|
+
updated_dps[123] = params
|
|
170
|
+
|
|
171
|
+
# Command: WATER_LEVEL (124)
|
|
172
|
+
elif code == 124:
|
|
173
|
+
self.status[124] = params
|
|
174
|
+
updated_dps[124] = params
|
|
175
|
+
|
|
176
|
+
# Command: CLEAN_MODE (137)
|
|
177
|
+
elif code == 137:
|
|
178
|
+
self.status[137] = params
|
|
179
|
+
updated_dps[137] = params
|
|
180
|
+
|
|
181
|
+
# Custom settings properties routed to sub-traits (DND, child lock, volume, dust collections)
|
|
182
|
+
elif code in (26, 47, 25, 37, 50):
|
|
183
|
+
# These live in the nested dpCommon (101) dict
|
|
184
|
+
common_dict = self.status[101]
|
|
185
|
+
if isinstance(common_dict, dict):
|
|
186
|
+
common_dict[code] = params
|
|
187
|
+
# We return them nested under 101
|
|
188
|
+
if 101 not in updated_dps:
|
|
189
|
+
updated_dps[101] = {}
|
|
190
|
+
updated_dps[101][code] = params
|
|
191
|
+
|
|
192
|
+
if updated_dps:
|
|
193
|
+
# Send the changed datapoints back to the client
|
|
194
|
+
self.push_dps(updated_dps)
|
|
195
|
+
|
|
196
|
+
def push_dps(self, dps_updates: dict[int, Any]) -> None:
|
|
197
|
+
"""Push a set of status datapoint changes to the client."""
|
|
198
|
+
payload = {
|
|
199
|
+
"dps": {
|
|
200
|
+
str(k): ({str(sub_k): sub_v for sub_k, sub_v in v.items()} if isinstance(v, dict) and k == 101 else v)
|
|
201
|
+
for k, v in dps_updates.items()
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
message = RoborockMessage(
|
|
205
|
+
protocol=RoborockMessageProtocol.RPC_RESPONSE,
|
|
206
|
+
version=B01_VERSION,
|
|
207
|
+
payload=json.dumps(payload).encode("utf-8"),
|
|
208
|
+
)
|
|
209
|
+
self.mqtt_channel.notify_subscribers(message)
|
|
210
|
+
|
|
211
|
+
def trigger_push_update(self) -> None:
|
|
212
|
+
"""Send the full status dump to the client."""
|
|
213
|
+
self.push_dps(self.status)
|
|
@@ -5,7 +5,8 @@ subscription, and publishing logic at the message boundary. It acts as an
|
|
|
5
5
|
in-memory replacement for `MqttChannel` and `LocalChannel` during testing.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import asyncio
|
|
9
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
9
10
|
from typing import Any
|
|
10
11
|
from unittest.mock import AsyncMock, MagicMock
|
|
11
12
|
|
|
@@ -53,7 +54,7 @@ class FakeChannel(Channel):
|
|
|
53
54
|
self._is_local = is_local
|
|
54
55
|
|
|
55
56
|
# A callback to intercept published messages (e.g., bound simulator handler).
|
|
56
|
-
#
|
|
57
|
+
# Must be asynchronous: Callable[[RoborockMessage], Awaitable[Any]].
|
|
57
58
|
# By default, routes to self._default_publish_handler to handle the response_queue.
|
|
58
59
|
self.publish_handler: Callable[[RoborockMessage], Awaitable[Any]] | None = self._default_publish_handler
|
|
59
60
|
|
|
@@ -146,3 +147,17 @@ class FakeChannel(Channel):
|
|
|
146
147
|
self.publish.side_effect = self._publish
|
|
147
148
|
self.subscribe.side_effect = self._subscribe
|
|
148
149
|
self.connect.side_effect = self._connect
|
|
150
|
+
|
|
151
|
+
async def subscribe_stream(self) -> AsyncGenerator[RoborockMessage, None]:
|
|
152
|
+
"""Stream messages received via this channel."""
|
|
153
|
+
queue: asyncio.Queue[RoborockMessage] = asyncio.Queue()
|
|
154
|
+
|
|
155
|
+
def callback(message: RoborockMessage) -> None:
|
|
156
|
+
queue.put_nowait(message)
|
|
157
|
+
|
|
158
|
+
unsub = await self.subscribe(callback)
|
|
159
|
+
try:
|
|
160
|
+
while True:
|
|
161
|
+
yield await queue.get()
|
|
162
|
+
finally:
|
|
163
|
+
unsub()
|
|
@@ -16,6 +16,7 @@ from aioresponses import CallbackResult, aioresponses
|
|
|
16
16
|
from roborock.data import HomeData, Reference, RRiot, UserData
|
|
17
17
|
from roborock.devices.rpc.v1_channel import create_v1_channel as original_create_v1_channel
|
|
18
18
|
from roborock.devices.transport.mqtt_channel import create_mqtt_channel as original_create_mqtt_channel
|
|
19
|
+
from roborock.testing.b01_q10_simulator import Q10VacuumSimulator
|
|
19
20
|
from roborock.testing.simulator import RoborockDeviceSimulator
|
|
20
21
|
from roborock.testing.v1_simulator import V1VacuumSimulator
|
|
21
22
|
|
|
@@ -242,11 +243,6 @@ class FakeRoborockCloud:
|
|
|
242
243
|
|
|
243
244
|
# Wrapper function for create_v1_channel
|
|
244
245
|
def mock_create_v1_channel(user_data, mqtt_params, mqtt_session, device, device_cache):
|
|
245
|
-
if device.pv in ("A01", "B01"):
|
|
246
|
-
raise NotImplementedError(
|
|
247
|
-
f"Simulating protocol {device.pv} is not yet supported. "
|
|
248
|
-
"TODO: Implement stateful simulators for B01 (Q7/Q10) and A01 (Zeo/Dyad) devices."
|
|
249
|
-
)
|
|
250
246
|
server = self.simulated_devices.get(device.duid)
|
|
251
247
|
if server is not None:
|
|
252
248
|
if not isinstance(server, V1VacuumSimulator):
|
|
@@ -255,18 +251,29 @@ class FakeRoborockCloud:
|
|
|
255
251
|
f"simulator, but create_v1_channel requires a V1VacuumSimulator."
|
|
256
252
|
)
|
|
257
253
|
return server.v1_channel
|
|
254
|
+
if device.pv in ("A01", "B01"):
|
|
255
|
+
raise NotImplementedError(
|
|
256
|
+
f"Simulating protocol {device.pv} is not yet supported. "
|
|
257
|
+
"TODO: Implement stateful simulators for B01 (Q7) and A01 (Zeo/Dyad) devices."
|
|
258
|
+
)
|
|
258
259
|
return original_create_v1_channel(user_data, mqtt_params, mqtt_session, device, device_cache)
|
|
259
260
|
|
|
260
261
|
# Wrapper function for create_mqtt_channel
|
|
261
262
|
def mock_create_mqtt_channel(user_data, mqtt_params, mqtt_session, device):
|
|
263
|
+
server = self.simulated_devices.get(device.duid)
|
|
264
|
+
if server:
|
|
265
|
+
if device.pv == "B01" and not isinstance(server, Q10VacuumSimulator):
|
|
266
|
+
raise NotImplementedError(
|
|
267
|
+
f"Simulating protocol {device.pv} is not yet supported. "
|
|
268
|
+
"Only Q10VacuumSimulator is supported for B01 protocols currently."
|
|
269
|
+
)
|
|
270
|
+
server.mqtt_channel._is_connected = True
|
|
271
|
+
return server.mqtt_channel
|
|
262
272
|
if device.pv in ("A01", "B01"):
|
|
263
273
|
raise NotImplementedError(
|
|
264
274
|
f"Simulating protocol {device.pv} is not yet supported. "
|
|
265
|
-
"TODO: Implement stateful simulators for B01 (Q7
|
|
275
|
+
"TODO: Implement stateful simulators for B01 (Q7) and A01 (Zeo/Dyad) devices."
|
|
266
276
|
)
|
|
267
|
-
server = self.simulated_devices.get(device.duid)
|
|
268
|
-
if server:
|
|
269
|
-
return server.mqtt_channel
|
|
270
277
|
return original_create_mqtt_channel(user_data, mqtt_params, mqtt_session, device)
|
|
271
278
|
|
|
272
279
|
# Route Web requests using the dynamic FakeWebApiClient
|
|
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-5.30.0 → python_roborock-5.31.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/data/b01_q10/b01_q10_containers.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/__init__.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/button_light.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/child_lock.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/clean_history.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/command.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/consumable.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/dust_collection.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/network_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/__init__.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/map_content.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.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
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/network_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/smart_wash_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/wash_towel_mode.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/transport/local_channel.py
RENAMED
|
File without changes
|
{python_roborock-5.30.0 → python_roborock-5.31.0}/roborock/devices/transport/mqtt_channel.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|