python-roborock 4.20.0__tar.gz → 4.22.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-4.20.0 → python_roborock-4.22.0}/PKG-INFO +1 -1
- {python_roborock-4.20.0 → python_roborock-4.22.0}/pyproject.toml +1 -1
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/b01_q7/b01_q7_containers.py +27 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/v1/v1_code_mappings.py +0 -11
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/v1/v1_containers.py +2 -2
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/rpc/b01_q7_channel.py +76 -34
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q7/__init__.py +22 -2
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q7/clean_summary.py +2 -2
- python_roborock-4.22.0/roborock/devices/traits/b01/q7/map.py +59 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/__init__.py +6 -0
- python_roborock-4.22.0/roborock/devices/traits/v1/wash_towel_mode.py +48 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/protocols/b01_q7_protocol.py +1 -0
- python_roborock-4.20.0/roborock/devices/traits/v1/wash_towel_mode.py +0 -13
- {python_roborock-4.20.0 → python_roborock-4.22.0}/.gitignore +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/LICENSE +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/README.md +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/callbacks.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/cli.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/const.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/code_mappings.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/containers.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/device_features.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/README.md +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/cache.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/device.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/file_cache.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/rpc/a01_channel.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q10/common.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q10/status.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/diagnostics.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/exceptions.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/map/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/protocol.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/protocols/__init__.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/py.typed +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/roborock_message.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/util.py +0 -0
- {python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.22.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
Project-URL: Repository, https://github.com/humbertogontijo/python-roborock
|
|
6
6
|
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.22.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"
|
|
@@ -75,6 +75,33 @@ class Recommend(RoborockBase):
|
|
|
75
75
|
room_id: list[int] = field(default_factory=list)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
@dataclass
|
|
79
|
+
class Q7MapListEntry(RoborockBase):
|
|
80
|
+
"""Single map list entry returned by `service.get_map_list`."""
|
|
81
|
+
|
|
82
|
+
id: int | None = None
|
|
83
|
+
cur: bool | None = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class Q7MapList(RoborockBase):
|
|
88
|
+
"""Map list response returned by `service.get_map_list`."""
|
|
89
|
+
|
|
90
|
+
map_list: list[Q7MapListEntry] = field(default_factory=list)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def current_map_id(self) -> int | None:
|
|
94
|
+
"""Current map id, preferring the entry marked current."""
|
|
95
|
+
if not self.map_list:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
ordered = sorted(self.map_list, key=lambda entry: entry.cur or False, reverse=True)
|
|
99
|
+
first = next(iter(ordered), None)
|
|
100
|
+
if first is None or not isinstance(first.id, int):
|
|
101
|
+
return None
|
|
102
|
+
return first.id
|
|
103
|
+
|
|
104
|
+
|
|
78
105
|
@dataclass
|
|
79
106
|
class B01Props(RoborockBase):
|
|
80
107
|
"""
|
|
@@ -587,17 +587,6 @@ class RoborockDockDustCollectionModeCode(RoborockEnum):
|
|
|
587
587
|
max = 4
|
|
588
588
|
|
|
589
589
|
|
|
590
|
-
class RoborockDockWashTowelModeCode(RoborockEnum):
|
|
591
|
-
"""Describes the wash towel mode of the vacuum cleaner."""
|
|
592
|
-
|
|
593
|
-
# TODO: Get the correct values for various different docks
|
|
594
|
-
unknown = -9999
|
|
595
|
-
light = 0
|
|
596
|
-
balanced = 1
|
|
597
|
-
deep = 2
|
|
598
|
-
smart = 10
|
|
599
|
-
|
|
600
|
-
|
|
601
590
|
class RoborockStateCode(RoborockEnum):
|
|
602
591
|
unknown = 0
|
|
603
592
|
starting = 1
|
|
@@ -39,6 +39,7 @@ from roborock.const import (
|
|
|
39
39
|
from roborock.exceptions import RoborockException
|
|
40
40
|
|
|
41
41
|
from ..containers import NamedRoomMapping, RoborockBase, RoborockBaseTimer, _attr_repr
|
|
42
|
+
from .v1_clean_modes import WashTowelModes
|
|
42
43
|
from .v1_code_mappings import (
|
|
43
44
|
CleanFluidStatus,
|
|
44
45
|
ClearWaterBoxStatus,
|
|
@@ -48,7 +49,6 @@ from .v1_code_mappings import (
|
|
|
48
49
|
RoborockDockDustCollectionModeCode,
|
|
49
50
|
RoborockDockErrorCode,
|
|
50
51
|
RoborockDockTypeCode,
|
|
51
|
-
RoborockDockWashTowelModeCode,
|
|
52
52
|
RoborockErrorCode,
|
|
53
53
|
RoborockFanPowerCode,
|
|
54
54
|
RoborockFanSpeedP10,
|
|
@@ -750,7 +750,7 @@ class DustCollectionMode(RoborockBase):
|
|
|
750
750
|
|
|
751
751
|
@dataclass
|
|
752
752
|
class WashTowelMode(RoborockBase):
|
|
753
|
-
wash_mode:
|
|
753
|
+
wash_mode: WashTowelModes | None = None
|
|
754
754
|
|
|
755
755
|
|
|
756
756
|
@dataclass
|
|
@@ -5,31 +5,67 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
-
from
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from typing import Any, TypeVar
|
|
9
10
|
|
|
10
11
|
from roborock.devices.transport.mqtt_channel import MqttChannel
|
|
11
12
|
from roborock.exceptions import RoborockException
|
|
12
|
-
from roborock.protocols.b01_q7_protocol import
|
|
13
|
-
|
|
14
|
-
decode_rpc_response,
|
|
15
|
-
encode_mqtt_payload,
|
|
16
|
-
)
|
|
17
|
-
from roborock.roborock_message import RoborockMessage
|
|
13
|
+
from roborock.protocols.b01_q7_protocol import B01_VERSION, Q7RequestMessage, decode_rpc_response, encode_mqtt_payload
|
|
14
|
+
from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
|
|
18
15
|
|
|
19
16
|
_LOGGER = logging.getLogger(__name__)
|
|
20
17
|
_TIMEOUT = 10.0
|
|
18
|
+
_T = TypeVar("_T")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _matches_map_response(response_message: RoborockMessage, *, version: bytes | None) -> bytes | None:
|
|
22
|
+
"""Return raw map payload bytes for matching MAP_RESPONSE messages."""
|
|
23
|
+
if (
|
|
24
|
+
response_message.protocol == RoborockMessageProtocol.MAP_RESPONSE
|
|
25
|
+
and response_message.payload
|
|
26
|
+
and response_message.version == version
|
|
27
|
+
):
|
|
28
|
+
return response_message.payload
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def _send_command(
|
|
33
|
+
mqtt_channel: MqttChannel,
|
|
34
|
+
request_message: Q7RequestMessage,
|
|
35
|
+
*,
|
|
36
|
+
response_matcher: Callable[[RoborockMessage], _T | None],
|
|
37
|
+
) -> _T:
|
|
38
|
+
"""Publish a B01 command and resolve on the first matching response."""
|
|
39
|
+
roborock_message = encode_mqtt_payload(request_message)
|
|
40
|
+
future: asyncio.Future[_T] = asyncio.get_running_loop().create_future()
|
|
41
|
+
|
|
42
|
+
def on_message(response_message: RoborockMessage) -> None:
|
|
43
|
+
if future.done():
|
|
44
|
+
return
|
|
45
|
+
try:
|
|
46
|
+
response = response_matcher(response_message)
|
|
47
|
+
except Exception as ex:
|
|
48
|
+
future.set_exception(ex)
|
|
49
|
+
return
|
|
50
|
+
if response is not None:
|
|
51
|
+
future.set_result(response)
|
|
52
|
+
|
|
53
|
+
unsub = await mqtt_channel.subscribe(on_message)
|
|
54
|
+
try:
|
|
55
|
+
await mqtt_channel.publish(roborock_message)
|
|
56
|
+
return await asyncio.wait_for(future, timeout=_TIMEOUT)
|
|
57
|
+
finally:
|
|
58
|
+
unsub()
|
|
21
59
|
|
|
22
60
|
|
|
23
61
|
async def send_decoded_command(
|
|
24
62
|
mqtt_channel: MqttChannel,
|
|
25
63
|
request_message: Q7RequestMessage,
|
|
26
|
-
) ->
|
|
64
|
+
) -> Any:
|
|
27
65
|
"""Send a command on the MQTT channel and get a decoded response."""
|
|
28
66
|
_LOGGER.debug("Sending B01 MQTT command: %s", request_message)
|
|
29
|
-
roborock_message = encode_mqtt_payload(request_message)
|
|
30
|
-
future: asyncio.Future[Any] = asyncio.get_running_loop().create_future()
|
|
31
67
|
|
|
32
|
-
def find_response(response_message: RoborockMessage) -> None:
|
|
68
|
+
def find_response(response_message: RoborockMessage) -> Any | None:
|
|
33
69
|
"""Handle incoming messages and resolve the future."""
|
|
34
70
|
try:
|
|
35
71
|
decoded_dps = decode_rpc_response(response_message)
|
|
@@ -41,7 +77,7 @@ async def send_decoded_command(
|
|
|
41
77
|
response_message,
|
|
42
78
|
ex,
|
|
43
79
|
)
|
|
44
|
-
return
|
|
80
|
+
return None
|
|
45
81
|
for dps_value in decoded_dps.values():
|
|
46
82
|
# valid responses are JSON strings wrapped in the dps value
|
|
47
83
|
if not isinstance(dps_value, str):
|
|
@@ -55,31 +91,23 @@ async def send_decoded_command(
|
|
|
55
91
|
continue
|
|
56
92
|
if isinstance(inner, dict) and inner.get("msgId") == str(request_message.msg_id):
|
|
57
93
|
_LOGGER.debug("Received query response: %s", inner)
|
|
58
|
-
# Check for error code (0 = success, non-zero = error)
|
|
59
94
|
code = inner.get("code", 0)
|
|
60
95
|
if code != 0:
|
|
61
96
|
error_msg = f"B01 command failed with code {code} ({request_message})"
|
|
62
97
|
_LOGGER.debug("B01 error response: %s", error_msg)
|
|
63
|
-
|
|
64
|
-
future.set_exception(RoborockException(error_msg))
|
|
65
|
-
return
|
|
98
|
+
raise RoborockException(error_msg)
|
|
66
99
|
data = inner.get("data")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
return
|
|
74
|
-
if not future.done():
|
|
75
|
-
future.set_result(data)
|
|
76
|
-
|
|
77
|
-
unsub = await mqtt_channel.subscribe(find_response)
|
|
78
|
-
|
|
79
|
-
_LOGGER.debug("Sending MQTT message: %s", roborock_message)
|
|
100
|
+
if request_message.command == "prop.get" and not isinstance(data, dict):
|
|
101
|
+
raise RoborockException(f"Unexpected data type for response {data} ({request_message})")
|
|
102
|
+
return data
|
|
103
|
+
return None
|
|
104
|
+
|
|
80
105
|
try:
|
|
81
|
-
await
|
|
82
|
-
|
|
106
|
+
return await _send_command(
|
|
107
|
+
mqtt_channel,
|
|
108
|
+
request_message,
|
|
109
|
+
response_matcher=find_response,
|
|
110
|
+
)
|
|
83
111
|
except TimeoutError as ex:
|
|
84
112
|
raise RoborockException(f"B01 command timed out after {_TIMEOUT}s ({request_message})") from ex
|
|
85
113
|
except RoborockException as ex:
|
|
@@ -89,7 +117,6 @@ async def send_decoded_command(
|
|
|
89
117
|
ex,
|
|
90
118
|
)
|
|
91
119
|
raise
|
|
92
|
-
|
|
93
120
|
except Exception as ex:
|
|
94
121
|
_LOGGER.exception(
|
|
95
122
|
"Error sending B01 decoded command (%ss): %s",
|
|
@@ -97,5 +124,20 @@ async def send_decoded_command(
|
|
|
97
124
|
ex,
|
|
98
125
|
)
|
|
99
126
|
raise
|
|
100
|
-
|
|
101
|
-
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def send_map_command(mqtt_channel: MqttChannel, request_message: Q7RequestMessage) -> bytes:
|
|
130
|
+
"""Send map upload command and wait for MAP_RESPONSE payload bytes.
|
|
131
|
+
|
|
132
|
+
This stays separate from ``send_decoded_command()`` because map uploads arrive as
|
|
133
|
+
raw ``MAP_RESPONSE`` payload bytes instead of a decoded RPC ``data`` payload.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
return await _send_command(
|
|
138
|
+
mqtt_channel,
|
|
139
|
+
request_message,
|
|
140
|
+
response_matcher=lambda response_message: _matches_map_response(response_message, version=B01_VERSION),
|
|
141
|
+
)
|
|
142
|
+
except TimeoutError as ex:
|
|
143
|
+
raise RoborockException(f"B01 map command timed out after {_TIMEOUT}s ({request_message})") from ex
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q7/__init__.py
RENAMED
|
@@ -4,6 +4,7 @@ Potentially other devices may fall into this category in the future."""
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from roborock import B01Props
|
|
7
|
+
from roborock.data import Q7MapList, Q7MapListEntry
|
|
7
8
|
from roborock.data.b01_q7.b01_q7_code_mappings import (
|
|
8
9
|
CleanPathPreferenceMapping,
|
|
9
10
|
CleanRepeatMapping,
|
|
@@ -16,15 +17,19 @@ from roborock.data.b01_q7.b01_q7_code_mappings import (
|
|
|
16
17
|
from roborock.devices.rpc.b01_q7_channel import send_decoded_command
|
|
17
18
|
from roborock.devices.traits import Trait
|
|
18
19
|
from roborock.devices.transport.mqtt_channel import MqttChannel
|
|
19
|
-
from roborock.protocols.b01_q7_protocol import CommandType, ParamsType, Q7RequestMessage
|
|
20
|
+
from roborock.protocols.b01_q7_protocol import B01_Q7_DPS, CommandType, ParamsType, Q7RequestMessage
|
|
20
21
|
from roborock.roborock_message import RoborockB01Props
|
|
21
22
|
from roborock.roborock_typing import RoborockB01Q7Methods
|
|
22
23
|
|
|
23
24
|
from .clean_summary import CleanSummaryTrait
|
|
25
|
+
from .map import MapTrait
|
|
24
26
|
|
|
25
27
|
__all__ = [
|
|
26
28
|
"Q7PropertiesApi",
|
|
27
29
|
"CleanSummaryTrait",
|
|
30
|
+
"MapTrait",
|
|
31
|
+
"Q7MapList",
|
|
32
|
+
"Q7MapListEntry",
|
|
28
33
|
]
|
|
29
34
|
|
|
30
35
|
|
|
@@ -34,10 +39,14 @@ class Q7PropertiesApi(Trait):
|
|
|
34
39
|
clean_summary: CleanSummaryTrait
|
|
35
40
|
"""Trait for clean records / clean summary (Q7 `service.get_record_list`)."""
|
|
36
41
|
|
|
42
|
+
map: MapTrait
|
|
43
|
+
"""Trait for map list metadata + raw map payload retrieval."""
|
|
44
|
+
|
|
37
45
|
def __init__(self, channel: MqttChannel) -> None:
|
|
38
46
|
"""Initialize the B01Props API."""
|
|
39
47
|
self._channel = channel
|
|
40
48
|
self.clean_summary = CleanSummaryTrait(channel)
|
|
49
|
+
self.map = MapTrait(channel)
|
|
41
50
|
|
|
42
51
|
async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None:
|
|
43
52
|
"""Query the device for the values of the given Q7 properties."""
|
|
@@ -87,6 +96,17 @@ class Q7PropertiesApi(Trait):
|
|
|
87
96
|
},
|
|
88
97
|
)
|
|
89
98
|
|
|
99
|
+
async def clean_segments(self, segment_ids: list[int]) -> None:
|
|
100
|
+
"""Start segment cleaning for the given ids (Q7 uses room ids)."""
|
|
101
|
+
await self.send(
|
|
102
|
+
command=RoborockB01Q7Methods.SET_ROOM_CLEAN,
|
|
103
|
+
params={
|
|
104
|
+
"clean_type": CleanTaskTypeMapping.ROOM.code,
|
|
105
|
+
"ctrl_value": SCDeviceCleanParam.START.code,
|
|
106
|
+
"room_ids": segment_ids,
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
90
110
|
async def pause_clean(self) -> None:
|
|
91
111
|
"""Pause cleaning."""
|
|
92
112
|
await self.send(
|
|
@@ -127,7 +147,7 @@ class Q7PropertiesApi(Trait):
|
|
|
127
147
|
"""Send a command to the device."""
|
|
128
148
|
return await send_decoded_command(
|
|
129
149
|
self._channel,
|
|
130
|
-
Q7RequestMessage(dps=
|
|
150
|
+
Q7RequestMessage(dps=B01_Q7_DPS, command=command, params=params),
|
|
131
151
|
)
|
|
132
152
|
|
|
133
153
|
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q7/clean_summary.py
RENAMED
|
@@ -13,7 +13,7 @@ from roborock.devices.rpc.b01_q7_channel import send_decoded_command
|
|
|
13
13
|
from roborock.devices.traits import Trait
|
|
14
14
|
from roborock.devices.transport.mqtt_channel import MqttChannel
|
|
15
15
|
from roborock.exceptions import RoborockException
|
|
16
|
-
from roborock.protocols.b01_q7_protocol import Q7RequestMessage
|
|
16
|
+
from roborock.protocols.b01_q7_protocol import B01_Q7_DPS, Q7RequestMessage
|
|
17
17
|
from roborock.roborock_typing import RoborockB01Q7Methods
|
|
18
18
|
|
|
19
19
|
__all__ = [
|
|
@@ -50,7 +50,7 @@ class CleanSummaryTrait(CleanRecordSummary, Trait):
|
|
|
50
50
|
"""Fetch the raw device clean record list (`service.get_record_list`)."""
|
|
51
51
|
result = await send_decoded_command(
|
|
52
52
|
self._channel,
|
|
53
|
-
Q7RequestMessage(dps=
|
|
53
|
+
Q7RequestMessage(dps=B01_Q7_DPS, command=RoborockB01Q7Methods.GET_RECORD_LIST, params={}),
|
|
54
54
|
)
|
|
55
55
|
|
|
56
56
|
if not isinstance(result, dict):
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Map trait for B01 Q7 devices."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from roborock.data import Q7MapList
|
|
6
|
+
from roborock.devices.rpc.b01_q7_channel import send_decoded_command, send_map_command
|
|
7
|
+
from roborock.devices.traits import Trait
|
|
8
|
+
from roborock.devices.transport.mqtt_channel import MqttChannel
|
|
9
|
+
from roborock.exceptions import RoborockException
|
|
10
|
+
from roborock.protocols.b01_q7_protocol import B01_Q7_DPS, Q7RequestMessage
|
|
11
|
+
from roborock.roborock_typing import RoborockB01Q7Methods
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MapTrait(Q7MapList, Trait):
|
|
15
|
+
"""Map retrieval + map metadata helpers for Q7 devices."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, channel: MqttChannel) -> None:
|
|
18
|
+
super().__init__()
|
|
19
|
+
self._channel = channel
|
|
20
|
+
# Map uploads are serialized per-device to avoid response cross-wiring.
|
|
21
|
+
self._map_command_lock = asyncio.Lock()
|
|
22
|
+
self._loaded = False
|
|
23
|
+
|
|
24
|
+
async def refresh(self) -> None:
|
|
25
|
+
"""Refresh cached map list metadata from the device."""
|
|
26
|
+
response = await send_decoded_command(
|
|
27
|
+
self._channel,
|
|
28
|
+
Q7RequestMessage(dps=B01_Q7_DPS, command=RoborockB01Q7Methods.GET_MAP_LIST, params={}),
|
|
29
|
+
)
|
|
30
|
+
if not isinstance(response, dict):
|
|
31
|
+
raise RoborockException(
|
|
32
|
+
f"Unexpected response type for GET_MAP_LIST: {type(response).__name__}: {response!r}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if (parsed := Q7MapList.from_dict(response)) is None:
|
|
36
|
+
raise RoborockException(f"Failed to decode map list response: {response!r}")
|
|
37
|
+
|
|
38
|
+
self.map_list = parsed.map_list
|
|
39
|
+
self._loaded = True
|
|
40
|
+
|
|
41
|
+
async def _get_map_payload(self, *, map_id: int) -> bytes:
|
|
42
|
+
"""Fetch raw map payload bytes for the given map id."""
|
|
43
|
+
request = Q7RequestMessage(
|
|
44
|
+
dps=B01_Q7_DPS,
|
|
45
|
+
command=RoborockB01Q7Methods.UPLOAD_BY_MAPID,
|
|
46
|
+
params={"map_id": map_id},
|
|
47
|
+
)
|
|
48
|
+
async with self._map_command_lock:
|
|
49
|
+
return await send_map_command(self._channel, request)
|
|
50
|
+
|
|
51
|
+
async def get_current_map_payload(self) -> bytes:
|
|
52
|
+
"""Fetch raw map payload bytes for the currently selected map."""
|
|
53
|
+
if not self._loaded:
|
|
54
|
+
await self.refresh()
|
|
55
|
+
|
|
56
|
+
map_id = self.current_map_id
|
|
57
|
+
if map_id is None:
|
|
58
|
+
raise RoborockException(f"Unable to determine map_id from map list response: {self!r}")
|
|
59
|
+
return await self._get_map_payload(map_id=map_id)
|
|
@@ -234,6 +234,12 @@ class PropertiesApi(Trait):
|
|
|
234
234
|
# Dock type also acts like a device feature for some traits.
|
|
235
235
|
dock_type = await self._dock_type()
|
|
236
236
|
|
|
237
|
+
# Initialize traits with special arguments before the generic loop
|
|
238
|
+
if self.wash_towel_mode is None and self._is_supported(WashTowelModeTrait, "wash_towel_mode", dock_type):
|
|
239
|
+
wash_towel_mode = WashTowelModeTrait(self.device_features)
|
|
240
|
+
wash_towel_mode._rpc_channel = self._get_rpc_channel(wash_towel_mode) # type: ignore[assignment]
|
|
241
|
+
self.wash_towel_mode = wash_towel_mode
|
|
242
|
+
|
|
237
243
|
# Dynamically create any traits that need to be populated
|
|
238
244
|
for item in fields(self):
|
|
239
245
|
if (trait := getattr(self, item.name, None)) is not None:
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Trait for wash towel mode."""
|
|
2
|
+
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import Self
|
|
5
|
+
|
|
6
|
+
from roborock.data import WashTowelMode, WashTowelModes, get_wash_towel_modes
|
|
7
|
+
from roborock.device_features import is_wash_n_fill_dock
|
|
8
|
+
from roborock.devices.traits.v1 import common
|
|
9
|
+
from roborock.devices.traits.v1.device_features import DeviceFeaturesTrait
|
|
10
|
+
from roborock.roborock_typing import RoborockCommand
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WashTowelModeTrait(WashTowelMode, common.V1TraitMixin):
|
|
14
|
+
"""Trait for wash towel mode."""
|
|
15
|
+
|
|
16
|
+
command = RoborockCommand.GET_WASH_TOWEL_MODE
|
|
17
|
+
requires_dock_type = is_wash_n_fill_dock
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
device_feature_trait: DeviceFeaturesTrait,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.device_feature_trait = device_feature_trait
|
|
25
|
+
|
|
26
|
+
def _parse_response(self, response: common.V1ResponseData) -> Self:
|
|
27
|
+
"""Parse the response from the device into a WashTowelMode object."""
|
|
28
|
+
if isinstance(response, list):
|
|
29
|
+
response = response[0]
|
|
30
|
+
if isinstance(response, dict):
|
|
31
|
+
return WashTowelMode.from_dict(response)
|
|
32
|
+
raise ValueError(f"Unexpected wash towel mode format: {response!r}")
|
|
33
|
+
|
|
34
|
+
@cached_property
|
|
35
|
+
def wash_towel_mode_options(self) -> list[WashTowelModes]:
|
|
36
|
+
return get_wash_towel_modes(self.device_feature_trait)
|
|
37
|
+
|
|
38
|
+
async def set_wash_towel_mode(self, mode: WashTowelModes) -> None:
|
|
39
|
+
"""Set the wash towel mode."""
|
|
40
|
+
await self.rpc_channel.send_command(RoborockCommand.SET_WASH_TOWEL_MODE, params={"wash_mode": mode.code})
|
|
41
|
+
|
|
42
|
+
async def start_wash(self) -> None:
|
|
43
|
+
"""Start washing the mop."""
|
|
44
|
+
await self.rpc_channel.send_command(RoborockCommand.APP_START_WASH)
|
|
45
|
+
|
|
46
|
+
async def stop_wash(self) -> None:
|
|
47
|
+
"""Stop washing the mop."""
|
|
48
|
+
await self.rpc_channel.send_command(RoborockCommand.APP_STOP_WASH)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"""Trait for wash towel mode."""
|
|
2
|
-
|
|
3
|
-
from roborock.data import WashTowelMode
|
|
4
|
-
from roborock.device_features import is_wash_n_fill_dock
|
|
5
|
-
from roborock.devices.traits.v1 import common
|
|
6
|
-
from roborock.roborock_typing import RoborockCommand
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class WashTowelModeTrait(WashTowelMode, common.V1TraitMixin):
|
|
10
|
-
"""Trait for wash towel mode."""
|
|
11
|
-
|
|
12
|
-
command = RoborockCommand.GET_WASH_TOWEL_MODE
|
|
13
|
-
requires_dock_type = is_wash_n_fill_dock
|
|
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-4.20.0 → python_roborock-4.22.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/data/b01_q10/b01_q10_containers.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.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
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q10/__init__.py
RENAMED
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/b01/q10/command.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.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-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/network_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/traits/v1/smart_wash_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.0}/roborock/devices/transport/local_channel.py
RENAMED
|
File without changes
|
{python_roborock-4.20.0 → python_roborock-4.22.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
|