python-roborock 5.29.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.29.0 → python_roborock-5.31.0}/PKG-INFO +1 -1
- {python_roborock-5.29.0 → python_roborock-5.31.0}/pyproject.toml +1 -1
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/containers.py +19 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/v1/v1_containers.py +27 -7
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/__init__.py +1 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/device_features.py +45 -18
- {python_roborock-5.29.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.29.0 → python_roborock-5.31.0}/roborock/testing/channel.py +17 -2
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/testing/cloud.py +16 -9
- {python_roborock-5.29.0 → python_roborock-5.31.0}/.gitignore +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/LICENSE +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/README.md +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/callbacks.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/cli.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/const.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/code_mappings.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/mower/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/mower/mower_containers.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/device_features.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/README.md +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/cache.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/device.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/file_cache.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/rpc/a01_channel.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/rpc/b01_q7_channel.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/button_light.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/common.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/consumable.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/map.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/network_info.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/remote.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/status.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/volume.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/map.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/map_content.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/common.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/diagnostics.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/exceptions.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/b01_grid_layers.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/b01_map_parser.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/b01_q10_map_parser.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/b01_q10_overlays.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/proto/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/proto/b01_scmap.proto +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/map/proto/b01_scmap_pb2.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/protocol.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/protocols/__init__.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/protocols/b01_q7_protocol.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/py.typed +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/roborock_message.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/testing/simulator.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/testing/v1_simulator.py +0 -0
- {python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/util.py +0 -0
- {python_roborock-5.29.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"
|
|
@@ -60,6 +60,25 @@ def _attr_repr(obj: Any) -> str:
|
|
|
60
60
|
return f"{type(obj).__name__}({', '.join(parts)})"
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def field_metadata(**kwargs):
|
|
64
|
+
"""Decorator to attach capability check metadata to a property.
|
|
65
|
+
|
|
66
|
+
This attaches a `_field_metadata` dictionary to the underlying getter function,
|
|
67
|
+
which is then preserved when decorated with `@property`.
|
|
68
|
+
|
|
69
|
+
Supported metadata keys:
|
|
70
|
+
- `feature` (str): Name of a capability property on `DeviceFeaturesTrait`.
|
|
71
|
+
- `dock_feature` (str): Name of a capability property on `RoborockDockFeatures`.
|
|
72
|
+
- `dps` (str/int): RoborockDataProtocol ID to check against supported schema IDs.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def decorator(func):
|
|
76
|
+
func._field_metadata = kwargs
|
|
77
|
+
return func
|
|
78
|
+
|
|
79
|
+
return decorator
|
|
80
|
+
|
|
81
|
+
|
|
63
82
|
@dataclass(repr=False)
|
|
64
83
|
class RoborockBase:
|
|
65
84
|
"""Base class for all Roborock data classes."""
|
|
@@ -39,7 +39,7 @@ from roborock.const import (
|
|
|
39
39
|
from roborock.exceptions import RoborockException
|
|
40
40
|
from roborock.roborock_message import RoborockDataProtocol
|
|
41
41
|
|
|
42
|
-
from ..containers import NamedRoomMapping, RoborockBase, RoborockBaseTimer, _attr_repr
|
|
42
|
+
from ..containers import NamedRoomMapping, RoborockBase, RoborockBaseTimer, _attr_repr, field_metadata
|
|
43
43
|
from .v1_clean_modes import WashTowelModes
|
|
44
44
|
from .v1_code_mappings import (
|
|
45
45
|
CleanFluidStatus,
|
|
@@ -117,6 +117,15 @@ class StatusField(FieldNameBase):
|
|
|
117
117
|
CHARGE_STATUS = "charge_status"
|
|
118
118
|
DRY_STATUS = "dry_status"
|
|
119
119
|
ERROR_CODE = "error_code"
|
|
120
|
+
WATER_BOX_CARRIAGE_STATUS = "water_box_carriage_status"
|
|
121
|
+
WATER_BOX_STATUS = "water_box_status"
|
|
122
|
+
WATER_SHORTAGE_STATUS = "water_shortage_status"
|
|
123
|
+
DIRTY_WATER_BOX_STATUS = "dirty_water_box_status"
|
|
124
|
+
CLEAR_WATER_BOX_STATUS = "clear_water_box_status"
|
|
125
|
+
CLEAN_FLUID_STATUS = "clean_fluid_status"
|
|
126
|
+
CLEAN_PERCENT = "clean_percent"
|
|
127
|
+
DOCK_ERROR_STATUS = "dock_error_status"
|
|
128
|
+
RDT = "rdt"
|
|
120
129
|
|
|
121
130
|
|
|
122
131
|
@dataclass
|
|
@@ -246,12 +255,14 @@ class Status(RoborockBase):
|
|
|
246
255
|
return (self.dss & 3) == 2
|
|
247
256
|
|
|
248
257
|
@property
|
|
258
|
+
@field_metadata(dock_feature="is_washable")
|
|
249
259
|
def clear_water_box_status(self) -> ClearWaterBoxStatus | None:
|
|
250
260
|
if self.dss:
|
|
251
261
|
return ClearWaterBoxStatus((self.dss >> 2) & 3)
|
|
252
262
|
return None
|
|
253
263
|
|
|
254
264
|
@property
|
|
265
|
+
@field_metadata(dock_feature="is_washable")
|
|
255
266
|
def dirty_water_box_status(self) -> DirtyWaterBoxStatus | None:
|
|
256
267
|
if self.dss:
|
|
257
268
|
return DirtyWaterBoxStatus((self.dss >> 4) & 3)
|
|
@@ -270,6 +281,9 @@ class Status(RoborockBase):
|
|
|
270
281
|
return None
|
|
271
282
|
|
|
272
283
|
@property
|
|
284
|
+
@field_metadata(
|
|
285
|
+
dock_feature="is_clean_fluid_auto_delivery_supported",
|
|
286
|
+
)
|
|
273
287
|
def clean_fluid_status(self) -> CleanFluidStatus | None:
|
|
274
288
|
if self.dss:
|
|
275
289
|
value = (self.dss >> 10) & 3
|
|
@@ -313,7 +327,7 @@ class StatusV2(RoborockBase):
|
|
|
313
327
|
in_returning: int | None = None
|
|
314
328
|
in_fresh_state: int | None = None
|
|
315
329
|
lab_status: int | None = None
|
|
316
|
-
water_box_status: int | None = None
|
|
330
|
+
water_box_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
|
|
317
331
|
back_type: int | None = None
|
|
318
332
|
wash_phase: int | None = None
|
|
319
333
|
wash_ready: int | None = None
|
|
@@ -323,14 +337,14 @@ class StatusV2(RoborockBase):
|
|
|
323
337
|
is_locating: int | None = None
|
|
324
338
|
lock_status: int | None = None
|
|
325
339
|
water_box_mode: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.WATER_BOX_MODE})
|
|
326
|
-
water_box_carriage_status: int | None = None
|
|
340
|
+
water_box_carriage_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
|
|
327
341
|
mop_forbidden_enable: int | None = None
|
|
328
342
|
camera_status: int | None = None
|
|
329
343
|
is_exploring: int | None = None
|
|
330
344
|
home_sec_status: int | None = None
|
|
331
345
|
home_sec_enable_password: int | None = None
|
|
332
346
|
adbumper_status: list[int] | None = None
|
|
333
|
-
water_shortage_status: int | None = None
|
|
347
|
+
water_shortage_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
|
|
334
348
|
dock_type: RoborockDockTypeCode | None = None
|
|
335
349
|
dust_collection_status: int | None = None
|
|
336
350
|
auto_dust_collection: int | None = None
|
|
@@ -339,7 +353,8 @@ class StatusV2(RoborockBase):
|
|
|
339
353
|
debug_mode: int | None = None
|
|
340
354
|
collision_avoid_status: int | None = None
|
|
341
355
|
switch_map_mode: int | None = None
|
|
342
|
-
dock_error_status: RoborockDockErrorCode | None = None
|
|
356
|
+
dock_error_status: RoborockDockErrorCode | None = field(default=None, metadata={"dock_feature": "has_dock"})
|
|
357
|
+
|
|
343
358
|
charge_status: RoborockChargeStatus | None = field(
|
|
344
359
|
default=None, metadata={"dps": RoborockDataProtocol.CHARGE_STATUS}
|
|
345
360
|
)
|
|
@@ -349,8 +364,8 @@ class StatusV2(RoborockBase):
|
|
|
349
364
|
distance_off: int | None = None
|
|
350
365
|
in_warmup: int | None = None
|
|
351
366
|
dry_status: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.DRYING_STATUS})
|
|
352
|
-
rdt: int | None = None
|
|
353
|
-
clean_percent: int | None = None
|
|
367
|
+
rdt: int | None = field(default=None, metadata={"feature": "is_supported_drying"})
|
|
368
|
+
clean_percent: int | None = field(default=None, metadata={"feature": "is_support_clean_estimate"})
|
|
354
369
|
rss: int | None = None
|
|
355
370
|
dss: int | None = None
|
|
356
371
|
common_status: int | None = None
|
|
@@ -389,12 +404,14 @@ class StatusV2(RoborockBase):
|
|
|
389
404
|
return (self.dss & 3) == 2
|
|
390
405
|
|
|
391
406
|
@property
|
|
407
|
+
@field_metadata(dock_feature="is_washable")
|
|
392
408
|
def clear_water_box_status(self) -> ClearWaterBoxStatus | None:
|
|
393
409
|
if self.dss:
|
|
394
410
|
return ClearWaterBoxStatus((self.dss >> 2) & 3)
|
|
395
411
|
return None
|
|
396
412
|
|
|
397
413
|
@property
|
|
414
|
+
@field_metadata(dock_feature="is_washable")
|
|
398
415
|
def dirty_water_box_status(self) -> DirtyWaterBoxStatus | None:
|
|
399
416
|
if self.dss:
|
|
400
417
|
return DirtyWaterBoxStatus((self.dss >> 4) & 3)
|
|
@@ -413,6 +430,9 @@ class StatusV2(RoborockBase):
|
|
|
413
430
|
return None
|
|
414
431
|
|
|
415
432
|
@property
|
|
433
|
+
@field_metadata(
|
|
434
|
+
dock_feature="is_clean_fluid_auto_delivery_supported",
|
|
435
|
+
)
|
|
416
436
|
def clean_fluid_status(self) -> CleanFluidStatus | None:
|
|
417
437
|
if self.dss:
|
|
418
438
|
value = (self.dss >> 10) & 3
|
|
@@ -264,6 +264,7 @@ class PropertiesApi(Trait):
|
|
|
264
264
|
# Dock type also acts like a device feature for some traits.
|
|
265
265
|
dock_type = await self._dock_type()
|
|
266
266
|
dock_features = RoborockDockFeatures.from_dock_type(dock_type, has_am=self.status.has_am)
|
|
267
|
+
self.device_features.dock_features = dock_features
|
|
267
268
|
|
|
268
269
|
# Initialize traits with special arguments before the generic loop
|
|
269
270
|
if self.wash_towel_mode is None and self._is_supported(WashTowelModeTrait, "wash_towel_mode", dock_features):
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
from dataclasses import
|
|
1
|
+
from dataclasses import fields
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
4
|
from roborock.data import AppInitStatus, HomeDataProduct, RoborockBase
|
|
5
|
+
from roborock.data.v1 import RoborockDockTypeCode
|
|
4
6
|
from roborock.data.v1.v1_containers import FieldNameBase
|
|
5
|
-
from roborock.device_features import DeviceFeatures
|
|
7
|
+
from roborock.device_features import DeviceFeatures, RoborockDockFeatures
|
|
6
8
|
from roborock.devices.cache import DeviceCache
|
|
7
9
|
from roborock.devices.traits.v1 import common
|
|
8
10
|
from roborock.roborock_typing import RoborockCommand
|
|
9
11
|
|
|
12
|
+
# Cache of metadata for each trait class
|
|
13
|
+
_metadata_cache: dict[type[RoborockBase], dict[str, dict[str, Any]]] = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_field_metadata(cls: type[RoborockBase]) -> dict[str, Any]:
|
|
17
|
+
"""Helper to get metadata from either class properties or dataclass fields."""
|
|
18
|
+
if cls not in _metadata_cache:
|
|
19
|
+
metadata_map = {}
|
|
20
|
+
# Inspect properties with @field_metadata
|
|
21
|
+
for name in dir(cls):
|
|
22
|
+
prop = getattr(cls, name, None)
|
|
23
|
+
if isinstance(prop, property):
|
|
24
|
+
metadata_map[name] = getattr(prop.fget, "_field_metadata", {})
|
|
25
|
+
# Inspect dataclass fields metadata
|
|
26
|
+
for f in fields(cls):
|
|
27
|
+
metadata_map[f.name] = f.metadata
|
|
28
|
+
_metadata_cache[cls] = metadata_map
|
|
29
|
+
return _metadata_cache[cls]
|
|
30
|
+
|
|
10
31
|
|
|
11
32
|
class DeviceTraitsConverter(common.V1TraitDataConverter):
|
|
12
33
|
"""Converter for APP_GET_INIT_STATUS responses into DeviceFeatures."""
|
|
@@ -40,31 +61,37 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
|
|
|
40
61
|
self.converter = DeviceTraitsConverter(product)
|
|
41
62
|
self._product = product
|
|
42
63
|
self._device_cache = device_cache
|
|
64
|
+
# Dock features are populated after device feature discovery
|
|
65
|
+
# is triggered.
|
|
66
|
+
self.dock_features: RoborockDockFeatures = RoborockDockFeatures.from_dock_type(RoborockDockTypeCode.o0_dock)
|
|
43
67
|
# All fields of DeviceFeatures are required. Initialize them to False
|
|
44
68
|
# so we have some known state.
|
|
45
69
|
for field in fields(self):
|
|
46
70
|
setattr(self, field.name, False)
|
|
47
71
|
|
|
48
|
-
@staticmethod
|
|
49
|
-
def _get_dataclass_field(cls: type[RoborockBase], field_name: FieldNameBase) -> Field:
|
|
50
|
-
"""Look up a dataclass field by its FieldNameBase name."""
|
|
51
|
-
for f in fields(cls):
|
|
52
|
-
if f.name == field_name:
|
|
53
|
-
return f
|
|
54
|
-
raise ValueError(f"Field {field_name!r} not found in {cls}")
|
|
55
|
-
|
|
56
72
|
def is_field_supported(self, cls: type[RoborockBase], field_name: FieldNameBase) -> bool:
|
|
57
73
|
"""Determines if the specified field is supported by this device.
|
|
58
74
|
|
|
59
|
-
We
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
We inspect the metadata defined for the field (either via dataclass field metadata
|
|
76
|
+
or the `@field_metadata` decorator on properties). Supported checks include:
|
|
77
|
+
|
|
78
|
+
- `feature`: Maps to a boolean capability property on `DeviceFeatures` / `DeviceFeaturesTrait`
|
|
79
|
+
(e.g. `is_support_water_mode`).
|
|
80
|
+
- `dock_feature`: Maps to a boolean capability property on `RoborockDockFeatures` (e.g. `is_washable`).
|
|
81
|
+
- `dps`: Maps to a `RoborockDataProtocol` ID checked against the product's supported schema IDs.
|
|
62
82
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
if self.dock_features is None:
|
|
84
|
+
raise ValueError("DeviceFeaturesTrait was invoked but was not fully initialized")
|
|
85
|
+
metadata_map = _get_field_metadata(cls)
|
|
86
|
+
if (field_metadata := metadata_map.get(field_name)) is not None:
|
|
87
|
+
if (feature := field_metadata.get("feature")) is not None:
|
|
88
|
+
return getattr(self, feature, False)
|
|
89
|
+
if (dock_feature := field_metadata.get("dock_feature")) is not None:
|
|
90
|
+
return getattr(self.dock_features, dock_feature, False)
|
|
91
|
+
if (dps := field_metadata.get("dps")) is not None:
|
|
92
|
+
return int(dps) in self._product.supported_schema_ids
|
|
93
|
+
# No metadata, field is assumed always supported
|
|
94
|
+
return True
|
|
68
95
|
|
|
69
96
|
async def refresh(self) -> None:
|
|
70
97
|
"""Refresh the contents of this trait.
|
|
@@ -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.29.0 → python_roborock-5.31.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/data/b01_q10/b01_q10_containers.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-5.29.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
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/__init__.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/button_light.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/child_lock.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/clean_history.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/command.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/consumable.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q10/dust_collection.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-5.29.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.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/__init__.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/b01/q7/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-5.29.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
|
{python_roborock-5.29.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.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-5.29.0 → python_roborock-5.31.0}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-5.29.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.29.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.29.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.29.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.29.0 → python_roborock-5.31.0}/roborock/devices/transport/local_channel.py
RENAMED
|
File without changes
|
{python_roborock-5.29.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
|