python-roborock 5.0.0__tar.gz → 5.1.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.0.0 → python_roborock-5.1.0}/PKG-INFO +3 -2
- {python_roborock-5.0.0 → python_roborock-5.1.0}/pyproject.toml +9 -2
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/broadcast_protocol.py +0 -2
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/code_mappings.py +6 -8
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/v1/v1_code_mappings.py +3 -1
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/device_features.py +2 -4
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/device_manager.py +1 -1
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/rpc/b01_q10_channel.py +0 -2
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/rpc/b01_q7_channel.py +4 -3
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q7/__init__.py +28 -8
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q7/clean_summary.py +0 -2
- python_roborock-5.1.0/roborock/devices/traits/b01/q7/map_content.py +98 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/network_info.py +0 -2
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/diagnostics.py +4 -6
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/exceptions.py +0 -2
- python_roborock-5.1.0/roborock/map/b01_map_parser.py +178 -0
- python_roborock-5.1.0/roborock/map/proto/__init__.py +1 -0
- python_roborock-5.1.0/roborock/map/proto/b01_scmap.proto +70 -0
- python_roborock-5.1.0/roborock/map/proto/b01_scmap_pb2.py +48 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/protocol.py +0 -2
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/roborock_message.py +3 -4
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/roborock_typing.py +2 -3
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/util.py +0 -2
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/web_api.py +0 -2
- {python_roborock-5.0.0 → python_roborock-5.1.0}/.gitignore +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/LICENSE +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/README.md +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/callbacks.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/cli.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/const.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/containers.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/README.md +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/cache.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/device.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/file_cache.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/rpc/a01_channel.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q10/common.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q10/status.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q7/map.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/map/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/protocols/__init__.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/protocols/b01_q7_protocol.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/py.typed +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.1.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
|
-
Project-URL: Repository, https://github.com/
|
|
5
|
+
Project-URL: Repository, https://github.com/python-roborock/python-roborock
|
|
6
6
|
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
7
7
|
Author: Lash-L, allenporter
|
|
8
8
|
Author-email: humbertogontijo <humbertogontijo@users.noreply.github.com>
|
|
@@ -21,6 +21,7 @@ Requires-Dist: click-shell~=2.1
|
|
|
21
21
|
Requires-Dist: click>=8
|
|
22
22
|
Requires-Dist: construct<3,>=2.10.57
|
|
23
23
|
Requires-Dist: paho-mqtt<3.0.0,>=1.6.1
|
|
24
|
+
Requires-Dist: protobuf<7,>=5
|
|
24
25
|
Requires-Dist: pycryptodomex~=3.18; sys_platform == 'darwin'
|
|
25
26
|
Requires-Dist: pycryptodome~=3.18
|
|
26
27
|
Requires-Dist: pyrate-limiter<5,>=4.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "5.
|
|
3
|
+
version = "5.1.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"
|
|
@@ -25,6 +25,7 @@ dependencies = [
|
|
|
25
25
|
"pycryptodomex~=3.18 ; sys_platform == 'darwin'",
|
|
26
26
|
"paho-mqtt>=1.6.1,<3.0.0",
|
|
27
27
|
"construct>=2.10.57,<3",
|
|
28
|
+
"protobuf>=5,<7",
|
|
28
29
|
"vacuum-map-parser-roborock",
|
|
29
30
|
"pyrate-limiter>=4.0.0,<5",
|
|
30
31
|
"aiomqtt>=2.5.0,<3",
|
|
@@ -32,7 +33,7 @@ dependencies = [
|
|
|
32
33
|
]
|
|
33
34
|
|
|
34
35
|
[project.urls]
|
|
35
|
-
Repository = "https://github.com/
|
|
36
|
+
Repository = "https://github.com/python-roborock/python-roborock"
|
|
36
37
|
Documentation = "https://python-roborock.readthedocs.io/"
|
|
37
38
|
|
|
38
39
|
[project.scripts]
|
|
@@ -97,9 +98,15 @@ major_tags= ["refactor"]
|
|
|
97
98
|
lint.ignore = ["F403", "E741"]
|
|
98
99
|
lint.select=["E", "F", "UP", "I"]
|
|
99
100
|
line-length = 120
|
|
101
|
+
extend-exclude = ["roborock/map/proto/*_pb2.py"]
|
|
100
102
|
|
|
101
103
|
[tool.ruff.lint.per-file-ignores]
|
|
102
104
|
"*/__init__.py" = ["F401"]
|
|
105
|
+
"roborock/map/proto/*_pb2.py" = ["E501", "I001", "UP009"]
|
|
106
|
+
|
|
107
|
+
[[tool.mypy.overrides]]
|
|
108
|
+
module = ["roborock.map.proto.*"]
|
|
109
|
+
ignore_errors = true
|
|
103
110
|
|
|
104
111
|
[tool.pytest.ini_options]
|
|
105
112
|
asyncio_mode = "auto"
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import logging
|
|
4
2
|
from collections import namedtuple
|
|
5
3
|
from enum import Enum, IntEnum, StrEnum
|
|
@@ -17,7 +15,7 @@ class RoborockEnum(IntEnum):
|
|
|
17
15
|
return super().name.lower()
|
|
18
16
|
|
|
19
17
|
@classmethod
|
|
20
|
-
def _missing_(cls: type[
|
|
18
|
+
def _missing_(cls: type[Self], key) -> Self:
|
|
21
19
|
if hasattr(cls, "unknown"):
|
|
22
20
|
warning = f"Missing {cls.__name__} code: {key} - defaulting to 'unknown'"
|
|
23
21
|
if warning not in completed_warnings:
|
|
@@ -32,23 +30,23 @@ class RoborockEnum(IntEnum):
|
|
|
32
30
|
return default_value
|
|
33
31
|
|
|
34
32
|
@classmethod
|
|
35
|
-
def as_dict(cls: type[
|
|
33
|
+
def as_dict(cls: type[Self]):
|
|
36
34
|
return {i.name: i.value for i in cls if i.name != "missing"}
|
|
37
35
|
|
|
38
36
|
@classmethod
|
|
39
|
-
def as_enum_dict(cls: type[
|
|
37
|
+
def as_enum_dict(cls: type[Self]):
|
|
40
38
|
return {i.value: i for i in cls if i.name != "missing"}
|
|
41
39
|
|
|
42
40
|
@classmethod
|
|
43
|
-
def values(cls: type[
|
|
41
|
+
def values(cls: type[Self]) -> list[int]:
|
|
44
42
|
return list(cls.as_dict().values())
|
|
45
43
|
|
|
46
44
|
@classmethod
|
|
47
|
-
def keys(cls: type[
|
|
45
|
+
def keys(cls: type[Self]) -> list[str]:
|
|
48
46
|
return list(cls.as_dict().keys())
|
|
49
47
|
|
|
50
48
|
@classmethod
|
|
51
|
-
def items(cls: type[
|
|
49
|
+
def items(cls: type[Self]):
|
|
52
50
|
return cls.as_dict().items()
|
|
53
51
|
|
|
54
52
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
|
|
1
3
|
from ..code_mappings import RoborockEnum
|
|
2
4
|
|
|
3
5
|
|
|
@@ -91,7 +93,7 @@ class RoborockStartType(RoborockEnum):
|
|
|
91
93
|
|
|
92
94
|
class RoborockDssCodes(RoborockEnum):
|
|
93
95
|
@classmethod
|
|
94
|
-
def _missing_(cls: type[
|
|
96
|
+
def _missing_(cls: type[Self], key) -> Self:
|
|
95
97
|
# If the calculated value is not provided, then it should be viewed as okay.
|
|
96
98
|
# As the math will sometimes result in you getting numbers that don't matter.
|
|
97
99
|
return cls.okay # type: ignore
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from dataclasses import dataclass, field, fields
|
|
4
2
|
from enum import IntEnum, StrEnum
|
|
5
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Self
|
|
6
4
|
|
|
7
5
|
from roborock.data.code_mappings import RoborockProductNickname
|
|
8
6
|
from roborock.data.containers import RoborockBase
|
|
@@ -566,7 +564,7 @@ class DeviceFeatures(RoborockBase):
|
|
|
566
564
|
new_feature_info_str: str,
|
|
567
565
|
feature_info: list[int],
|
|
568
566
|
product_nickname: RoborockProductNickname | None,
|
|
569
|
-
) ->
|
|
567
|
+
) -> Self:
|
|
570
568
|
"""Creates a DeviceFeatures instance from raw feature flags.
|
|
571
569
|
:param new_feature_info: A int from get_init_status (sometimes can be found in homedata, but it is not always)
|
|
572
570
|
:param new_feature_info_str: A hex string from get_init_status or home_data.
|
|
@@ -251,7 +251,7 @@ async def create_device_manager(
|
|
|
251
251
|
trait = b01.q10.create(channel)
|
|
252
252
|
elif "sc" in model_part:
|
|
253
253
|
# Q7 devices start with 'sc' in their model naming.
|
|
254
|
-
trait = b01.q7.create(channel)
|
|
254
|
+
trait = b01.q7.create(product, device, channel)
|
|
255
255
|
else:
|
|
256
256
|
raise UnsupportedDeviceError(f"Device {device.name} has unsupported B01 model: {product.model}")
|
|
257
257
|
case _:
|
|
@@ -6,7 +6,7 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
from collections.abc import Callable
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import TypeAlias, TypeVar
|
|
10
10
|
|
|
11
11
|
from roborock.devices.transport.mqtt_channel import MqttChannel
|
|
12
12
|
from roborock.exceptions import RoborockException
|
|
@@ -16,6 +16,7 @@ from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
|
|
|
16
16
|
_LOGGER = logging.getLogger(__name__)
|
|
17
17
|
_TIMEOUT = 10.0
|
|
18
18
|
_T = TypeVar("_T")
|
|
19
|
+
DecodedB01Response: TypeAlias = dict[str, object] | str
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def _matches_map_response(response_message: RoborockMessage, *, version: bytes | None) -> bytes | None:
|
|
@@ -61,11 +62,11 @@ async def _send_command(
|
|
|
61
62
|
async def send_decoded_command(
|
|
62
63
|
mqtt_channel: MqttChannel,
|
|
63
64
|
request_message: Q7RequestMessage,
|
|
64
|
-
) ->
|
|
65
|
+
) -> DecodedB01Response:
|
|
65
66
|
"""Send a command on the MQTT channel and get a decoded response."""
|
|
66
67
|
_LOGGER.debug("Sending B01 MQTT command: %s", request_message)
|
|
67
68
|
|
|
68
|
-
def find_response(response_message: RoborockMessage) ->
|
|
69
|
+
def find_response(response_message: RoborockMessage) -> DecodedB01Response | None:
|
|
69
70
|
"""Handle incoming messages and resolve the future."""
|
|
70
71
|
try:
|
|
71
72
|
decoded_dps = decode_rpc_response(response_message)
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""Traits for Q7 B01 devices.
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
Potentially other devices may fall into this category in the future.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
3
7
|
|
|
4
8
|
from typing import Any
|
|
5
9
|
|
|
6
10
|
from roborock import B01Props
|
|
7
|
-
from roborock.data import Q7MapList, Q7MapListEntry
|
|
11
|
+
from roborock.data import HomeDataDevice, HomeDataProduct, Q7MapList, Q7MapListEntry
|
|
8
12
|
from roborock.data.b01_q7.b01_q7_code_mappings import (
|
|
9
13
|
CleanPathPreferenceMapping,
|
|
10
14
|
CleanRepeatMapping,
|
|
@@ -23,18 +27,20 @@ from roborock.roborock_typing import RoborockB01Q7Methods
|
|
|
23
27
|
|
|
24
28
|
from .clean_summary import CleanSummaryTrait
|
|
25
29
|
from .map import MapTrait
|
|
30
|
+
from .map_content import MapContentTrait
|
|
26
31
|
|
|
27
32
|
__all__ = [
|
|
28
33
|
"Q7PropertiesApi",
|
|
29
34
|
"CleanSummaryTrait",
|
|
30
35
|
"MapTrait",
|
|
36
|
+
"MapContentTrait",
|
|
31
37
|
"Q7MapList",
|
|
32
38
|
"Q7MapListEntry",
|
|
33
39
|
]
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
class Q7PropertiesApi(Trait):
|
|
37
|
-
"""API for interacting with B01 devices."""
|
|
43
|
+
"""API for interacting with B01 Q7 devices."""
|
|
38
44
|
|
|
39
45
|
clean_summary: CleanSummaryTrait
|
|
40
46
|
"""Trait for clean records / clean summary (Q7 `service.get_record_list`)."""
|
|
@@ -42,11 +48,25 @@ class Q7PropertiesApi(Trait):
|
|
|
42
48
|
map: MapTrait
|
|
43
49
|
"""Trait for map list metadata + raw map payload retrieval."""
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
map_content: MapContentTrait
|
|
52
|
+
"""Trait for fetching parsed current map content."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, channel: MqttChannel, *, device: HomeDataDevice, product: HomeDataProduct) -> None:
|
|
55
|
+
"""Initialize the Q7 API."""
|
|
47
56
|
self._channel = channel
|
|
57
|
+
self._device = device
|
|
58
|
+
self._product = product
|
|
59
|
+
|
|
60
|
+
if not device.sn or not product.model:
|
|
61
|
+
raise ValueError("B01 Q7 map content requires device serial number and product model metadata")
|
|
62
|
+
|
|
48
63
|
self.clean_summary = CleanSummaryTrait(channel)
|
|
49
64
|
self.map = MapTrait(channel)
|
|
65
|
+
self.map_content = MapContentTrait(
|
|
66
|
+
self.map,
|
|
67
|
+
serial=device.sn,
|
|
68
|
+
model=product.model,
|
|
69
|
+
)
|
|
50
70
|
|
|
51
71
|
async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None:
|
|
52
72
|
"""Query the device for the values of the given Q7 properties."""
|
|
@@ -151,6 +171,6 @@ class Q7PropertiesApi(Trait):
|
|
|
151
171
|
)
|
|
152
172
|
|
|
153
173
|
|
|
154
|
-
def create(channel: MqttChannel) -> Q7PropertiesApi:
|
|
155
|
-
"""Create traits for B01 devices."""
|
|
156
|
-
return Q7PropertiesApi(channel)
|
|
174
|
+
def create(product: HomeDataProduct, device: HomeDataDevice, channel: MqttChannel) -> Q7PropertiesApi:
|
|
175
|
+
"""Create traits for B01 Q7 devices."""
|
|
176
|
+
return Q7PropertiesApi(channel, device=device, product=product)
|
{python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/b01/q7/clean_summary.py
RENAMED
|
@@ -4,8 +4,6 @@ For B01/Q7, the Roborock app uses `service.get_record_list` which returns totals
|
|
|
4
4
|
and a `record_list` whose items contain a JSON string in `detail`.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
7
|
import logging
|
|
10
8
|
|
|
11
9
|
from roborock import CleanRecordDetail, CleanRecordList, CleanRecordSummary
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Trait for fetching parsed map content from B01/Q7 devices.
|
|
2
|
+
|
|
3
|
+
This intentionally mirrors the v1 `MapContentTrait` contract:
|
|
4
|
+
- `refresh()` performs I/O and populates cached fields
|
|
5
|
+
- `parse_map_content()` reparses cached raw bytes without I/O
|
|
6
|
+
- fields `image_content`, `map_data`, and `raw_api_response` are then readable
|
|
7
|
+
|
|
8
|
+
For B01/Q7 devices, the underlying raw map payload is retrieved via `MapTrait`.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from vacuum_map_parser_base.map_data import MapData
|
|
14
|
+
|
|
15
|
+
from roborock.data import RoborockBase
|
|
16
|
+
from roborock.devices.traits import Trait
|
|
17
|
+
from roborock.exceptions import RoborockException
|
|
18
|
+
from roborock.map.b01_map_parser import B01MapParser, B01MapParserConfig
|
|
19
|
+
|
|
20
|
+
from .map import MapTrait
|
|
21
|
+
|
|
22
|
+
_TRUNCATE_LENGTH = 20
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class MapContent(RoborockBase):
|
|
27
|
+
"""Dataclass representing map content."""
|
|
28
|
+
|
|
29
|
+
image_content: bytes | None = None
|
|
30
|
+
"""The rendered image of the map in PNG format."""
|
|
31
|
+
|
|
32
|
+
map_data: MapData | None = None
|
|
33
|
+
"""Parsed map data (metadata for points on the map)."""
|
|
34
|
+
|
|
35
|
+
raw_api_response: bytes | None = None
|
|
36
|
+
"""Raw bytes of the map payload from the device.
|
|
37
|
+
|
|
38
|
+
This should be treated as an opaque blob used only internally by this
|
|
39
|
+
library to re-parse the map data when needed.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
img = self.image_content
|
|
44
|
+
if img and len(img) > _TRUNCATE_LENGTH:
|
|
45
|
+
img = img[: _TRUNCATE_LENGTH - 3] + b"..."
|
|
46
|
+
return f"MapContent(image_content={img!r}, map_data={self.map_data!r})"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class MapContentTrait(MapContent, Trait):
|
|
50
|
+
"""Trait for fetching parsed map content for Q7 devices."""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
map_trait: MapTrait,
|
|
55
|
+
*,
|
|
56
|
+
serial: str,
|
|
57
|
+
model: str,
|
|
58
|
+
map_parser_config: B01MapParserConfig | None = None,
|
|
59
|
+
) -> None:
|
|
60
|
+
super().__init__()
|
|
61
|
+
self._map_trait = map_trait
|
|
62
|
+
self._serial = serial
|
|
63
|
+
self._model = model
|
|
64
|
+
self._map_parser = B01MapParser(map_parser_config)
|
|
65
|
+
|
|
66
|
+
async def refresh(self) -> None:
|
|
67
|
+
"""Fetch, decode, and parse the current map payload."""
|
|
68
|
+
raw_payload = await self._map_trait.get_current_map_payload()
|
|
69
|
+
parsed = self.parse_map_content(raw_payload)
|
|
70
|
+
self.image_content = parsed.image_content
|
|
71
|
+
self.map_data = parsed.map_data
|
|
72
|
+
self.raw_api_response = parsed.raw_api_response
|
|
73
|
+
|
|
74
|
+
def parse_map_content(self, response: bytes) -> MapContent:
|
|
75
|
+
"""Parse map content from raw bytes.
|
|
76
|
+
|
|
77
|
+
This mirrors the v1 trait behavior so cached map payload bytes can be
|
|
78
|
+
reparsed without going back to the device.
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
parsed_data = self._map_parser.parse(
|
|
82
|
+
response,
|
|
83
|
+
serial=self._serial,
|
|
84
|
+
model=self._model,
|
|
85
|
+
)
|
|
86
|
+
except RoborockException:
|
|
87
|
+
raise
|
|
88
|
+
except Exception as ex:
|
|
89
|
+
raise RoborockException("Failed to parse B01 map data") from ex
|
|
90
|
+
|
|
91
|
+
if parsed_data.image_content is None:
|
|
92
|
+
raise RoborockException("Failed to render B01 map image")
|
|
93
|
+
|
|
94
|
+
return MapContent(
|
|
95
|
+
image_content=parsed_data.image_content,
|
|
96
|
+
map_data=parsed_data.map_data,
|
|
97
|
+
raw_api_response=response,
|
|
98
|
+
)
|
|
@@ -9,13 +9,11 @@ data is collected and exposed to clients via higher level APIs like the
|
|
|
9
9
|
DeviceManager.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
12
|
import time
|
|
15
13
|
from collections import Counter
|
|
16
14
|
from collections.abc import Generator, Mapping
|
|
17
15
|
from contextlib import contextmanager
|
|
18
|
-
from typing import Any, TypeVar, cast
|
|
16
|
+
from typing import Any, Self, TypeVar, cast
|
|
19
17
|
|
|
20
18
|
|
|
21
19
|
class Diagnostics:
|
|
@@ -28,7 +26,7 @@ class Diagnostics:
|
|
|
28
26
|
def __init__(self) -> None:
|
|
29
27
|
"""Initialize Diagnostics."""
|
|
30
28
|
self._counter: Counter = Counter()
|
|
31
|
-
self._subkeys: dict[str,
|
|
29
|
+
self._subkeys: dict[str, Self] = {}
|
|
32
30
|
|
|
33
31
|
def increment(self, key: str, count: int = 1) -> None:
|
|
34
32
|
"""Increment a counter for the specified key/event."""
|
|
@@ -49,7 +47,7 @@ class Diagnostics:
|
|
|
49
47
|
data[k] = v
|
|
50
48
|
return data
|
|
51
49
|
|
|
52
|
-
def subkey(self, key: str) ->
|
|
50
|
+
def subkey(self, key: str) -> Self:
|
|
53
51
|
"""Return sub-Diagnostics object with the specified subkey.
|
|
54
52
|
|
|
55
53
|
This will create a new Diagnostics object if one does not already exist
|
|
@@ -63,7 +61,7 @@ class Diagnostics:
|
|
|
63
61
|
The Diagnostics object for the specified subkey.
|
|
64
62
|
"""
|
|
65
63
|
if key not in self._subkeys:
|
|
66
|
-
self._subkeys[key] =
|
|
64
|
+
self._subkeys[key] = type(self)()
|
|
67
65
|
return self._subkeys[key]
|
|
68
66
|
|
|
69
67
|
@contextmanager
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Module for parsing B01/Q7 map content.
|
|
2
|
+
|
|
3
|
+
Observed Q7 `MAP_RESPONSE` payloads follow this decode pipeline:
|
|
4
|
+
- base64-encoded ASCII
|
|
5
|
+
- AES-ECB encrypted with the derived map key
|
|
6
|
+
- PKCS7 padded
|
|
7
|
+
- ASCII hex for a zlib-compressed SCMap payload
|
|
8
|
+
|
|
9
|
+
The inner SCMap blob is parsed with protobuf messages generated from
|
|
10
|
+
`roborock/map/proto/b01_scmap.proto`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import base64
|
|
14
|
+
import binascii
|
|
15
|
+
import hashlib
|
|
16
|
+
import io
|
|
17
|
+
import zlib
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
from Crypto.Cipher import AES
|
|
21
|
+
from google.protobuf.message import DecodeError, Message
|
|
22
|
+
from PIL import Image
|
|
23
|
+
from vacuum_map_parser_base.config.image_config import ImageConfig
|
|
24
|
+
from vacuum_map_parser_base.map_data import ImageData, MapData
|
|
25
|
+
|
|
26
|
+
from roborock.exceptions import RoborockException
|
|
27
|
+
from roborock.map.proto.b01_scmap_pb2 import RobotMap # type: ignore[attr-defined]
|
|
28
|
+
from roborock.protocol import Utils
|
|
29
|
+
|
|
30
|
+
from .map_parser import ParsedMapData
|
|
31
|
+
|
|
32
|
+
_MAP_FILE_FORMAT = "PNG"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class B01MapParserConfig:
|
|
37
|
+
"""Configuration for the B01/Q7 map parser."""
|
|
38
|
+
|
|
39
|
+
map_scale: int = 4
|
|
40
|
+
"""Scale factor for the rendered map image."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class B01MapParser:
|
|
44
|
+
"""Decoder/parser for B01/Q7 SCMap payloads."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, config: B01MapParserConfig | None = None) -> None:
|
|
47
|
+
self._config = config or B01MapParserConfig()
|
|
48
|
+
|
|
49
|
+
def parse(self, raw_payload: bytes, *, serial: str, model: str) -> ParsedMapData:
|
|
50
|
+
"""Parse a raw MAP_RESPONSE payload and return a PNG + MapData."""
|
|
51
|
+
inflated = _decode_b01_map_payload(raw_payload, serial=serial, model=model)
|
|
52
|
+
parsed = _parse_scmap_payload(inflated)
|
|
53
|
+
size_x, size_y, grid = _extract_grid(parsed)
|
|
54
|
+
room_names = _extract_room_names(parsed)
|
|
55
|
+
|
|
56
|
+
image = _render_occupancy_image(grid, size_x=size_x, size_y=size_y, scale=self._config.map_scale)
|
|
57
|
+
|
|
58
|
+
map_data = MapData()
|
|
59
|
+
map_data.image = ImageData(
|
|
60
|
+
size=size_x * size_y,
|
|
61
|
+
top=0,
|
|
62
|
+
left=0,
|
|
63
|
+
height=size_y,
|
|
64
|
+
width=size_x,
|
|
65
|
+
image_config=ImageConfig(scale=self._config.map_scale),
|
|
66
|
+
data=image,
|
|
67
|
+
img_transformation=lambda p: p,
|
|
68
|
+
)
|
|
69
|
+
if room_names:
|
|
70
|
+
map_data.additional_parameters["room_names"] = room_names
|
|
71
|
+
|
|
72
|
+
image_bytes = io.BytesIO()
|
|
73
|
+
image.save(image_bytes, format=_MAP_FILE_FORMAT)
|
|
74
|
+
|
|
75
|
+
return ParsedMapData(
|
|
76
|
+
image_content=image_bytes.getvalue(),
|
|
77
|
+
map_data=map_data,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _derive_map_key(serial: str, model: str) -> bytes:
|
|
82
|
+
"""Derive the B01/Q7 map decrypt key from serial + model."""
|
|
83
|
+
model_suffix = model.split(".")[-1]
|
|
84
|
+
model_key = (model_suffix + "0" * 16)[:16].encode()
|
|
85
|
+
material = f"{serial}+{model_suffix}+{serial}".encode()
|
|
86
|
+
encrypted = Utils.encrypt_ecb(material, model_key)
|
|
87
|
+
md5 = hashlib.md5(base64.b64encode(encrypted), usedforsecurity=False).hexdigest()
|
|
88
|
+
return md5[8:24].encode()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _decode_base64_payload(raw_payload: bytes) -> bytes:
|
|
92
|
+
blob = raw_payload.strip()
|
|
93
|
+
padded = blob + b"=" * (-len(blob) % 4)
|
|
94
|
+
try:
|
|
95
|
+
return base64.b64decode(padded, validate=True)
|
|
96
|
+
except binascii.Error as err:
|
|
97
|
+
raise RoborockException("Failed to decode B01 map payload") from err
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _decode_b01_map_payload(raw_payload: bytes, *, serial: str, model: str) -> bytes:
|
|
101
|
+
"""Decode raw B01 `MAP_RESPONSE` payload into inflated SCMap bytes."""
|
|
102
|
+
# TODO: Move this lower-level B01 transport decode under `roborock.protocols`
|
|
103
|
+
# so this module only handles SCMap parsing/rendering.
|
|
104
|
+
encrypted_payload = _decode_base64_payload(raw_payload)
|
|
105
|
+
if len(encrypted_payload) % AES.block_size != 0:
|
|
106
|
+
raise RoborockException("Unexpected encrypted B01 map payload length")
|
|
107
|
+
|
|
108
|
+
map_key = _derive_map_key(serial, model)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
compressed_hex = Utils.decrypt_ecb(encrypted_payload, map_key).decode("ascii")
|
|
112
|
+
compressed_payload = bytes.fromhex(compressed_hex)
|
|
113
|
+
return zlib.decompress(compressed_payload)
|
|
114
|
+
except (ValueError, UnicodeDecodeError, zlib.error) as err:
|
|
115
|
+
raise RoborockException("Failed to decode B01 map payload") from err
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _parse_proto(blob: bytes, message: Message, *, context: str) -> None:
|
|
119
|
+
try:
|
|
120
|
+
message.ParseFromString(blob)
|
|
121
|
+
except DecodeError as err:
|
|
122
|
+
raise RoborockException(f"Failed to parse {context}") from err
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _parse_scmap_payload(payload: bytes) -> RobotMap:
|
|
126
|
+
"""Parse inflated SCMap bytes into a generated protobuf message."""
|
|
127
|
+
parsed = RobotMap()
|
|
128
|
+
_parse_proto(payload, parsed, context="B01 SCMap")
|
|
129
|
+
return parsed
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _extract_grid(parsed: RobotMap) -> tuple[int, int, bytes]:
|
|
133
|
+
if not parsed.HasField("mapHead") or not parsed.HasField("mapData"):
|
|
134
|
+
raise RoborockException("Failed to parse B01 map header/grid")
|
|
135
|
+
|
|
136
|
+
size_x = parsed.mapHead.sizeX if parsed.mapHead.HasField("sizeX") else 0
|
|
137
|
+
size_y = parsed.mapHead.sizeY if parsed.mapHead.HasField("sizeY") else 0
|
|
138
|
+
if not size_x or not size_y or not parsed.mapData.HasField("mapData"):
|
|
139
|
+
raise RoborockException("Failed to parse B01 map header/grid")
|
|
140
|
+
|
|
141
|
+
map_data = parsed.mapData.mapData
|
|
142
|
+
expected_len = size_x * size_y
|
|
143
|
+
if len(map_data) < expected_len:
|
|
144
|
+
raise RoborockException("B01 map data shorter than expected dimensions")
|
|
145
|
+
|
|
146
|
+
return size_x, size_y, map_data[:expected_len]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _extract_room_names(parsed: RobotMap) -> dict[int, str]:
|
|
150
|
+
# Expose room id/name mapping without inventing room geometry/polygons.
|
|
151
|
+
room_names: dict[int, str] = {}
|
|
152
|
+
for room in parsed.roomDataInfo:
|
|
153
|
+
if room.HasField("roomId"):
|
|
154
|
+
room_id = room.roomId
|
|
155
|
+
room_names[room_id] = room.roomName if room.HasField("roomName") else f"Room {room_id}"
|
|
156
|
+
return room_names
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _render_occupancy_image(grid: bytes, *, size_x: int, size_y: int, scale: int) -> Image.Image:
|
|
160
|
+
"""Render the B01 occupancy grid into a simple image."""
|
|
161
|
+
|
|
162
|
+
# The observed occupancy grid contains only:
|
|
163
|
+
# - 0: outside/unknown
|
|
164
|
+
# - 127: wall/obstacle
|
|
165
|
+
# - 128: floor/free
|
|
166
|
+
table = bytearray(range(256))
|
|
167
|
+
table[0] = 0
|
|
168
|
+
table[127] = 180
|
|
169
|
+
table[128] = 255
|
|
170
|
+
|
|
171
|
+
mapped = grid.translate(bytes(table))
|
|
172
|
+
img = Image.frombytes("L", (size_x, size_y), mapped)
|
|
173
|
+
img = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM).convert("RGB")
|
|
174
|
+
|
|
175
|
+
if scale > 1:
|
|
176
|
+
img = img.resize((size_x * scale, size_y * scale), resample=Image.Resampling.NEAREST)
|
|
177
|
+
|
|
178
|
+
return img
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Generated protobuf modules for Roborock map payloads."""
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Checked-in B01/Q7 SCMap schema for the generated runtime protobuf module.
|
|
2
|
+
// Regenerate the checked-in Python module after edits with:
|
|
3
|
+
// python -m grpc_tools.protoc -I./roborock/map/proto --python_out=./roborock/map/proto roborock/map/proto/b01_scmap.proto
|
|
4
|
+
// The generated file `b01_scmap_pb2.py` is checked in for runtime use and should
|
|
5
|
+
// not be edited by hand.
|
|
6
|
+
syntax = "proto2";
|
|
7
|
+
|
|
8
|
+
package b01.scmap;
|
|
9
|
+
|
|
10
|
+
message DevicePointInfo {
|
|
11
|
+
optional float x = 1;
|
|
12
|
+
optional float y = 2;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
message MapBoundaryInfo {
|
|
16
|
+
optional string mapMd5 = 1;
|
|
17
|
+
optional uint32 vMinX = 2;
|
|
18
|
+
optional uint32 vMaxX = 3;
|
|
19
|
+
optional uint32 vMinY = 4;
|
|
20
|
+
optional uint32 vMaxY = 5;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
message MapExtInfo {
|
|
24
|
+
optional uint32 taskBeginDate = 1;
|
|
25
|
+
optional uint32 mapUploadDate = 2;
|
|
26
|
+
optional uint32 mapValid = 3;
|
|
27
|
+
optional uint32 radian = 4;
|
|
28
|
+
optional uint32 force = 5;
|
|
29
|
+
optional uint32 cleanPath = 6;
|
|
30
|
+
optional MapBoundaryInfo boudaryInfo = 7;
|
|
31
|
+
optional uint32 mapVersion = 8;
|
|
32
|
+
optional uint32 mapValueType = 9;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
message MapHeadInfo {
|
|
36
|
+
optional uint32 mapHeadId = 1;
|
|
37
|
+
optional uint32 sizeX = 2;
|
|
38
|
+
optional uint32 sizeY = 3;
|
|
39
|
+
optional float minX = 4;
|
|
40
|
+
optional float minY = 5;
|
|
41
|
+
optional float maxX = 6;
|
|
42
|
+
optional float maxY = 7;
|
|
43
|
+
optional float resolution = 8;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
message MapDataInfo {
|
|
47
|
+
optional bytes mapData = 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
message RoomDataInfo {
|
|
51
|
+
optional uint32 roomId = 1;
|
|
52
|
+
optional string roomName = 2;
|
|
53
|
+
optional uint32 roomTypeId = 3;
|
|
54
|
+
optional uint32 meterialId = 4;
|
|
55
|
+
optional uint32 cleanState = 5;
|
|
56
|
+
optional uint32 roomClean = 6;
|
|
57
|
+
optional uint32 roomCleanIndex = 7;
|
|
58
|
+
optional DevicePointInfo roomNamePost = 8;
|
|
59
|
+
optional uint32 colorId = 10;
|
|
60
|
+
optional uint32 floor_direction = 11;
|
|
61
|
+
optional uint32 global_seq = 12;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
message RobotMap {
|
|
65
|
+
optional uint32 mapType = 1;
|
|
66
|
+
optional MapExtInfo mapExtInfo = 2;
|
|
67
|
+
optional MapHeadInfo mapHead = 3;
|
|
68
|
+
optional MapDataInfo mapData = 4;
|
|
69
|
+
repeated RoomDataInfo roomDataInfo = 12;
|
|
70
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: roborock/map/proto/b01_scmap.proto
|
|
5
|
+
# Protobuf Python Version: 6.31.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
6,
|
|
15
|
+
31,
|
|
16
|
+
1,
|
|
17
|
+
'',
|
|
18
|
+
'roborock/map/proto/b01_scmap.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"roborock/map/proto/b01_scmap.proto\x12\tb01.scmap\"\'\n\x0f\x44\x65vicePointInfo\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"]\n\x0fMapBoundaryInfo\x12\x0e\n\x06mapMd5\x18\x01 \x01(\t\x12\r\n\x05vMinX\x18\x02 \x01(\r\x12\r\n\x05vMaxX\x18\x03 \x01(\r\x12\r\n\x05vMinY\x18\x04 \x01(\r\x12\r\n\x05vMaxY\x18\x05 \x01(\r\"\xd9\x01\n\nMapExtInfo\x12\x15\n\rtaskBeginDate\x18\x01 \x01(\r\x12\x15\n\rmapUploadDate\x18\x02 \x01(\r\x12\x10\n\x08mapValid\x18\x03 \x01(\r\x12\x0e\n\x06radian\x18\x04 \x01(\r\x12\r\n\x05\x66orce\x18\x05 \x01(\r\x12\x11\n\tcleanPath\x18\x06 \x01(\r\x12/\n\x0b\x62oudaryInfo\x18\x07 \x01(\x0b\x32\x1a.b01.scmap.MapBoundaryInfo\x12\x12\n\nmapVersion\x18\x08 \x01(\r\x12\x14\n\x0cmapValueType\x18\t \x01(\r\"\x8a\x01\n\x0bMapHeadInfo\x12\x11\n\tmapHeadId\x18\x01 \x01(\r\x12\r\n\x05sizeX\x18\x02 \x01(\r\x12\r\n\x05sizeY\x18\x03 \x01(\r\x12\x0c\n\x04minX\x18\x04 \x01(\x02\x12\x0c\n\x04minY\x18\x05 \x01(\x02\x12\x0c\n\x04maxX\x18\x06 \x01(\x02\x12\x0c\n\x04maxY\x18\x07 \x01(\x02\x12\x12\n\nresolution\x18\x08 \x01(\x02\"\x1e\n\x0bMapDataInfo\x12\x0f\n\x07mapData\x18\x01 \x01(\x0c\"\x87\x02\n\x0cRoomDataInfo\x12\x0e\n\x06roomId\x18\x01 \x01(\r\x12\x10\n\x08roomName\x18\x02 \x01(\t\x12\x12\n\nroomTypeId\x18\x03 \x01(\r\x12\x12\n\nmeterialId\x18\x04 \x01(\r\x12\x12\n\ncleanState\x18\x05 \x01(\r\x12\x11\n\troomClean\x18\x06 \x01(\r\x12\x16\n\x0eroomCleanIndex\x18\x07 \x01(\r\x12\x30\n\x0croomNamePost\x18\x08 \x01(\x0b\x32\x1a.b01.scmap.DevicePointInfo\x12\x0f\n\x07\x63olorId\x18\n \x01(\r\x12\x17\n\x0f\x66loor_direction\x18\x0b \x01(\r\x12\x12\n\nglobal_seq\x18\x0c \x01(\r\"\xc7\x01\n\x08RobotMap\x12\x0f\n\x07mapType\x18\x01 \x01(\r\x12)\n\nmapExtInfo\x18\x02 \x01(\x0b\x32\x15.b01.scmap.MapExtInfo\x12\'\n\x07mapHead\x18\x03 \x01(\x0b\x32\x16.b01.scmap.MapHeadInfo\x12\'\n\x07mapData\x18\x04 \x01(\x0b\x32\x16.b01.scmap.MapDataInfo\x12-\n\x0croomDataInfo\x18\x0c \x03(\x0b\x32\x17.b01.scmap.RoomDataInfo')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'roborock.map.proto.b01_scmap_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
DESCRIPTOR._loaded_options = None
|
|
34
|
+
_globals['_DEVICEPOINTINFO']._serialized_start=49
|
|
35
|
+
_globals['_DEVICEPOINTINFO']._serialized_end=88
|
|
36
|
+
_globals['_MAPBOUNDARYINFO']._serialized_start=90
|
|
37
|
+
_globals['_MAPBOUNDARYINFO']._serialized_end=183
|
|
38
|
+
_globals['_MAPEXTINFO']._serialized_start=186
|
|
39
|
+
_globals['_MAPEXTINFO']._serialized_end=403
|
|
40
|
+
_globals['_MAPHEADINFO']._serialized_start=406
|
|
41
|
+
_globals['_MAPHEADINFO']._serialized_end=544
|
|
42
|
+
_globals['_MAPDATAINFO']._serialized_start=546
|
|
43
|
+
_globals['_MAPDATAINFO']._serialized_end=576
|
|
44
|
+
_globals['_ROOMDATAINFO']._serialized_start=579
|
|
45
|
+
_globals['_ROOMDATAINFO']._serialized_end=842
|
|
46
|
+
_globals['_ROBOTMAP']._serialized_start=845
|
|
47
|
+
_globals['_ROBOTMAP']._serialized_end=1044
|
|
48
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from dataclasses import dataclass, field
|
|
4
2
|
from enum import StrEnum
|
|
3
|
+
from typing import Self
|
|
5
4
|
|
|
6
5
|
from roborock import RoborockEnum
|
|
7
6
|
from roborock.util import get_next_int, get_timestamp
|
|
@@ -37,8 +36,8 @@ class RoborockDataProtocol(RoborockEnum):
|
|
|
37
36
|
OFFLINE_STATUS = 135
|
|
38
37
|
|
|
39
38
|
@classmethod
|
|
40
|
-
def _missing_(cls: type[
|
|
41
|
-
raise ValueError("
|
|
39
|
+
def _missing_(cls: type[Self], key) -> Self:
|
|
40
|
+
raise ValueError(f"{key} not a valid key for Data Protocol")
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
class RoborockDyadDataProtocol(RoborockEnum):
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
from dataclasses import dataclass, field
|
|
4
2
|
from enum import Enum, StrEnum
|
|
3
|
+
from typing import Self
|
|
5
4
|
|
|
6
5
|
from .data import (
|
|
7
6
|
CleanRecord,
|
|
@@ -368,7 +367,7 @@ class DeviceProp(RoborockBase):
|
|
|
368
367
|
):
|
|
369
368
|
self.dust_collection_mode_name = self.dock_summary.dust_collection_mode.mode.name
|
|
370
369
|
|
|
371
|
-
def update(self, device_prop:
|
|
370
|
+
def update(self, device_prop: Self) -> None:
|
|
372
371
|
if device_prop.status:
|
|
373
372
|
self.status = device_prop.status
|
|
374
373
|
if device_prop.clean_summary:
|
|
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.0.0 → python_roborock-5.1.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/data/b01_q7/b01_q7_code_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
File without changes
|
{python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-5.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/flow_led_status.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-5.0.0 → python_roborock-5.1.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.0.0 → python_roborock-5.1.0}/roborock/devices/traits/v1/wash_towel_mode.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|