python-roborock 6.2.1__tar.gz → 7.1.1__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-6.2.1 → python_roborock-7.1.1}/PKG-INFO +2 -2
- {python_roborock-6.2.1 → python_roborock-7.1.1}/pyproject.toml +1 -1
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/cli.py +29 -12
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/b01_q10/b01_q10_containers.py +22 -1
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/b01_q7/b01_q7_code_mappings.py +12 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/code_mappings.py +10 -3
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/containers.py +3 -2
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/mower/__init__.py +1 -0
- python_roborock-7.1.1/roborock/data/mower/mower_code_mappings.py +72 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/mower/mower_containers.py +29 -3
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/v1/v1_clean_modes.py +12 -18
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/zeo/zeo_code_mappings.py +11 -12
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/device_features.py +8 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/device.py +10 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/rpc/a01_channel.py +2 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/rpc/b01_q7_channel.py +23 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/a01/__init__.py +132 -6
- python_roborock-7.1.1/roborock/devices/traits/a01/device_feature.py +396 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/__init__.py +24 -7
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/map.py +43 -6
- python_roborock-7.1.1/roborock/devices/traits/b01/q10/maps.py +58 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q7/__init__.py +14 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q7/map_content.py +36 -3
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/status.py +1 -1
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/diagnostics.py +1 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/b01_map_parser.py +9 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/b01_q10_map_parser.py +5 -4
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/roborock_message.py +3 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/testing/__init__.py +20 -0
- python_roborock-7.1.1/roborock/testing/a01_simulator.py +287 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/testing/cloud.py +7 -1
- {python_roborock-6.2.1 → python_roborock-7.1.1}/.gitignore +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/LICENSE +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/README.md +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/callbacks.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/const.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/README.md +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/cache.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/device_manager.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/file_cache.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/rpc/b01_q10_channel.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/button_light.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/common.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/consumable.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/network_info.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/remote.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/status.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q10/volume.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/b01/q7/map.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/common.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/obstacle_photos.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/exceptions.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/b01_grid_layers.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/b01_q10_overlays.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/b01_q10_render.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/map_parser.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/proto/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/proto/b01_scmap.proto +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/proto/b01_scmap_pb2.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/map/room_colors.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/mqtt/session.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/protocol.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/protocols/__init__.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/protocols/b01_q7_protocol.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/py.typed +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/roborock_typing.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/testing/b01_q10_simulator.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/testing/channel.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/testing/simulator.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/testing/v1_simulator.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/util.py +0 -0
- {python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.1.1
|
|
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 = "
|
|
3
|
+
version = "7.1.1"
|
|
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"
|
|
@@ -594,9 +594,9 @@ async def maps(ctx, device_id: str):
|
|
|
594
594
|
await _display_v1_trait(context, device_id, lambda v1: v1.maps)
|
|
595
595
|
|
|
596
596
|
|
|
597
|
-
# The Q10
|
|
598
|
-
# ~once per 60-70s, so
|
|
599
|
-
#
|
|
597
|
+
# The Q10 publishes its map asynchronously after a dpMultiMap list/get request.
|
|
598
|
+
# Firmware throttles pushes to ~once per 60-70s, so rapid re-requests may not be
|
|
599
|
+
# answered immediately. This bounds how long a one-shot CLI command waits.
|
|
600
600
|
_Q10_MAP_PUSH_TIMEOUT = 30.0
|
|
601
601
|
|
|
602
602
|
|
|
@@ -607,13 +607,12 @@ async def _await_q10_map_push(
|
|
|
607
607
|
timeout: float = _Q10_MAP_PUSH_TIMEOUT,
|
|
608
608
|
allow_cached_on_timeout: bool = False,
|
|
609
609
|
) -> bool:
|
|
610
|
-
"""
|
|
610
|
+
"""Request Q10 map content and wait for usable map-trait state.
|
|
611
611
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
satisfy ``predicate``. Returns whether it did within ``timeout``.
|
|
612
|
+
A Q10 needs a saved-map ID before it can request content. The map list and
|
|
613
|
+
content have independent refresh schedules, so the list is requested only
|
|
614
|
+
when no ID is stored. The content then arrives as a later ``MAP_RESPONSE``
|
|
615
|
+
and is published through the standard trait update interface.
|
|
617
616
|
"""
|
|
618
617
|
loop = asyncio.get_running_loop()
|
|
619
618
|
updated: asyncio.Future[None] = loop.create_future()
|
|
@@ -624,8 +623,23 @@ async def _await_q10_map_push(
|
|
|
624
623
|
|
|
625
624
|
unsub = properties.map.add_update_listener(on_update)
|
|
626
625
|
try:
|
|
627
|
-
|
|
628
|
-
|
|
626
|
+
async with asyncio.timeout(timeout):
|
|
627
|
+
if properties.maps.current_map_id is None:
|
|
628
|
+
map_list_updated: asyncio.Future[None] = loop.create_future()
|
|
629
|
+
|
|
630
|
+
def on_map_list_update() -> None:
|
|
631
|
+
if properties.maps.current_map_id is not None and not map_list_updated.done():
|
|
632
|
+
map_list_updated.set_result(None)
|
|
633
|
+
|
|
634
|
+
unsub_maps = properties.maps.add_update_listener(on_map_list_update)
|
|
635
|
+
try:
|
|
636
|
+
await properties.maps.refresh()
|
|
637
|
+
if properties.maps.current_map_id is None:
|
|
638
|
+
await map_list_updated
|
|
639
|
+
finally:
|
|
640
|
+
unsub_maps()
|
|
641
|
+
await properties.map.refresh()
|
|
642
|
+
await updated
|
|
629
643
|
return True
|
|
630
644
|
except TimeoutError:
|
|
631
645
|
return allow_cached_on_timeout and predicate()
|
|
@@ -706,7 +720,10 @@ async def q10_position(ctx, device_id: str, include_path: bool):
|
|
|
706
720
|
click.echo("Feature not supported by device")
|
|
707
721
|
return
|
|
708
722
|
properties = device.b01_q10_properties
|
|
709
|
-
got_trace = await _await_q10_map_push(
|
|
723
|
+
got_trace = await _await_q10_map_push(
|
|
724
|
+
properties,
|
|
725
|
+
lambda: bool(properties.map.path),
|
|
726
|
+
)
|
|
710
727
|
if not got_trace:
|
|
711
728
|
click.echo("No live trace available (the robot only reports position while cleaning).")
|
|
712
729
|
return
|
|
@@ -83,11 +83,32 @@ class Q10CleanRecord(RoborockBase):
|
|
|
83
83
|
return None
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
@dataclass
|
|
87
|
+
class Q10MapInfo(RoborockBase):
|
|
88
|
+
"""A saved map reported by ``dpMultiMap``.
|
|
89
|
+
|
|
90
|
+
Q10 firmware represents the map identifier as a string on the wire. The
|
|
91
|
+
value is sent back unchanged in a subsequent ``{"op": "get"}`` request.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
id: str
|
|
95
|
+
name: str | None = None
|
|
96
|
+
timestamp: int | None = None
|
|
97
|
+
|
|
98
|
+
|
|
86
99
|
@dataclass
|
|
87
100
|
class dpMultiMap(RoborockBase):
|
|
101
|
+
"""Response envelope for the Q10 ``dpMultiMap`` data point."""
|
|
102
|
+
|
|
88
103
|
op: str
|
|
89
104
|
result: int
|
|
90
|
-
data: list
|
|
105
|
+
data: list[Q10MapInfo] = field(default_factory=list)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def current_map_id(self) -> str | None:
|
|
109
|
+
"""Return the first saved-map identifier, if one was reported."""
|
|
110
|
+
first = next((map_info for map_info in self.data if map_info.id), None)
|
|
111
|
+
return first.id if first else None
|
|
91
112
|
|
|
92
113
|
|
|
93
114
|
@dataclass
|
{python_roborock-6.2.1 → python_roborock-7.1.1}/roborock/data/b01_q7/b01_q7_code_mappings.py
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Self, cast
|
|
2
|
+
|
|
1
3
|
from ..code_mappings import RoborockModeEnum
|
|
2
4
|
|
|
3
5
|
|
|
@@ -15,6 +17,16 @@ class WorkStatusMapping(RoborockModeEnum):
|
|
|
15
17
|
UPDATING = ("updating", 8)
|
|
16
18
|
MOP_CLEANING = ("mop_cleaning", 9)
|
|
17
19
|
MOP_AIRDRYING = ("mop_airdrying", 10)
|
|
20
|
+
WORKING_SLEEP = ("working_sleep", 11)
|
|
21
|
+
UNKNOWN = ("unknown", -1)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_code(cls, code: int) -> Self:
|
|
25
|
+
"""Map unrecognized Q7 statuses to UNKNOWN without failing the response."""
|
|
26
|
+
try:
|
|
27
|
+
return super().from_code(code)
|
|
28
|
+
except ValueError:
|
|
29
|
+
return cast(Self, cls.UNKNOWN)
|
|
18
30
|
|
|
19
31
|
|
|
20
32
|
class SCWindMapping(RoborockModeEnum):
|
|
@@ -72,14 +72,21 @@ class RoborockModeEnum(StrEnum):
|
|
|
72
72
|
|
|
73
73
|
code: int
|
|
74
74
|
"""The integer code associated with the enum member."""
|
|
75
|
+
_display_name_: str | None
|
|
75
76
|
|
|
76
|
-
def __new__(cls, value: str, code: int) -> Self:
|
|
77
|
+
def __new__(cls, value: str, code: int, display_name: str | None = None) -> Self:
|
|
77
78
|
"""Creates a new enum member."""
|
|
78
79
|
member = str.__new__(cls, value)
|
|
79
80
|
member._value_ = value
|
|
80
81
|
member.code = code
|
|
82
|
+
member._display_name_ = display_name
|
|
81
83
|
return member
|
|
82
84
|
|
|
85
|
+
@property
|
|
86
|
+
def display_name(self) -> str:
|
|
87
|
+
"""Return the canonical user-facing name for the mode."""
|
|
88
|
+
return self._display_name_ or self.value
|
|
89
|
+
|
|
83
90
|
@classmethod
|
|
84
91
|
def from_code(cls, code: int) -> Self:
|
|
85
92
|
for member in cls:
|
|
@@ -149,8 +156,8 @@ class RoborockModeEnum(StrEnum):
|
|
|
149
156
|
|
|
150
157
|
@classmethod
|
|
151
158
|
def keys(cls) -> list[str]:
|
|
152
|
-
"""Returns a list of
|
|
153
|
-
return
|
|
159
|
+
"""Returns a de-duplicated list of canonical member names."""
|
|
160
|
+
return list(dict.fromkeys(member.display_name for member in cls))
|
|
154
161
|
|
|
155
162
|
def __eq__(self, other: Any) -> bool:
|
|
156
163
|
if isinstance(other, str):
|
|
@@ -162,13 +162,14 @@ class RoborockBase:
|
|
|
162
162
|
|
|
163
163
|
return result
|
|
164
164
|
|
|
165
|
-
def as_dict(self) -> dict:
|
|
165
|
+
def as_dict(self, exclude: set[str] | None = None) -> dict:
|
|
166
|
+
exclude_set = exclude or set()
|
|
166
167
|
return asdict(
|
|
167
168
|
self,
|
|
168
169
|
dict_factory=lambda _fields: {
|
|
169
170
|
_camelize(key): value.value if isinstance(value, Enum) else value
|
|
170
171
|
for (key, value) in _fields
|
|
171
|
-
if value is not None
|
|
172
|
+
if value is not None and key not in exclude_set
|
|
172
173
|
},
|
|
173
174
|
)
|
|
174
175
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Code mappings for Roborock mower devices."""
|
|
2
|
+
|
|
3
|
+
from roborock.data.code_mappings import RoborockEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RoborockMowerStateCode(RoborockEnum):
|
|
7
|
+
"""Detailed mower operating state reported by DPS 123."""
|
|
8
|
+
|
|
9
|
+
unknown = -999
|
|
10
|
+
idle = 0
|
|
11
|
+
|
|
12
|
+
map_initializing = 1
|
|
13
|
+
map_undocking = 2
|
|
14
|
+
map_undock_fault = 3
|
|
15
|
+
map_locating = 4
|
|
16
|
+
map_prepare_boundary = 5
|
|
17
|
+
map_prepare_island = 6
|
|
18
|
+
map_prepare_path = 7
|
|
19
|
+
map_boundary = 8
|
|
20
|
+
map_island = 9
|
|
21
|
+
map_path = 10
|
|
22
|
+
map_boundary_auto = 11
|
|
23
|
+
map_erasing = 12
|
|
24
|
+
map_save = 13
|
|
25
|
+
map_wait = 14
|
|
26
|
+
map_recoverable_fault = 15
|
|
27
|
+
map_fault = 16
|
|
28
|
+
map_emergency_stop = 17
|
|
29
|
+
map_waiting_fault = 18
|
|
30
|
+
|
|
31
|
+
mow_initializing = 51
|
|
32
|
+
mow_undocking = 52
|
|
33
|
+
mow_locating = 53
|
|
34
|
+
mow_adjust_cutter = 54
|
|
35
|
+
mow_zig_zag = 55
|
|
36
|
+
mow_edge = 56
|
|
37
|
+
mow_goto = 57
|
|
38
|
+
mow_suspend = 58
|
|
39
|
+
mow_recoverable_fault = 59
|
|
40
|
+
mow_fault = 60
|
|
41
|
+
mow_docked_rainfall = 61
|
|
42
|
+
mow_docked_do_not_disturb = 62
|
|
43
|
+
mow_docked_low_battery = 63
|
|
44
|
+
mow_wait = 64
|
|
45
|
+
mow_prepare_remote = 65
|
|
46
|
+
mow_remote = 66
|
|
47
|
+
mow_emergency_stop = 67
|
|
48
|
+
mow_docked_manual = 68
|
|
49
|
+
mow_dock_fault = 69
|
|
50
|
+
mow_remote_undocking = 70
|
|
51
|
+
mow_to_dock_initializing = 71
|
|
52
|
+
mow_to_dock_locating = 72
|
|
53
|
+
mow_to_dock_recoverable_fault = 73
|
|
54
|
+
mow_to_dock_fault = 74
|
|
55
|
+
mow_to_dock_emergency_stop = 75
|
|
56
|
+
mow_to_dock_charging = 76
|
|
57
|
+
mow_to_dock_charge_completed = 77
|
|
58
|
+
|
|
59
|
+
free = 101
|
|
60
|
+
free_initializing = 102
|
|
61
|
+
free_locating = 103
|
|
62
|
+
free_docked_manual = 104
|
|
63
|
+
free_docked_mow_end = 105
|
|
64
|
+
free_docked_plan_end = 106
|
|
65
|
+
free_emergency_stop = 107
|
|
66
|
+
free_recoverable_fault = 108
|
|
67
|
+
free_fault = 109
|
|
68
|
+
|
|
69
|
+
charge_charging = 151
|
|
70
|
+
charge_completed = 152
|
|
71
|
+
charge_waiting = 153
|
|
72
|
+
charge_fault = 154
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""Data containers for Roborock mower devices."""
|
|
2
2
|
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any
|
|
3
|
+
from dataclasses import dataclass, field, fields
|
|
4
|
+
from typing import Any, Self
|
|
5
5
|
|
|
6
6
|
from roborock.data.containers import RoborockBase
|
|
7
7
|
from roborock.roborock_message import RoborockMowerDataProtocol
|
|
8
8
|
|
|
9
|
+
from .mower_code_mappings import RoborockMowerStateCode
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
@dataclass
|
|
11
13
|
class MowerStatus(RoborockBase):
|
|
@@ -14,7 +16,9 @@ class MowerStatus(RoborockBase):
|
|
|
14
16
|
error_code: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.ERROR_CODE})
|
|
15
17
|
battery: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.BATTERY})
|
|
16
18
|
mow_type: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_TYPE})
|
|
17
|
-
mow_state:
|
|
19
|
+
mow_state: RoborockMowerStateCode | None = field(
|
|
20
|
+
default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_STATE}
|
|
21
|
+
)
|
|
18
22
|
mapping_type: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MAPPING_TYPE})
|
|
19
23
|
mapping_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MAPPING_STATE})
|
|
20
24
|
ota_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.OTA_STATE})
|
|
@@ -41,3 +45,25 @@ class MowerStatus(RoborockBase):
|
|
|
41
45
|
)
|
|
42
46
|
afs_status: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.AFS_STATUS})
|
|
43
47
|
network_channel: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.NETWORK_CHANNEL})
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dps(cls, dps: dict[int | str, Any] | None) -> Self:
|
|
51
|
+
"""Create mower status from raw DPS data.
|
|
52
|
+
|
|
53
|
+
Cloud snapshots use string DPS keys while decoded MQTT messages may use
|
|
54
|
+
integer keys. Unknown and malformed keys are ignored.
|
|
55
|
+
"""
|
|
56
|
+
field_by_dps = {
|
|
57
|
+
int(field_info.metadata["dps"]): field_info.name
|
|
58
|
+
for field_info in fields(cls)
|
|
59
|
+
if "dps" in field_info.metadata
|
|
60
|
+
}
|
|
61
|
+
values: dict[str, Any] = {}
|
|
62
|
+
for key, value in (dps or {}).items():
|
|
63
|
+
try:
|
|
64
|
+
dps_id = int(key)
|
|
65
|
+
except (TypeError, ValueError):
|
|
66
|
+
continue
|
|
67
|
+
if field_name := field_by_dps.get(dps_id):
|
|
68
|
+
values[field_name] = value
|
|
69
|
+
return cls.from_dict(values)
|
|
@@ -29,7 +29,7 @@ class CleanRoutes(RoborockModeEnum):
|
|
|
29
29
|
DEEP = ("deep", 301)
|
|
30
30
|
DEEP_PLUS = ("deep_plus", 303)
|
|
31
31
|
FAST = ("fast", 304)
|
|
32
|
-
DEEP_PLUS_CN = ("
|
|
32
|
+
DEEP_PLUS_CN = ("deep_plus_cn", 305, "deep_plus")
|
|
33
33
|
SMART_MODE = ("smart_mode", 306)
|
|
34
34
|
CUSTOMIZED = ("custom", 302)
|
|
35
35
|
|
|
@@ -118,10 +118,7 @@ def get_clean_modes(features: DeviceFeatures) -> list[VacuumModes]:
|
|
|
118
118
|
modes.append(VacuumModes.MAX_PLUS)
|
|
119
119
|
if features.is_pure_clean_mop_supported:
|
|
120
120
|
# If the vacuum is capable of 'pure mop clean' aka no vacuum
|
|
121
|
-
|
|
122
|
-
modes.append(VacuumModes.OFF_RAISE_MAIN_BRUSH)
|
|
123
|
-
else:
|
|
124
|
-
modes.append(VacuumModes.OFF)
|
|
121
|
+
modes.append(VacuumModes.OFF)
|
|
125
122
|
else:
|
|
126
123
|
# If not, we can add gentle
|
|
127
124
|
modes.append(VacuumModes.GENTLE)
|
|
@@ -134,16 +131,19 @@ def get_clean_modes(features: DeviceFeatures) -> list[VacuumModes]:
|
|
|
134
131
|
|
|
135
132
|
def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]:
|
|
136
133
|
"""The routes that the vacuum will take while mopping"""
|
|
134
|
+
if not features.is_clean_route_setting_supported:
|
|
135
|
+
return []
|
|
137
136
|
if features.is_none_pure_clean_mop_with_max_plus:
|
|
138
137
|
return [CleanRoutes.FAST, CleanRoutes.STANDARD]
|
|
139
|
-
supported = [CleanRoutes.STANDARD
|
|
138
|
+
supported = [CleanRoutes.STANDARD]
|
|
139
|
+
if not features.is_clean_efficiency_supported:
|
|
140
|
+
supported.append(CleanRoutes.DEEP)
|
|
140
141
|
if features.is_careful_slow_mop_supported:
|
|
141
|
-
if
|
|
142
|
-
features.is_corner_clean_mode_supported
|
|
142
|
+
if (
|
|
143
|
+
not features.is_corner_clean_mode_supported
|
|
143
144
|
and features.is_clean_route_deep_slow_plus_supported
|
|
144
145
|
and region == "cn"
|
|
145
146
|
):
|
|
146
|
-
# for some reason there is a china specific deep plus mode
|
|
147
147
|
supported.append(CleanRoutes.DEEP_PLUS_CN)
|
|
148
148
|
else:
|
|
149
149
|
supported.append(CleanRoutes.DEEP_PLUS)
|
|
@@ -218,17 +218,11 @@ def get_cleaning_mode_options(features: DeviceFeatures) -> list[CleaningMode]:
|
|
|
218
218
|
|
|
219
219
|
|
|
220
220
|
def get_mop_only_vacuum_mode(features: DeviceFeatures) -> VacuumModes:
|
|
221
|
-
"""
|
|
222
|
-
|
|
223
|
-
There are three cases that must be handled:
|
|
224
|
-
1. The device does not support only mopping.
|
|
225
|
-
2. The device supports raising the vacuum brush while mopping
|
|
226
|
-
3. All other cases.
|
|
227
|
-
"""
|
|
221
|
+
"""Return the vacuum mode used by the app for mop-only cleaning."""
|
|
228
222
|
if not features.is_pure_clean_mop_supported:
|
|
229
223
|
raise RoborockUnsupportedFeature("Mop-only cleaning is not supported")
|
|
230
|
-
|
|
231
|
-
|
|
224
|
+
# Main-brush lift is a device capability, not an alternate fan-power
|
|
225
|
+
# command. The app still sends CleanModeZero (105) for mop-only cleaning.
|
|
232
226
|
return VacuumModes.OFF
|
|
233
227
|
|
|
234
228
|
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
"""Zeo (washing machine) device enums.
|
|
2
2
|
|
|
3
|
-
Member-level comments show the manufacturer's official identifiers
|
|
4
|
-
extracted from the React Native app plugin bundle.
|
|
5
|
-
|
|
6
3
|
For enums that map between protocol-level positions and physical units
|
|
7
4
|
(e.g. spin speed, temperature), the comment format is
|
|
8
5
|
``# L<N>, <physical-value>`` where ``L<N>`` is the protocol position
|
|
@@ -18,12 +15,7 @@ from ..code_mappings import RoborockEnum
|
|
|
18
15
|
|
|
19
16
|
|
|
20
17
|
class ZeoFeatureBits(RoborockEnum):
|
|
21
|
-
"""Bit positions in DP 237 (FEATURE_BITS).
|
|
22
|
-
|
|
23
|
-
Extracted from the official Roborock Washer app plugin bundle
|
|
24
|
-
(index.ios.bundle, module 726). Each member's integer value
|
|
25
|
-
is the exact bit offset the device reports at DP 237.
|
|
26
|
-
"""
|
|
18
|
+
"""Bit positions in DP 237 (FEATURE_BITS)."""
|
|
27
19
|
|
|
28
20
|
smart_hosting = 0
|
|
29
21
|
silent_mode = 1
|
|
@@ -52,7 +44,7 @@ class ZeoFeatureBits(RoborockEnum):
|
|
|
52
44
|
|
|
53
45
|
|
|
54
46
|
class ZeoMode(RoborockEnum):
|
|
55
|
-
null = 0
|
|
47
|
+
null = 0
|
|
56
48
|
wash = 1
|
|
57
49
|
wash_and_dry = 2
|
|
58
50
|
dry = 3
|
|
@@ -60,6 +52,7 @@ class ZeoMode(RoborockEnum):
|
|
|
60
52
|
|
|
61
53
|
|
|
62
54
|
class ZeoState(RoborockEnum):
|
|
55
|
+
null = 0
|
|
63
56
|
standby = 1
|
|
64
57
|
weighing = 2 # Checking
|
|
65
58
|
soaking = 3
|
|
@@ -83,7 +76,7 @@ class ZeoState(RoborockEnum):
|
|
|
83
76
|
|
|
84
77
|
|
|
85
78
|
class ZeoProgram(RoborockEnum):
|
|
86
|
-
null = 0
|
|
79
|
+
null = 0
|
|
87
80
|
standard = 1 # Mixed
|
|
88
81
|
quick = 2
|
|
89
82
|
sanitize = 3 # Sterilization
|
|
@@ -152,6 +145,7 @@ class ZeoSoak(RoborockEnum):
|
|
|
152
145
|
|
|
153
146
|
|
|
154
147
|
class ZeoTemperature(RoborockEnum):
|
|
148
|
+
null = 0
|
|
155
149
|
normal = 1 # L1, 0 C
|
|
156
150
|
low = 2 # L2, 30 C
|
|
157
151
|
medium = 3 # L3, 40 C
|
|
@@ -203,11 +197,13 @@ class ZeoSoftenerType(RoborockEnum):
|
|
|
203
197
|
|
|
204
198
|
|
|
205
199
|
class ZeoDetergentExpansionType(RoborockEnum):
|
|
200
|
+
null = 0
|
|
206
201
|
concentrated_detergent = 1
|
|
207
202
|
detergent = 2
|
|
208
203
|
|
|
209
204
|
|
|
210
205
|
class ZeoSoftenerExpansionType(RoborockEnum):
|
|
206
|
+
null = 0
|
|
211
207
|
softener = 1
|
|
212
208
|
softener_expansion = 2
|
|
213
209
|
wool_detergent = 3
|
|
@@ -241,6 +237,7 @@ class ZeoError(RoborockEnum):
|
|
|
241
237
|
|
|
242
238
|
|
|
243
239
|
class ZeoDryingMethod(RoborockEnum):
|
|
240
|
+
null = 0
|
|
244
241
|
l1 = 1 # L1, Saving
|
|
245
242
|
l2 = 2 # L2, Standard
|
|
246
243
|
l3 = 3 # L3, SuperFast
|
|
@@ -255,11 +252,13 @@ class ZeoSteamVolume(RoborockEnum):
|
|
|
255
252
|
|
|
256
253
|
|
|
257
254
|
class ZeoDryAndCare(RoborockEnum):
|
|
255
|
+
null = 0
|
|
258
256
|
soft = 1
|
|
259
257
|
normal = 2
|
|
260
258
|
|
|
261
259
|
|
|
262
260
|
class ZeoDryerStartError(RoborockEnum):
|
|
261
|
+
null = 0
|
|
263
262
|
dryer_running = 1 # Washer-Dryer Pairing cannot start: Dryer is running.
|
|
264
263
|
dryer_error = 2 # Washer-Dryer Pairing cannot start: Dryer has an error.
|
|
265
264
|
dryer_done = 3 # Dryer drying is complete, please remove the clothes first.
|
|
@@ -269,7 +268,7 @@ class ZeoDryerStartError(RoborockEnum):
|
|
|
269
268
|
dryer_network_fail = 7 # Please check the dryer network connection.
|
|
270
269
|
|
|
271
270
|
|
|
272
|
-
# The following are App-internal lookup tables
|
|
271
|
+
# The following are App-internal lookup tables.
|
|
273
272
|
# They are NOT DP protocol enums — the device never receives these values.
|
|
274
273
|
# They control how the official app adjusts recommended dosages or UI visibility.
|
|
275
274
|
#
|
|
@@ -87,6 +87,7 @@ class NewFeatureStrBit(IntEnum):
|
|
|
87
87
|
TWO_GEARS_NO_COLLISION = 118
|
|
88
88
|
CARPET_SHAPE_TYPE = 119
|
|
89
89
|
SR_MAP = 120
|
|
90
|
+
ROLLER_MOP = 128
|
|
90
91
|
|
|
91
92
|
|
|
92
93
|
class ProductFeatures(StrEnum):
|
|
@@ -474,6 +475,7 @@ class DeviceFeatures(RoborockBase):
|
|
|
474
475
|
)
|
|
475
476
|
is_carpet_shape_type_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.CARPET_SHAPE_TYPE})
|
|
476
477
|
is_sr_map_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.SR_MAP})
|
|
478
|
+
is_roller_mop_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.ROLLER_MOP})
|
|
477
479
|
|
|
478
480
|
# Features from feature_info list
|
|
479
481
|
is_led_status_switch_supported: bool = field(metadata={"robot_features": 119})
|
|
@@ -644,6 +646,12 @@ class DeviceFeatures(RoborockBase):
|
|
|
644
646
|
if any(feat in available_features for feat in product_features): # type: ignore
|
|
645
647
|
kwargs[f.name] = True
|
|
646
648
|
|
|
649
|
+
# The app combines runtime shake-mop, model shake/spin, and roller-mop
|
|
650
|
+
# capabilities when deciding whether mop routes can be configured.
|
|
651
|
+
kwargs["is_clean_route_setting_supported"] |= (
|
|
652
|
+
kwargs["is_shake_mop_set_supported"] or kwargs["is_roller_mop_supported"]
|
|
653
|
+
)
|
|
654
|
+
|
|
647
655
|
return cls(**kwargs)
|
|
648
656
|
|
|
649
657
|
def get_supported_features(self) -> list[str]:
|
|
@@ -202,6 +202,10 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
202
202
|
await self.v1_properties.start()
|
|
203
203
|
elif self.b01_q10_properties is not None:
|
|
204
204
|
await self.b01_q10_properties.start()
|
|
205
|
+
elif self.b01_q7_properties is not None:
|
|
206
|
+
await self.b01_q7_properties.start()
|
|
207
|
+
elif self.zeo is not None:
|
|
208
|
+
await self.zeo.start()
|
|
205
209
|
except RoborockException:
|
|
206
210
|
# Expected: start() can fail transiently. Unsubscribe before propagating
|
|
207
211
|
# so the retry by connect_loop() gets a clean channel.
|
|
@@ -230,6 +234,10 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
230
234
|
self.v1_properties.close()
|
|
231
235
|
if self.b01_q10_properties is not None:
|
|
232
236
|
await self.b01_q10_properties.close()
|
|
237
|
+
if self.b01_q7_properties is not None:
|
|
238
|
+
await self.b01_q7_properties.close()
|
|
239
|
+
if self.zeo is not None:
|
|
240
|
+
self.zeo.close()
|
|
233
241
|
if self._unsub:
|
|
234
242
|
self._unsub()
|
|
235
243
|
self._unsub = None
|
|
@@ -243,6 +251,8 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
243
251
|
extra: dict[str, Any] = {}
|
|
244
252
|
if self.v1_properties:
|
|
245
253
|
extra["traits"] = self.v1_properties.as_dict()
|
|
254
|
+
elif self.b01_q10_properties:
|
|
255
|
+
extra["traits"] = self.b01_q10_properties.as_dict()
|
|
246
256
|
return redact_device_data(
|
|
247
257
|
{
|
|
248
258
|
"device": self.device_info.as_dict(),
|
|
@@ -98,8 +98,10 @@ async def send_decoded_command(
|
|
|
98
98
|
try:
|
|
99
99
|
await asyncio.wait_for(finished.wait(), timeout=_TIMEOUT)
|
|
100
100
|
except TimeoutError as ex:
|
|
101
|
+
await mqtt_channel.health_manager.on_timeout()
|
|
101
102
|
raise RoborockException(f"Command timed out after {_TIMEOUT}s") from ex
|
|
102
103
|
finally:
|
|
103
104
|
unsub()
|
|
104
105
|
|
|
106
|
+
await mqtt_channel.health_manager.on_success()
|
|
105
107
|
return result # type: ignore[return-value]
|
|
@@ -53,6 +53,10 @@ class Q7MapRpcChannel(Protocol):
|
|
|
53
53
|
"""Send a map command and get decoded bytes."""
|
|
54
54
|
...
|
|
55
55
|
|
|
56
|
+
async def subscribe_map_pushes(self, callback: Callable[[bytes], None]) -> Callable[[], None]:
|
|
57
|
+
"""Subscribe to unsolicited map pushes, invoking callback with decoded SCMap bytes."""
|
|
58
|
+
...
|
|
59
|
+
|
|
56
60
|
|
|
57
61
|
def _matches_map_response(response_message: RoborockMessage, *, version: bytes | None) -> bytes | None:
|
|
58
62
|
"""Return raw map payload bytes for matching MAP_RESPONSE messages."""
|
|
@@ -208,6 +212,25 @@ class B01Q7Channel(Channel, Q7RpcChannel, Q7MapRpcChannel):
|
|
|
208
212
|
|
|
209
213
|
return decode_map_payload(raw_payload, map_key=self._map_key)
|
|
210
214
|
|
|
215
|
+
async def subscribe_map_pushes(self, callback: Callable[[bytes], None]) -> Callable[[], None]:
|
|
216
|
+
"""Subscribe to unsolicited ``MAP_RESPONSE`` pushes.
|
|
217
|
+
|
|
218
|
+
The device streams full SCMap frames on its own during cleaning; the
|
|
219
|
+
callback receives the decoded (inflated) SCMap bytes for each frame.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
def on_message(message: RoborockMessage) -> None:
|
|
223
|
+
if (raw_payload := _matches_map_response(message, version=B01_VERSION)) is None:
|
|
224
|
+
return
|
|
225
|
+
try:
|
|
226
|
+
decoded = decode_map_payload(raw_payload, map_key=self._map_key)
|
|
227
|
+
except RoborockException as ex:
|
|
228
|
+
_LOGGER.debug("Failed to decode pushed B01 map payload: %s", ex)
|
|
229
|
+
return
|
|
230
|
+
callback(decoded)
|
|
231
|
+
|
|
232
|
+
return await self._mqtt_channel.subscribe(on_message)
|
|
233
|
+
|
|
211
234
|
|
|
212
235
|
def create_b01_q7_channel(
|
|
213
236
|
device: HomeDataDevice,
|