python-roborock 7.15.0__tar.gz → 7.15.2__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-7.15.0 → python_roborock-7.15.2}/PKG-INFO +1 -1
- {python_roborock-7.15.0 → python_roborock-7.15.2}/pyproject.toml +1 -1
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/mower/mower_code_mappings.py +4 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/mower/mower_containers.py +1 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/b01_q10_map_parser.py +0 -118
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/b01_q10_render.py +92 -6
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/protocols/v1_protocol.py +52 -6
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/roborock_message.py +1 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/testing/v1_simulator.py +44 -5
- python_roborock-7.15.2/roborock/web_api/__init__.py +37 -0
- python_roborock-7.15.2/roborock/web_api/_base.py +168 -0
- python_roborock-7.15.2/roborock/web_api/_hawk.py +58 -0
- python_roborock-7.15.2/roborock/web_api/_request.py +44 -0
- python_roborock-7.15.2/roborock/web_api/auth.py +278 -0
- python_roborock-7.15.2/roborock/web_api/client.py +80 -0
- python_roborock-7.15.2/roborock/web_api/devices.py +84 -0
- python_roborock-7.15.2/roborock/web_api/homes.py +81 -0
- python_roborock-7.15.2/roborock/web_api/jobs.py +50 -0
- python_roborock-7.15.2/roborock/web_api/ota.py +44 -0
- python_roborock-7.15.2/roborock/web_api/products.py +49 -0
- python_roborock-7.15.2/roborock/web_api/scenes.py +28 -0
- python_roborock-7.15.0/roborock/web_api.py +0 -977
- {python_roborock-7.15.0 → python_roborock-7.15.2}/.gitignore +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/LICENSE +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/README.md +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/callbacks.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/cli.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/const.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/camera.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/code_mappings.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/containers.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/mower/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/v1/v1_dynamic_map.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/device_features.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/README.md +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/cache.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/device.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/device_manager.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/file_cache.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/rpc/a01_channel.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/rpc/b01_q10_channel.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/rpc/b01_q7_channel.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/a01/device_feature.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/button_light.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/common.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/consumable.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/goto.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/map.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/maps.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/network_info.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/remote.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/status.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q10/volume.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q7/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q7/map.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/b01/q7/map_content.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/common.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/mop_dryer.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/obstacle_photos.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/diagnostics.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/exceptions.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/b01_grid_layers.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/b01_map_parser.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/b01_q10_overlays.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/dynamic_blocks.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/map_parser.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/proto/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/proto/b01_scmap.proto +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/proto/b01_scmap_pb2.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/map/room_colors.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/mqtt/session.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/protocol.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/protocols/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/protocols/b01_q7_protocol.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/protocols/camera_signaling.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/py.typed +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/roborock_typing.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/testing/__init__.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/testing/a01_simulator.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/testing/b01_q10_simulator.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/testing/channel.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/testing/cloud.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/testing/simulator.py +0 -0
- {python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 7.15.
|
|
3
|
+
Version: 7.15.2
|
|
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 = "7.15.
|
|
3
|
+
version = "7.15.2"
|
|
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"
|
{python_roborock-7.15.0 → python_roborock-7.15.2}/roborock/data/mower/mower_code_mappings.py
RENAMED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""Code mappings for Roborock mower devices."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from typing import Self, cast
|
|
4
5
|
|
|
5
6
|
from roborock.data.code_mappings import RoborockModeEnum
|
|
6
7
|
|
|
8
|
+
_LOGGER = logging.getLogger(__name__)
|
|
9
|
+
|
|
7
10
|
__all__ = [
|
|
8
11
|
"MowerAfsStatus",
|
|
9
12
|
"MowerChargeState",
|
|
@@ -38,6 +41,7 @@ class MowerEnum(RoborockModeEnum):
|
|
|
38
41
|
"""Resolve a wire value by name, string value, or integer code."""
|
|
39
42
|
if (member := cls.from_any_optional(code)) is not None:
|
|
40
43
|
return member
|
|
44
|
+
_LOGGER.debug("Unknown %s code %r; defaulting to UNKNOWN", cls.__name__, code)
|
|
41
45
|
return cast(Self, cls.UNKNOWN) # type: ignore[attr-defined]
|
|
42
46
|
|
|
43
47
|
|
|
@@ -48,6 +48,7 @@ class MowerStatus(RoborockBase):
|
|
|
48
48
|
"""Core mower status backed by mower DPS updates (data points 120-145)."""
|
|
49
49
|
|
|
50
50
|
error_code: MowerErrorCode | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.ERROR_CODE})
|
|
51
|
+
"""None means no error code was supplied; UNKNOWN means an unknown error."""
|
|
51
52
|
battery: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.BATTERY})
|
|
52
53
|
mow_type: MowerMowType | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_TYPE})
|
|
53
54
|
mow_state: MowerDetailState | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_STATE})
|
|
@@ -20,17 +20,11 @@ documentation that informed this clean-room implementation comes from the
|
|
|
20
20
|
https://github.com/v1b3c0d3x3r/roborock-qseries-map-bridge
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
import io
|
|
24
23
|
import math
|
|
25
24
|
import statistics
|
|
26
25
|
import struct
|
|
27
26
|
from dataclasses import dataclass, field, replace
|
|
28
27
|
|
|
29
|
-
from PIL import Image
|
|
30
|
-
from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
|
|
31
|
-
from vacuum_map_parser_base.config.image_config import ImageConfig
|
|
32
|
-
from vacuum_map_parser_base.map_data import ImageData, MapData, Point
|
|
33
|
-
|
|
34
28
|
from roborock.data.b01_q10.b01_q10_containers import Q10RoborockPoint
|
|
35
29
|
from roborock.data.code_mappings import RoborockEnum
|
|
36
30
|
from roborock.data.containers import RoborockBase
|
|
@@ -44,10 +38,6 @@ from .b01_grid_layers import (
|
|
|
44
38
|
GridLayers,
|
|
45
39
|
decompose_grid,
|
|
46
40
|
)
|
|
47
|
-
from .map_parser import ParsedMapData
|
|
48
|
-
from .room_colors import adjacency_aware_room_colors
|
|
49
|
-
|
|
50
|
-
_MAP_FILE_FORMAT = "PNG"
|
|
51
41
|
|
|
52
42
|
# Semantic raster classes, confirmed against real ss07 captures (rendered and
|
|
53
43
|
# eyeballed): 243 is the background outside the home (~half the grid), 240 is
|
|
@@ -776,111 +766,3 @@ class B01Q10MapParserConfig:
|
|
|
776
766
|
|
|
777
767
|
map_scale: int = 4
|
|
778
768
|
"""Scale factor for the rendered map image."""
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
class B01Q10MapParser:
|
|
782
|
-
"""Decoder/renderer for Q10 ``MAP_RESPONSE`` (protocol 301) payloads."""
|
|
783
|
-
|
|
784
|
-
def __init__(self, config: B01Q10MapParserConfig | None = None) -> None:
|
|
785
|
-
self._config = config or B01Q10MapParserConfig()
|
|
786
|
-
|
|
787
|
-
@property
|
|
788
|
-
def config(self) -> B01Q10MapParserConfig:
|
|
789
|
-
"""The parser configuration (image scale, ...)."""
|
|
790
|
-
return self._config
|
|
791
|
-
|
|
792
|
-
def parse(self, payload: bytes) -> ParsedMapData:
|
|
793
|
-
"""Parse a raw Q10 map packet into a rendered PNG + ``MapData``."""
|
|
794
|
-
return self.parsed_from_packet(parse_map_packet(payload))
|
|
795
|
-
|
|
796
|
-
def parse_packet(self, packet: Q10MapPacket) -> ParsedMapData:
|
|
797
|
-
"""Render an already-decoded packet.
|
|
798
|
-
|
|
799
|
-
This preserves the public API used by the existing map trait. Callers
|
|
800
|
-
rendering a modified packet can use :meth:`parsed_from_packet` directly.
|
|
801
|
-
"""
|
|
802
|
-
return self.parsed_from_packet(packet)
|
|
803
|
-
|
|
804
|
-
def map_data_from_packet(self, packet: Q10MapPacket) -> MapData:
|
|
805
|
-
"""Build MapData with a rendered base image, without PNG serialization."""
|
|
806
|
-
image = self._render(packet)
|
|
807
|
-
|
|
808
|
-
map_data = MapData()
|
|
809
|
-
map_data.image = ImageData(
|
|
810
|
-
size=packet.width * packet.height,
|
|
811
|
-
top=0,
|
|
812
|
-
left=0,
|
|
813
|
-
height=packet.height,
|
|
814
|
-
width=packet.width,
|
|
815
|
-
image_config=ImageConfig(scale=self._config.map_scale),
|
|
816
|
-
data=image,
|
|
817
|
-
# ImageDimensions uses V1's bottom-up map convention before
|
|
818
|
-
# projecting into the top-down PNG. Q10 points are already
|
|
819
|
-
# top-down grid pixels, so this adapter cancels that standard flip
|
|
820
|
-
# and lets Q10 use the shared V1 ImageGenerator without moving
|
|
821
|
-
# overlays vertically.
|
|
822
|
-
img_transformation=lambda p: Point(p.x, packet.height - p.y - 1, p.a),
|
|
823
|
-
)
|
|
824
|
-
room_names = {room.id: room.name for room in packet.rooms}
|
|
825
|
-
if room_names:
|
|
826
|
-
map_data.additional_parameters["room_names"] = room_names
|
|
827
|
-
|
|
828
|
-
# Carpet cells are flat grid indices (y*width + x) in the same top-down
|
|
829
|
-
# pixel space as the rendered raster, so they line up with the image.
|
|
830
|
-
if packet.carpet_mask is not None:
|
|
831
|
-
map_data.carpet_map = {i for i, value in enumerate(packet.carpet_mask) if value}
|
|
832
|
-
|
|
833
|
-
return map_data
|
|
834
|
-
|
|
835
|
-
def parsed_from_packet(self, packet: Q10MapPacket) -> ParsedMapData:
|
|
836
|
-
"""Render a (possibly erase-modified) packet into a PNG + ``MapData``."""
|
|
837
|
-
map_data = self.map_data_from_packet(packet)
|
|
838
|
-
image_bytes = io.BytesIO()
|
|
839
|
-
if map_data.image is not None:
|
|
840
|
-
map_data.image.data.save(image_bytes, format=_MAP_FILE_FORMAT)
|
|
841
|
-
return ParsedMapData(image_content=image_bytes.getvalue(), map_data=map_data)
|
|
842
|
-
|
|
843
|
-
def _render(self, packet: Q10MapPacket) -> Image.Image:
|
|
844
|
-
"""Render the Q10 grid with the V1 map palette."""
|
|
845
|
-
palette = _build_palette(packet.grid, packet.width)
|
|
846
|
-
flat_palette = [channel for color in palette for channel in color]
|
|
847
|
-
p_img = Image.frombytes("P", (packet.width, packet.height), packet.grid)
|
|
848
|
-
p_img.putpalette(flat_palette, rawmode="RGBA")
|
|
849
|
-
img = p_img.convert("RGBA")
|
|
850
|
-
# The ss07 grid is stored top-down (row 0 = top of the home), so it is
|
|
851
|
-
# rendered as-is -- unlike the V1/Q7 convention, no vertical flip.
|
|
852
|
-
scale = self._config.map_scale
|
|
853
|
-
if scale > 1:
|
|
854
|
-
img = img.resize((packet.width * scale, packet.height * scale), resample=Image.Resampling.NEAREST)
|
|
855
|
-
return img
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
def _opaque(color: tuple[int, ...]) -> tuple[int, int, int, int]:
|
|
859
|
-
"""Return a palette color as RGBA."""
|
|
860
|
-
return (color[0], color[1], color[2], color[3] if len(color) == 4 else 255)
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
def _build_palette(grid: bytes, width: int) -> list[tuple[int, int, int, int]]:
|
|
864
|
-
"""Map Q10 cells onto the same colors used by the V1 map renderer."""
|
|
865
|
-
|
|
866
|
-
def room_id(value: int) -> int | None:
|
|
867
|
-
return max(1, value // 4) if 0 < value < _WALL_THRESHOLD else None
|
|
868
|
-
|
|
869
|
-
colors = ColorsPalette()
|
|
870
|
-
room_colors = adjacency_aware_room_colors(
|
|
871
|
-
grid,
|
|
872
|
-
width,
|
|
873
|
-
colors,
|
|
874
|
-
room_id,
|
|
875
|
-
)
|
|
876
|
-
outside = (0, 0, 0, 0)
|
|
877
|
-
palette = [outside] * 256
|
|
878
|
-
for value in {value for value in grid if 0 < value < _WALL_THRESHOLD}:
|
|
879
|
-
palette[value] = _opaque(room_colors[max(1, value // 4)])
|
|
880
|
-
wall = _opaque(colors.get_color(SupportedColor.GREY_WALL))
|
|
881
|
-
for value in range(_WALL_THRESHOLD, 256):
|
|
882
|
-
palette[value] = wall
|
|
883
|
-
palette[_UNSEGMENTED_FLOOR_VALUE] = _opaque(colors.get_color(SupportedColor.MAP_INSIDE))
|
|
884
|
-
palette[_BACKGROUND_VALUE] = outside
|
|
885
|
-
palette[0] = outside
|
|
886
|
-
return palette
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Compose a Q10 (B01/ss07) map into a single rendered result.
|
|
2
2
|
|
|
3
|
-
The :
|
|
4
|
-
into a :class:`~roborock.map.b01_q10_map_parser.Q10MapPacket`; this module
|
|
3
|
+
The :func:`~roborock.map.b01_q10_map_parser.parse_map_packet` function turns wire
|
|
4
|
+
bytes into a :class:`~roborock.map.b01_q10_map_parser.Q10MapPacket`; this module
|
|
5
5
|
combines that map-protocol packet with the latest trace-protocol packet and DPS
|
|
6
6
|
overlay snapshot. Calibration, path and position are derived from those source
|
|
7
7
|
objects rather than managed independently by callers.
|
|
@@ -18,9 +18,12 @@ import math
|
|
|
18
18
|
from collections.abc import Sequence
|
|
19
19
|
from dataclasses import dataclass
|
|
20
20
|
|
|
21
|
+
from PIL import Image
|
|
22
|
+
from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
|
|
21
23
|
from vacuum_map_parser_base.config.drawable import Drawable
|
|
24
|
+
from vacuum_map_parser_base.config.image_config import ImageConfig
|
|
22
25
|
from vacuum_map_parser_base.config.size import Size, Sizes
|
|
23
|
-
from vacuum_map_parser_base.map_data import Area, MapData, Path, Point, Wall
|
|
26
|
+
from vacuum_map_parser_base.map_data import Area, ImageData, MapData, Path, Point, Wall
|
|
24
27
|
|
|
25
28
|
from roborock.exceptions import RoborockException
|
|
26
29
|
|
|
@@ -31,7 +34,9 @@ from .b01_grid_layers import (
|
|
|
31
34
|
solve_calibration_with_origin,
|
|
32
35
|
)
|
|
33
36
|
from .b01_q10_map_parser import (
|
|
34
|
-
|
|
37
|
+
_BACKGROUND_VALUE,
|
|
38
|
+
_UNSEGMENTED_FLOOR_VALUE,
|
|
39
|
+
_WALL_THRESHOLD,
|
|
35
40
|
B01Q10MapParserConfig,
|
|
36
41
|
Q10EraseZone,
|
|
37
42
|
Q10HistoricalTracePacket,
|
|
@@ -41,6 +46,7 @@ from .b01_q10_map_parser import (
|
|
|
41
46
|
)
|
|
42
47
|
from .b01_q10_overlays import ZONE_TYPE_NO_GO, ZONE_TYPE_NO_MOP, Q10Zone
|
|
43
48
|
from .map_parser import DEFAULT_DRAWABLES, MapParserConfig, _create_image_generator
|
|
49
|
+
from .room_colors import adjacency_aware_room_colors
|
|
44
50
|
|
|
45
51
|
# Path-units-per-pixel candidates for calibration. A dense ss07 path lands a
|
|
46
52
|
# best fit of 20.0 around the header origin -- ground-truthed June 2026 on the
|
|
@@ -97,7 +103,6 @@ def render_q10_map(
|
|
|
97
103
|
available trace and DPS overlays are projected and drawn in pixel space.
|
|
98
104
|
Raises :class:`RoborockException` if map rendering fails.
|
|
99
105
|
"""
|
|
100
|
-
parser = B01Q10MapParser(config)
|
|
101
106
|
trace_calibration = solve_q10_calibration(packet, trace)
|
|
102
107
|
vector_calibration = _vector_calibration(packet, trace_calibration)
|
|
103
108
|
|
|
@@ -109,7 +114,7 @@ def render_q10_map(
|
|
|
109
114
|
# areas disappear (as the app shows).
|
|
110
115
|
render_packet = erased_packet(packet, cells)
|
|
111
116
|
|
|
112
|
-
map_data =
|
|
117
|
+
map_data = _map_data_from_packet(render_packet, config=config)
|
|
113
118
|
if map_data.image is None:
|
|
114
119
|
raise RoborockException("Failed to render Q10 map image")
|
|
115
120
|
|
|
@@ -335,3 +340,84 @@ def _draw_map_content(
|
|
|
335
340
|
buffer = io.BytesIO()
|
|
336
341
|
map_data.image.data.save(buffer, format="PNG")
|
|
337
342
|
return buffer.getvalue()
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _map_data_from_packet(
|
|
346
|
+
packet: Q10MapPacket,
|
|
347
|
+
*,
|
|
348
|
+
config: B01Q10MapParserConfig,
|
|
349
|
+
) -> MapData:
|
|
350
|
+
"""Build MapData with a rendered base image, without PNG serialization."""
|
|
351
|
+
image = _render_base_grid(packet, scale=config.map_scale)
|
|
352
|
+
|
|
353
|
+
map_data = MapData()
|
|
354
|
+
map_data.image = ImageData(
|
|
355
|
+
size=packet.width * packet.height,
|
|
356
|
+
top=0,
|
|
357
|
+
left=0,
|
|
358
|
+
height=packet.height,
|
|
359
|
+
width=packet.width,
|
|
360
|
+
image_config=ImageConfig(scale=config.map_scale),
|
|
361
|
+
data=image,
|
|
362
|
+
# ImageDimensions uses V1's bottom-up map convention before
|
|
363
|
+
# projecting into the top-down PNG. Q10 points are already
|
|
364
|
+
# top-down grid pixels, so this adapter cancels that standard flip
|
|
365
|
+
# and lets Q10 use the shared V1 ImageGenerator without moving
|
|
366
|
+
# overlays vertically.
|
|
367
|
+
img_transformation=lambda p: Point(p.x, packet.height - p.y - 1, p.a),
|
|
368
|
+
)
|
|
369
|
+
room_names = {room.id: room.name for room in packet.rooms}
|
|
370
|
+
if room_names:
|
|
371
|
+
map_data.additional_parameters["room_names"] = room_names
|
|
372
|
+
|
|
373
|
+
# Carpet cells are flat grid indices (y*width + x) in the same top-down
|
|
374
|
+
# pixel space as the rendered raster, so they line up with the image.
|
|
375
|
+
if packet.carpet_mask is not None:
|
|
376
|
+
map_data.carpet_map = {i for i, value in enumerate(packet.carpet_mask) if value}
|
|
377
|
+
|
|
378
|
+
return map_data
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _render_base_grid(packet: Q10MapPacket, *, scale: int) -> Image.Image:
|
|
382
|
+
"""Render the Q10 grid with the V1 map palette."""
|
|
383
|
+
palette = _build_palette(packet.grid, packet.width)
|
|
384
|
+
flat_palette = [channel for color in palette for channel in color]
|
|
385
|
+
p_img = Image.frombytes("P", (packet.width, packet.height), packet.grid)
|
|
386
|
+
p_img.putpalette(flat_palette, rawmode="RGBA")
|
|
387
|
+
img = p_img.convert("RGBA")
|
|
388
|
+
# The ss07 grid is stored top-down (row 0 = top of the home), so it is
|
|
389
|
+
# rendered as-is -- unlike the V1/Q7 convention, no vertical flip.
|
|
390
|
+
if scale > 1:
|
|
391
|
+
img = img.resize((packet.width * scale, packet.height * scale), resample=Image.Resampling.NEAREST)
|
|
392
|
+
return img
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _opaque(color: tuple[int, ...]) -> tuple[int, int, int, int]:
|
|
396
|
+
"""Return a palette color as RGBA."""
|
|
397
|
+
return (color[0], color[1], color[2], color[3] if len(color) == 4 else 255)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _build_palette(grid: bytes, width: int) -> list[tuple[int, int, int, int]]:
|
|
401
|
+
"""Map Q10 cells onto the same colors used by the V1 map renderer."""
|
|
402
|
+
|
|
403
|
+
def room_id(value: int) -> int | None:
|
|
404
|
+
return max(1, value // 4) if 0 < value < _WALL_THRESHOLD else None
|
|
405
|
+
|
|
406
|
+
colors = ColorsPalette()
|
|
407
|
+
room_colors = adjacency_aware_room_colors(
|
|
408
|
+
grid,
|
|
409
|
+
width,
|
|
410
|
+
colors,
|
|
411
|
+
room_id,
|
|
412
|
+
)
|
|
413
|
+
outside = (0, 0, 0, 0)
|
|
414
|
+
palette = [outside] * 256
|
|
415
|
+
for value in {value for value in grid if 0 < value < _WALL_THRESHOLD}:
|
|
416
|
+
palette[value] = _opaque(room_colors[max(1, value // 4)])
|
|
417
|
+
wall = _opaque(colors.get_color(SupportedColor.GREY_WALL))
|
|
418
|
+
for value in range(_WALL_THRESHOLD, 256):
|
|
419
|
+
palette[value] = wall
|
|
420
|
+
palette[_UNSEGMENTED_FLOOR_VALUE] = _opaque(colors.get_color(SupportedColor.MAP_INSIDE))
|
|
421
|
+
palette[_BACKGROUND_VALUE] = outside
|
|
422
|
+
palette[0] = outside
|
|
423
|
+
return palette
|
|
@@ -306,16 +306,29 @@ def create_blob_response_decoder() -> Callable[[RoborockMessage], BlobResponse |
|
|
|
306
306
|
return _decode_blob_response
|
|
307
307
|
|
|
308
308
|
|
|
309
|
+
# Number of incomplete multi-part map responses kept while waiting for missing parts.
|
|
310
|
+
_MAX_PENDING_MAP_RESPONSES = 4
|
|
311
|
+
|
|
312
|
+
|
|
309
313
|
def create_map_response_decoder(security_data: SecurityData) -> Callable[[RoborockMessage], MapResponse | None]:
|
|
310
|
-
"""Create a decoder for V1 map response messages.
|
|
314
|
+
"""Create a decoder for V1 map response messages.
|
|
311
315
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
316
|
+
Large maps are split into multiple messages that share the same ``random``
|
|
317
|
+
and ``timestamp`` and are numbered by ``seq``. All parts except the last
|
|
318
|
+
one use protocol ``MAP_RESPONSE_PART`` (300), the last one uses
|
|
319
|
+
``MAP_RESPONSE`` (301). Only the first part starts with the map header.
|
|
320
|
+
Parts may arrive out of order, so they are buffered until complete.
|
|
321
|
+
"""
|
|
322
|
+
endpoint_bytes = security_data.endpoint.encode()
|
|
323
|
+
# (random, timestamp) -> seq -> (is last part, payload)
|
|
324
|
+
pending: dict[tuple[int, int], dict[int, tuple[bool, bytes]]] = {}
|
|
325
|
+
|
|
326
|
+
def _decode_payload(payload: bytes) -> MapResponse | None:
|
|
327
|
+
if len(payload) < 24:
|
|
315
328
|
raise RoborockException("Invalid V1 map response format: missing payload")
|
|
316
|
-
header, body =
|
|
329
|
+
header, body = payload[:24], payload[24:]
|
|
317
330
|
[endpoint, _, request_id, _] = struct.unpack("<8s8sH6s", header)
|
|
318
|
-
if not endpoint.
|
|
331
|
+
if not endpoint.startswith(endpoint_bytes):
|
|
319
332
|
_LOGGER.debug("Received map response not requested by this device, ignoring.")
|
|
320
333
|
return None
|
|
321
334
|
try:
|
|
@@ -325,6 +338,39 @@ def create_map_response_decoder(security_data: SecurityData) -> Callable[[Roboro
|
|
|
325
338
|
decompressed = Utils.decompress(decrypted)
|
|
326
339
|
return MapResponse(request_id=request_id, data=decompressed)
|
|
327
340
|
|
|
341
|
+
def _remember(key: tuple[int, int], parts: dict[int, tuple[bool, bytes]]) -> None:
|
|
342
|
+
pending[key] = parts
|
|
343
|
+
while len(pending) > _MAX_PENDING_MAP_RESPONSES:
|
|
344
|
+
del pending[next(iter(pending))]
|
|
345
|
+
|
|
346
|
+
def _add_part(key: tuple[int, int], seq: int, is_last: bool, payload: bytes) -> MapResponse | None:
|
|
347
|
+
parts = pending.pop(key, {})
|
|
348
|
+
parts[seq] = (is_last, payload)
|
|
349
|
+
seqs = sorted(parts)
|
|
350
|
+
complete = (
|
|
351
|
+
parts[seqs[-1]][0]
|
|
352
|
+
and seqs == list(range(seqs[0], seqs[-1] + 1))
|
|
353
|
+
and parts[seqs[0]][1].startswith(endpoint_bytes)
|
|
354
|
+
)
|
|
355
|
+
if not complete:
|
|
356
|
+
_remember(key, parts)
|
|
357
|
+
return None
|
|
358
|
+
return _decode_payload(b"".join(parts[s][1] for s in seqs))
|
|
359
|
+
|
|
360
|
+
def _decode_map_response(message: RoborockMessage) -> MapResponse | None:
|
|
361
|
+
"""Decode a V1 map response message."""
|
|
362
|
+
if not message.payload:
|
|
363
|
+
raise RoborockException("Invalid V1 map response format: missing payload")
|
|
364
|
+
payload = message.payload
|
|
365
|
+
key = (message.random, message.timestamp)
|
|
366
|
+
is_last = message.protocol == RoborockMessageProtocol.MAP_RESPONSE
|
|
367
|
+
if message.protocol == RoborockMessageProtocol.MAP_RESPONSE_PART or (is_last and key in pending):
|
|
368
|
+
return _add_part(key, message.seq, is_last, payload)
|
|
369
|
+
if is_last and not payload.startswith(endpoint_bytes):
|
|
370
|
+
# Possibly the last part of a multi-part response that arrived first.
|
|
371
|
+
_remember(key, {message.seq: (True, payload)})
|
|
372
|
+
return _decode_payload(payload)
|
|
373
|
+
|
|
328
374
|
return _decode_map_response
|
|
329
375
|
|
|
330
376
|
|
|
@@ -10,7 +10,7 @@ import json
|
|
|
10
10
|
import logging
|
|
11
11
|
import struct
|
|
12
12
|
import time
|
|
13
|
-
from collections.abc import Callable
|
|
13
|
+
from collections.abc import Callable, Sequence
|
|
14
14
|
from dataclasses import asdict, replace
|
|
15
15
|
from enum import Enum
|
|
16
16
|
from typing import Any
|
|
@@ -45,6 +45,7 @@ from roborock.protocols.v1_protocol import SecurityData
|
|
|
45
45
|
from roborock.roborock_message import RoborockDataProtocol, RoborockMessage, RoborockMessageProtocol
|
|
46
46
|
from roborock.testing.channel import FakeChannel
|
|
47
47
|
from roborock.testing.simulator import RoborockDeviceSimulator
|
|
48
|
+
from roborock.util import get_next_int
|
|
48
49
|
|
|
49
50
|
_LOGGER = logging.getLogger(__name__)
|
|
50
51
|
|
|
@@ -255,8 +256,12 @@ class V1VacuumSimulator(RoborockDeviceSimulator):
|
|
|
255
256
|
|
|
256
257
|
# The simulated robot's own view of its map. Tests extend the path
|
|
257
258
|
# with advance_path() and move the robot via robot_position.
|
|
259
|
+
# Set map_parts > 1 (and optionally map_part_order) to split map
|
|
260
|
+
# responses across MAP_RESPONSE_PART (300) and MAP_RESPONSE (301) frames.
|
|
258
261
|
self.path_length = 0
|
|
259
262
|
self.robot_position = (25000, 24000, 90)
|
|
263
|
+
self.map_parts = 1
|
|
264
|
+
self.map_part_order: Sequence[int] | None = None
|
|
260
265
|
|
|
261
266
|
self.device_cache = DeviceCache(self.duid, InMemoryCache())
|
|
262
267
|
# The endpoint must fit the eight byte map response header field and the
|
|
@@ -416,11 +421,44 @@ class V1VacuumSimulator(RoborockDeviceSimulator):
|
|
|
416
421
|
data = header + body + digest_header
|
|
417
422
|
return data + hashlib.sha1(data, usedforsecurity=False).digest()
|
|
418
423
|
|
|
419
|
-
def
|
|
420
|
-
"""Frame the map as
|
|
424
|
+
def _map_response_messages(self, request_id: int, map_bytes: bytes) -> list[RoborockMessage]:
|
|
425
|
+
"""Frame the map as one or more encrypted binary messages as the device sends."""
|
|
426
|
+
if self.map_parts < 1:
|
|
427
|
+
raise ValueError("map_parts must be at least 1")
|
|
421
428
|
body = Utils.encrypt_cbc(gzip.compress(map_bytes), self.security_data.nonce)
|
|
422
429
|
header = struct.pack("<8s8sH6s", self.security_data.endpoint.encode(), bytes(8), request_id & 0xFFFF, bytes(6))
|
|
423
|
-
|
|
430
|
+
if self.map_parts == 1:
|
|
431
|
+
if self.map_part_order is not None and list(self.map_part_order) != [0]:
|
|
432
|
+
raise ValueError("map_part_order must be a permutation of range(map_parts)")
|
|
433
|
+
return [RoborockMessage(protocol=RoborockMessageProtocol.MAP_RESPONSE, payload=header + body)]
|
|
434
|
+
if self.map_parts > len(body):
|
|
435
|
+
raise ValueError("map_parts cannot exceed encrypted map body length")
|
|
436
|
+
|
|
437
|
+
# Keep the 24-byte header intact on the first part and split the encrypted
|
|
438
|
+
# body evenly into map_parts non-empty contiguous slices.
|
|
439
|
+
total = len(body)
|
|
440
|
+
boundaries = [(i * total) // self.map_parts for i in range(self.map_parts + 1)]
|
|
441
|
+
chunks = [body[boundaries[i] : boundaries[i + 1]] for i in range(self.map_parts)]
|
|
442
|
+
chunks[0] = header + chunks[0]
|
|
443
|
+
random_id = get_next_int(10000, 99999)
|
|
444
|
+
timestamp = int(time.time())
|
|
445
|
+
messages = [
|
|
446
|
+
RoborockMessage(
|
|
447
|
+
protocol=RoborockMessageProtocol.MAP_RESPONSE
|
|
448
|
+
if idx == len(chunks) - 1
|
|
449
|
+
else RoborockMessageProtocol.MAP_RESPONSE_PART,
|
|
450
|
+
payload=chunk,
|
|
451
|
+
seq=idx + 1,
|
|
452
|
+
random=random_id,
|
|
453
|
+
timestamp=timestamp,
|
|
454
|
+
)
|
|
455
|
+
for idx, chunk in enumerate(chunks)
|
|
456
|
+
]
|
|
457
|
+
if self.map_part_order is not None:
|
|
458
|
+
if sorted(self.map_part_order) != list(range(self.map_parts)):
|
|
459
|
+
raise ValueError("map_part_order must be a permutation of range(map_parts)")
|
|
460
|
+
messages = [messages[i] for i in self.map_part_order]
|
|
461
|
+
return messages
|
|
424
462
|
|
|
425
463
|
async def _handle_publish(self, message: RoborockMessage, channel: FakeChannel) -> None:
|
|
426
464
|
if not message.payload:
|
|
@@ -455,7 +493,8 @@ class V1VacuumSimulator(RoborockDeviceSimulator):
|
|
|
455
493
|
|
|
456
494
|
if method == "get_map_v1" and error is None and isinstance(result, bytes):
|
|
457
495
|
# Custom handlers return decompressed map bytes, just like the default.
|
|
458
|
-
|
|
496
|
+
for map_msg in self._map_response_messages(msg_id, result):
|
|
497
|
+
channel.notify_subscribers(map_msg)
|
|
459
498
|
return
|
|
460
499
|
|
|
461
500
|
response_data = {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Roborock cloud REST API clients.
|
|
2
|
+
|
|
3
|
+
The public entry points are :class:`RoborockApiClient` and :class:`UserWebApiClient`.
|
|
4
|
+
Endpoints are grouped by resource into submodules (``auth``, ``homes``, ``devices``,
|
|
5
|
+
``scenes``, ``jobs``, ``ota``, ``products``); each exposes a mixin that
|
|
6
|
+
``RoborockApiClient`` composes. Everything that was importable from the former
|
|
7
|
+
``roborock.web_api`` module is re-exported here for backwards compatibility.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from ._base import BASE_URLS, IotLoginInfo, RoborockApiBase
|
|
11
|
+
from ._request import PreparedRequest
|
|
12
|
+
from .auth import DEFAULT_AGREEMENT_MAJOR_VERSION, DEFAULT_AGREEMENT_MINOR_VERSION, AuthApi
|
|
13
|
+
from .client import RoborockApiClient, UserWebApiClient
|
|
14
|
+
from .devices import DevicesApi
|
|
15
|
+
from .homes import HomesApi
|
|
16
|
+
from .jobs import JobsApi
|
|
17
|
+
from .ota import OtaApi
|
|
18
|
+
from .products import ProductsApi
|
|
19
|
+
from .scenes import ScenesApi
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"BASE_URLS",
|
|
23
|
+
"DEFAULT_AGREEMENT_MAJOR_VERSION",
|
|
24
|
+
"DEFAULT_AGREEMENT_MINOR_VERSION",
|
|
25
|
+
"AuthApi",
|
|
26
|
+
"DevicesApi",
|
|
27
|
+
"HomesApi",
|
|
28
|
+
"IotLoginInfo",
|
|
29
|
+
"JobsApi",
|
|
30
|
+
"OtaApi",
|
|
31
|
+
"PreparedRequest",
|
|
32
|
+
"ProductsApi",
|
|
33
|
+
"RoborockApiBase",
|
|
34
|
+
"RoborockApiClient",
|
|
35
|
+
"ScenesApi",
|
|
36
|
+
"UserWebApiClient",
|
|
37
|
+
]
|