python-roborock 6.2.0__tar.gz → 6.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_roborock-6.2.0 → python_roborock-6.2.1}/PKG-INFO +1 -1
- {python_roborock-6.2.0 → python_roborock-6.2.1}/pyproject.toml +1 -1
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/map.py +19 -11
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/common.py +6 -2
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/b01_q10_map_parser.py +29 -9
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/b01_q10_overlays.py +6 -5
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/b01_q10_render.py +126 -77
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/map_parser.py +48 -17
- {python_roborock-6.2.0 → python_roborock-6.2.1}/.gitignore +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/LICENSE +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/README.md +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/callbacks.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/cli.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/const.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/code_mappings.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/containers.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/mower/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/mower/mower_containers.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/device_features.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/README.md +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/cache.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/device.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/device_manager.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/file_cache.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/rpc/a01_channel.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/rpc/b01_q10_channel.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/rpc/b01_q7_channel.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/button_light.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/common.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/consumable.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/network_info.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/remote.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/status.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/volume.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q7/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q7/map.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q7/map_content.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/obstacle_photos.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/diagnostics.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/exceptions.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/b01_grid_layers.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/b01_map_parser.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/proto/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/proto/b01_scmap.proto +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/proto/b01_scmap_pb2.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/map/room_colors.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/mqtt/session.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/protocol.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/protocols/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/protocols/b01_q7_protocol.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/py.typed +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/roborock_message.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/roborock_typing.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/testing/__init__.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/testing/b01_q10_simulator.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/testing/channel.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/testing/cloud.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/testing/simulator.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/testing/v1_simulator.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/util.py +0 -0
- {python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 6.2.
|
|
3
|
+
Version: 6.2.1
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
Project-URL: Repository, https://github.com/python-roborock/python-roborock
|
|
6
6
|
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "6.2.
|
|
3
|
+
version = "6.2.1"
|
|
4
4
|
description = "A package to control Roborock vacuums."
|
|
5
5
|
authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
|
|
6
6
|
requires-python = ">=3.11, <4"
|
|
@@ -4,12 +4,12 @@ Map-related state arrives on three independent streams:
|
|
|
4
4
|
|
|
5
5
|
* map packets are decoded from map-protocol responses;
|
|
6
6
|
* trace packets are decoded from trace-protocol responses;
|
|
7
|
-
* restricted zones
|
|
7
|
+
* restricted zones, virtual walls and dock state arrive as ordinary DPS values.
|
|
8
8
|
|
|
9
|
-
``MapDpsTrait`` owns the low-level DPS read model.
|
|
10
|
-
|
|
11
|
-
functions in :mod:`roborock.map.b01_q10_render`. The high-level
|
|
12
|
-
the latest value from each source and one replace-whole
|
|
9
|
+
``MapDpsTrait`` owns the low-level map-specific DPS read model.
|
|
10
|
+
``MapContentTrait`` combines that state with the latest map/trace packets
|
|
11
|
+
through the pure functions in :mod:`roborock.map.b01_q10_render`. The high-level
|
|
12
|
+
trait keeps only the latest value from each source and one replace-whole image;
|
|
13
13
|
calibration, path placement and overlay placement remain inside the renderer.
|
|
14
14
|
"""
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ from dataclasses import dataclass, field
|
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
20
|
from roborock.data import RoborockBase
|
|
21
|
-
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
|
|
21
|
+
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP, YXDeviceState
|
|
22
22
|
from roborock.devices.traits.common import DpsDataConverter, TraitUpdateListener
|
|
23
23
|
from roborock.exceptions import RoborockException
|
|
24
24
|
from roborock.map.b01_q10_map_parser import (
|
|
@@ -34,12 +34,14 @@ from roborock.map.b01_q10_render import Q10MapOverlays, render_q10_map
|
|
|
34
34
|
from .common import UpdatableTrait
|
|
35
35
|
|
|
36
36
|
_LOGGER = logging.getLogger(__name__)
|
|
37
|
+
_DOCKED_STATES = {YXDeviceState.CHARGING, YXDeviceState.EMPTYING_THE_BIN}
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
@dataclass
|
|
40
41
|
class MapDps(RoborockBase):
|
|
41
42
|
"""Low-level map values delivered in the Q10 DPS stream."""
|
|
42
43
|
|
|
44
|
+
status: YXDeviceState | None = field(default=None, metadata={"dps": B01_Q10_DP.STATUS})
|
|
43
45
|
restricted_zone_up: str | None = field(default=None, metadata={"dps": B01_Q10_DP.RESTRICTED_ZONE_UP})
|
|
44
46
|
virtual_wall_up: str | None = field(default=None, metadata={"dps": B01_Q10_DP.VIRTUAL_WALL_UP})
|
|
45
47
|
|
|
@@ -59,8 +61,13 @@ class MapDpsTrait(MapDps, UpdatableTrait):
|
|
|
59
61
|
"""Overlays decoded once from the latest relevant DPS update."""
|
|
60
62
|
return self._overlays
|
|
61
63
|
|
|
64
|
+
@property
|
|
65
|
+
def robot_at_dock(self) -> bool:
|
|
66
|
+
"""Whether status places the idle robot at the saved dock."""
|
|
67
|
+
return self.status in _DOCKED_STATES
|
|
68
|
+
|
|
62
69
|
def update_from_dps(self, decoded_dps: dict[B01_Q10_DP, Any]) -> None:
|
|
63
|
-
"""
|
|
70
|
+
"""Update one coherent snapshot of the DPS inputs used by the map."""
|
|
64
71
|
if not self._CONVERTER.update_from_dps(self, decoded_dps):
|
|
65
72
|
return
|
|
66
73
|
self._overlays = Q10MapOverlays(
|
|
@@ -73,8 +80,8 @@ class MapDpsTrait(MapDps, UpdatableTrait):
|
|
|
73
80
|
class MapContentTrait(TraitUpdateListener):
|
|
74
81
|
"""High-level composed Q10 map view.
|
|
75
82
|
|
|
76
|
-
The latest map and trace packets are combined with the injected
|
|
77
|
-
|
|
83
|
+
The latest map and trace packets are combined with the injected map DPS
|
|
84
|
+
whenever any source changes.
|
|
78
85
|
"""
|
|
79
86
|
|
|
80
87
|
def __init__(
|
|
@@ -129,7 +136,7 @@ class MapContentTrait(TraitUpdateListener):
|
|
|
129
136
|
self._notify_update()
|
|
130
137
|
|
|
131
138
|
def _map_dps_updated(self) -> None:
|
|
132
|
-
"""Render after the low-level DPS source changes."""
|
|
139
|
+
"""Render after the low-level map DPS source changes."""
|
|
133
140
|
if self._map_packet is None:
|
|
134
141
|
return
|
|
135
142
|
self._render()
|
|
@@ -142,9 +149,10 @@ class MapContentTrait(TraitUpdateListener):
|
|
|
142
149
|
try:
|
|
143
150
|
self._image_content = render_q10_map(
|
|
144
151
|
self._map_packet,
|
|
145
|
-
self._trace_packet,
|
|
152
|
+
self._trace_packet if not self._map_dps.robot_at_dock else None,
|
|
146
153
|
self._map_dps.overlays,
|
|
147
154
|
config=self._config,
|
|
155
|
+
robot_at_dock=self._map_dps.robot_at_dock,
|
|
148
156
|
)
|
|
149
157
|
except RoborockException as ex:
|
|
150
158
|
_LOGGER.debug("Failed to render Q10 map packet: %s", ex)
|
|
@@ -105,10 +105,14 @@ class DpsDataConverter(Generic[TDps]):
|
|
|
105
105
|
decoded_dps: The decoded DPS data to convert.
|
|
106
106
|
|
|
107
107
|
Returns:
|
|
108
|
-
True if any values
|
|
108
|
+
True if any values changed, False otherwise.
|
|
109
109
|
"""
|
|
110
110
|
conversions = RoborockBase.convert_dict(self._dps_type_map, decoded_dps)
|
|
111
|
+
changed = False
|
|
111
112
|
for dps_id, value in conversions.items():
|
|
112
113
|
field_name = self._dps_field_map[dps_id]
|
|
114
|
+
if getattr(target, field_name) == value:
|
|
115
|
+
continue
|
|
113
116
|
setattr(target, field_name, value)
|
|
114
|
-
|
|
117
|
+
changed = True
|
|
118
|
+
return changed
|
|
@@ -27,7 +27,7 @@ from dataclasses import dataclass, field, replace
|
|
|
27
27
|
from PIL import Image
|
|
28
28
|
from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
|
|
29
29
|
from vacuum_map_parser_base.config.image_config import ImageConfig
|
|
30
|
-
from vacuum_map_parser_base.map_data import ImageData, MapData
|
|
30
|
+
from vacuum_map_parser_base.map_data import ImageData, MapData, Point
|
|
31
31
|
|
|
32
32
|
from roborock.exceptions import RoborockException
|
|
33
33
|
|
|
@@ -94,15 +94,18 @@ _MAX_ERASE_ZONE_VERTICES = 16
|
|
|
94
94
|
# 50 mm/px, so dividing by 10 yields the origin in grid pixels -- the (ox, oy)
|
|
95
95
|
# that solve_calibration otherwise recovers by sliding the path.
|
|
96
96
|
# - 15-16 resolution (u16be): reads 5 (= 0.05 m/px = 50 mm/px) universally.
|
|
97
|
-
# - 17-18 charger x, 19-20 charger y (s16be,
|
|
97
|
+
# - 17-18 charger x, 19-20 charger y (s16be, absolute map decipixels),
|
|
98
|
+
# 21-22 charger phi. The official app's device-point transform confirms these
|
|
99
|
+
# coordinates are already in the map-array frame and must not receive x_min /
|
|
100
|
+
# y_min a second time.
|
|
98
101
|
_ORIGIN_X_OFFSET = 11
|
|
99
102
|
_ORIGIN_Y_OFFSET = 13
|
|
100
103
|
_HEADER_RESOLUTION_OFFSET = 15
|
|
101
104
|
_CHARGER_X_OFFSET = 17
|
|
102
105
|
_CHARGER_Y_OFFSET = 19
|
|
103
106
|
_CHARGER_PHI_OFFSET = 21
|
|
104
|
-
# The header origin
|
|
105
|
-
#
|
|
107
|
+
# The header origin and charger fields both divide by 10 to reach grid pixels,
|
|
108
|
+
# although they use different coordinate frames as documented above.
|
|
106
109
|
_HEADER_UNITS_PER_PIXEL = 10
|
|
107
110
|
|
|
108
111
|
# Grid cell values >= this are walls / borders rather than room segments.
|
|
@@ -130,7 +133,8 @@ class Q10EraseZone:
|
|
|
130
133
|
|
|
131
134
|
These are the app's *Erase* tool rectangles -- regions the user marked to be
|
|
132
135
|
removed from the map (e.g. phantom floor the lidar mapped through windows).
|
|
133
|
-
Coordinates are world units
|
|
136
|
+
Coordinates are 5 mm world units, the same frame as restriction zones.
|
|
137
|
+
Trace points use a separate 2.5 mm scale.
|
|
134
138
|
|
|
135
139
|
Confirmed by a controlled diff: removing the two erase zones on a live device
|
|
136
140
|
dropped this section's count from 2 to 0 while the grid and the trailing
|
|
@@ -149,9 +153,11 @@ class Q10HeaderCalibration:
|
|
|
149
153
|
straight from the map packet -- no cleaning path / fit required, so it works
|
|
150
154
|
docked or pre-clean. See :meth:`origin_pixels`.
|
|
151
155
|
|
|
152
|
-
``origin_x`` / ``origin_y``
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
``origin_x`` / ``origin_y`` are in 5 mm units and define the world-coordinate
|
|
157
|
+
origin. The charger coordinates are absolute decipixels in the map-array
|
|
158
|
+
frame, so they are divided by 10 without applying that origin again.
|
|
159
|
+
``resolution`` is the raw header field (5 == 50 mm/px). ``charger_phi`` is
|
|
160
|
+
the raw dock heading field. Reported and verified by @andrewlyeats (ss07).
|
|
155
161
|
"""
|
|
156
162
|
|
|
157
163
|
origin_x: int
|
|
@@ -177,6 +183,15 @@ class Q10HeaderCalibration:
|
|
|
177
183
|
return None
|
|
178
184
|
return (self.origin_x / _HEADER_UNITS_PER_PIXEL, self.origin_y / _HEADER_UNITS_PER_PIXEL)
|
|
179
185
|
|
|
186
|
+
def charger_pixels(self) -> tuple[float, float] | None:
|
|
187
|
+
"""Return the saved dock in absolute map-array pixel coordinates."""
|
|
188
|
+
if (self.charger_x == 0 and self.charger_y == 0) or self.charger_x == -1 or self.charger_y == -1:
|
|
189
|
+
return None
|
|
190
|
+
return (
|
|
191
|
+
self.charger_x / _HEADER_UNITS_PER_PIXEL,
|
|
192
|
+
self.charger_y / _HEADER_UNITS_PER_PIXEL,
|
|
193
|
+
)
|
|
194
|
+
|
|
180
195
|
|
|
181
196
|
@dataclass
|
|
182
197
|
class Q10MapPacket:
|
|
@@ -640,7 +655,12 @@ class B01Q10MapParser:
|
|
|
640
655
|
width=packet.width,
|
|
641
656
|
image_config=ImageConfig(scale=self._config.map_scale),
|
|
642
657
|
data=image,
|
|
643
|
-
|
|
658
|
+
# ImageDimensions uses V1's bottom-up map convention before
|
|
659
|
+
# projecting into the top-down PNG. Q10 points are already
|
|
660
|
+
# top-down grid pixels, so this adapter cancels that standard flip
|
|
661
|
+
# and lets Q10 use the shared V1 ImageGenerator without moving
|
|
662
|
+
# overlays vertically.
|
|
663
|
+
img_transformation=lambda p: Point(p.x, packet.height - p.y - 1, p.a),
|
|
644
664
|
)
|
|
645
665
|
room_names = {room.id: room.name for room in packet.rooms}
|
|
646
666
|
if room_names:
|
|
@@ -19,11 +19,12 @@ the next, a coordinate, as a record count). The coordinate order matches the
|
|
|
19
19
|
zones (first wire word = x), confirmed against the app. Provenance and the
|
|
20
20
|
byte-level breakdown are in PR #850's review thread.
|
|
21
21
|
|
|
22
|
-
Coordinates are in
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
threshold, anything else -- incl. 0 -- a no-go zone); it
|
|
26
|
-
so callers can route polygons to the right ``MapData``
|
|
22
|
+
Coordinates are in 5 mm device world units, the same space as erase polygons.
|
|
23
|
+
Cleaning trace points use a separate 2.5 mm scale, so callers must not project
|
|
24
|
+
both through the same resolution. ``type`` distinguishes the restriction kind
|
|
25
|
+
(2 = no-mop, 3 = door threshold, anything else -- incl. 0 -- a no-go zone); it
|
|
26
|
+
is preserved verbatim so callers can route polygons to the right ``MapData``
|
|
27
|
+
layer.
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
30
|
import base64
|
|
@@ -18,7 +18,8 @@ import math
|
|
|
18
18
|
from collections.abc import Sequence
|
|
19
19
|
from dataclasses import dataclass
|
|
20
20
|
|
|
21
|
-
from
|
|
21
|
+
from vacuum_map_parser_base.config.drawable import Drawable
|
|
22
|
+
from vacuum_map_parser_base.config.size import Size, Sizes
|
|
22
23
|
from vacuum_map_parser_base.map_data import Area, MapData, Path, Point, Wall
|
|
23
24
|
|
|
24
25
|
from roborock.exceptions import RoborockException
|
|
@@ -38,6 +39,7 @@ from .b01_q10_map_parser import (
|
|
|
38
39
|
erased_packet,
|
|
39
40
|
)
|
|
40
41
|
from .b01_q10_overlays import ZONE_TYPE_NO_GO, ZONE_TYPE_NO_MOP, Q10Zone
|
|
42
|
+
from .map_parser import DEFAULT_DRAWABLES, MapParserConfig, _create_image_generator
|
|
41
43
|
|
|
42
44
|
# Path-units-per-pixel candidates for calibration. A dense ss07 path lands a
|
|
43
45
|
# best fit of 20.0 around the header origin -- ground-truthed June 2026 on the
|
|
@@ -49,6 +51,10 @@ from .b01_q10_overlays import ZONE_TYPE_NO_GO, ZONE_TYPE_NO_MOP, Q10Zone
|
|
|
49
51
|
# reach 20 (it railed at the bound), biasing the fit. A dense cleaning path
|
|
50
52
|
# selects the best fit within this bracket.
|
|
51
53
|
_Q10_RESOLUTIONS = [step * 0.5 for step in range(24, 53)] # 12.0 .. 26.0
|
|
54
|
+
# The header resolution is expressed in centimetres per grid pixel, while
|
|
55
|
+
# erase/restriction vectors use 5 mm units: one header unit therefore equals
|
|
56
|
+
# two vector units. Trace points use a separate 2.5 mm coordinate scale.
|
|
57
|
+
_Q10_VECTOR_UNITS_PER_HEADER_RESOLUTION_UNIT = 2.0
|
|
52
58
|
# A path needs enough shape to constrain a full (origin + resolution) fit; a few
|
|
53
59
|
# points cannot.
|
|
54
60
|
_MIN_CALIBRATION_POINTS = 20
|
|
@@ -57,6 +63,16 @@ _MIN_CALIBRATION_POINTS = 20
|
|
|
57
63
|
# one). See :func:`solve_calibration_with_origin`.
|
|
58
64
|
_MIN_HEADER_CALIBRATION_POINTS = 4
|
|
59
65
|
|
|
66
|
+
_Q10_DRAWABLE_TYPES = {
|
|
67
|
+
Drawable.CHARGER,
|
|
68
|
+
Drawable.NO_GO_AREAS,
|
|
69
|
+
Drawable.NO_MOPPING_AREAS,
|
|
70
|
+
Drawable.PATH,
|
|
71
|
+
Drawable.VACUUM_POSITION,
|
|
72
|
+
Drawable.VIRTUAL_WALLS,
|
|
73
|
+
}
|
|
74
|
+
_Q10_DRAWABLES = [drawable for drawable in DEFAULT_DRAWABLES if drawable in _Q10_DRAWABLE_TYPES]
|
|
75
|
+
|
|
60
76
|
|
|
61
77
|
@dataclass(frozen=True)
|
|
62
78
|
class Q10MapOverlays:
|
|
@@ -72,21 +88,22 @@ def render_q10_map(
|
|
|
72
88
|
overlays: Q10MapOverlays,
|
|
73
89
|
*,
|
|
74
90
|
config: B01Q10MapParserConfig,
|
|
91
|
+
robot_at_dock: bool = False,
|
|
75
92
|
) -> bytes:
|
|
76
93
|
"""Compose the latest map, trace and DPS inputs into one PNG image.
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
:class:`RoborockException` if map rendering fails.
|
|
95
|
+
Separate transforms are derived for 2.5 mm trace points and 5 mm
|
|
96
|
+
erase/restriction vectors. Erase zones are blanked out of the raster, while
|
|
97
|
+
available trace and DPS overlays are projected and drawn in pixel space.
|
|
98
|
+
Raises :class:`RoborockException` if map rendering fails.
|
|
83
99
|
"""
|
|
84
100
|
parser = B01Q10MapParser(config)
|
|
85
|
-
|
|
101
|
+
trace_calibration = solve_q10_calibration(packet, trace)
|
|
102
|
+
vector_calibration = _vector_calibration(packet, trace_calibration)
|
|
86
103
|
|
|
87
104
|
render_packet = packet
|
|
88
|
-
if
|
|
89
|
-
cells = _erased_cells(packet.layers, packet.erase_zones,
|
|
105
|
+
if vector_calibration is not None:
|
|
106
|
+
cells = _erased_cells(packet.layers, packet.erase_zones, vector_calibration)
|
|
90
107
|
if cells:
|
|
91
108
|
# Blank the erase-zone cells before parsing the raster so phantom
|
|
92
109
|
# areas disappear (as the app shows).
|
|
@@ -97,10 +114,19 @@ def render_q10_map(
|
|
|
97
114
|
raise RoborockException("Failed to render Q10 map image")
|
|
98
115
|
map_data = parsed.map_data
|
|
99
116
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
has_drawables = False
|
|
118
|
+
if trace_calibration is not None and trace is not None:
|
|
119
|
+
charger_heading = packet.header_calibration.charger_phi if packet.header_calibration is not None else None
|
|
120
|
+
_place_trace(map_data, trace_calibration, trace, charger_heading=charger_heading)
|
|
121
|
+
has_drawables = True
|
|
122
|
+
has_drawables = _place_charger_from_header(map_data, packet) or has_drawables
|
|
123
|
+
if robot_at_dock:
|
|
124
|
+
has_drawables = _place_docked_robot(map_data) or has_drawables
|
|
125
|
+
if vector_calibration is not None:
|
|
126
|
+
_place_overlays(map_data, vector_calibration, overlays)
|
|
127
|
+
has_drawables = has_drawables or bool(map_data.no_go_areas or map_data.no_mopping_areas or map_data.walls)
|
|
128
|
+
if has_drawables:
|
|
129
|
+
return _draw_map_content(map_data, config=config)
|
|
104
130
|
|
|
105
131
|
return parsed.image_content
|
|
106
132
|
|
|
@@ -137,6 +163,41 @@ def _calibration_from_header(
|
|
|
137
163
|
return solve_calibration_with_origin(packet.layers, points, origin, resolutions=_Q10_RESOLUTIONS)
|
|
138
164
|
|
|
139
165
|
|
|
166
|
+
def _calibration_from_header_metadata(
|
|
167
|
+
packet: Q10MapPacket,
|
|
168
|
+
*,
|
|
169
|
+
y_sign: int = 1,
|
|
170
|
+
) -> GridCalibration | None:
|
|
171
|
+
"""Build the vector transform directly from a usable map header."""
|
|
172
|
+
header = packet.header_calibration
|
|
173
|
+
if header is None or header.resolution <= 0 or (origin := header.origin_pixels()) is None:
|
|
174
|
+
return None
|
|
175
|
+
return GridCalibration(
|
|
176
|
+
resolution=header.resolution * _Q10_VECTOR_UNITS_PER_HEADER_RESOLUTION_UNIT,
|
|
177
|
+
origin_x=origin[0],
|
|
178
|
+
origin_y=origin[1],
|
|
179
|
+
y_sign=y_sign,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _vector_calibration(
|
|
184
|
+
packet: Q10MapPacket,
|
|
185
|
+
trace_calibration: GridCalibration | None,
|
|
186
|
+
) -> GridCalibration | None:
|
|
187
|
+
"""Derive the 5 mm erase/restriction-vector transform."""
|
|
188
|
+
y_sign = trace_calibration.y_sign if trace_calibration is not None else 1
|
|
189
|
+
if calibration := _calibration_from_header_metadata(packet, y_sign=y_sign):
|
|
190
|
+
return calibration
|
|
191
|
+
if trace_calibration is None:
|
|
192
|
+
return None
|
|
193
|
+
return GridCalibration(
|
|
194
|
+
resolution=trace_calibration.resolution / 2,
|
|
195
|
+
origin_x=trace_calibration.origin_x,
|
|
196
|
+
origin_y=trace_calibration.origin_y,
|
|
197
|
+
y_sign=trace_calibration.y_sign,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
140
201
|
def _calibration_from_fit(layers: GridLayers, points: list[tuple[float, float]]) -> GridCalibration | None:
|
|
141
202
|
"""Full origin + resolution fit; needs a reasonably dense path."""
|
|
142
203
|
if len(points) < _MIN_CALIBRATION_POINTS:
|
|
@@ -170,6 +231,8 @@ def _place_trace(
|
|
|
170
231
|
map_data: MapData,
|
|
171
232
|
calibration: GridCalibration,
|
|
172
233
|
trace: Q10TracePacket,
|
|
234
|
+
*,
|
|
235
|
+
charger_heading: int | None = None,
|
|
173
236
|
) -> None:
|
|
174
237
|
"""Project trace path, position, heading and charger into pixel space.
|
|
175
238
|
|
|
@@ -181,11 +244,48 @@ def _place_trace(
|
|
|
181
244
|
robot_position = trace.robot_position
|
|
182
245
|
if robot_position is not None:
|
|
183
246
|
px, py = calibration.world_to_pixel(robot_position.x, robot_position.y)
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
map_data.vacuum_position = Point(px, py,
|
|
247
|
+
# The shared V1 marker expects a map-space heading (+Y is up), not the
|
|
248
|
+
# top-down PNG angle previously used by Q10's custom marker.
|
|
249
|
+
map_data.vacuum_position = Point(px, py, calibration.y_sign * trace.heading)
|
|
187
250
|
if pixels:
|
|
188
|
-
map_data.charger =
|
|
251
|
+
map_data.charger = Point(
|
|
252
|
+
pixels[0].x,
|
|
253
|
+
pixels[0].y,
|
|
254
|
+
calibration.y_sign * charger_heading if charger_heading is not None else None,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _place_charger_from_header(
|
|
259
|
+
map_data: MapData,
|
|
260
|
+
packet: Q10MapPacket,
|
|
261
|
+
) -> bool:
|
|
262
|
+
"""Place the saved dock using its absolute header pixel coordinates."""
|
|
263
|
+
header = packet.header_calibration
|
|
264
|
+
if header is None or (position := header.charger_pixels()) is None:
|
|
265
|
+
return False
|
|
266
|
+
map_data.charger = Point(*position, header.charger_phi)
|
|
267
|
+
return True
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _place_docked_robot(map_data: MapData) -> bool:
|
|
271
|
+
"""Place a charging robot immediately in front of the saved dock.
|
|
272
|
+
|
|
273
|
+
A zero-point idle trace has no robot coordinates. The dock heading does,
|
|
274
|
+
however, identify its outward-facing side. Offset the robot by the shared
|
|
275
|
+
unscaled V1 charger radius so the two standard glyphs meet without one
|
|
276
|
+
covering the other, and preserve the saved dock heading.
|
|
277
|
+
"""
|
|
278
|
+
charger = map_data.charger
|
|
279
|
+
if charger is None or charger.a is None:
|
|
280
|
+
return False
|
|
281
|
+
angle = math.radians(charger.a)
|
|
282
|
+
offset = Sizes.SIZES[Size.CHARGER_RADIUS]
|
|
283
|
+
map_data.vacuum_position = Point(
|
|
284
|
+
charger.x + offset * math.cos(angle),
|
|
285
|
+
charger.y - offset * math.sin(angle),
|
|
286
|
+
charger.a,
|
|
287
|
+
)
|
|
288
|
+
return True
|
|
189
289
|
|
|
190
290
|
|
|
191
291
|
def _place_overlays(
|
|
@@ -217,69 +317,18 @@ def _place_overlays(
|
|
|
217
317
|
|
|
218
318
|
|
|
219
319
|
def _draw_map_content(
|
|
220
|
-
image_content: bytes,
|
|
221
320
|
map_data: MapData,
|
|
222
321
|
*,
|
|
223
322
|
config: B01Q10MapParserConfig,
|
|
224
|
-
line_color: tuple[int, int, int, int] = (235, 64, 52, 255),
|
|
225
|
-
position_color: tuple[int, int, int, int] = (255, 211, 0, 255),
|
|
226
323
|
) -> bytes:
|
|
227
|
-
"""Draw
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# Erase zones are applied to the raster itself (cells blanked), so they are
|
|
237
|
-
# not drawn here -- the base image already reflects them.
|
|
238
|
-
|
|
239
|
-
# No-go (blue) and no-mop (magenta) zones beneath the path.
|
|
240
|
-
for areas, fill, outline in (
|
|
241
|
-
(map_data.no_go_areas or [], (0, 120, 255, 70), (0, 80, 200, 255)),
|
|
242
|
-
(map_data.no_mopping_areas or [], (255, 0, 200, 70), (200, 0, 160, 255)),
|
|
243
|
-
):
|
|
244
|
-
for area in areas:
|
|
245
|
-
polygon = [
|
|
246
|
-
(area.x0 * scale, area.y0 * scale),
|
|
247
|
-
(area.x1 * scale, area.y1 * scale),
|
|
248
|
-
(area.x2 * scale, area.y2 * scale),
|
|
249
|
-
(area.x3 * scale, area.y3 * scale),
|
|
250
|
-
]
|
|
251
|
-
draw.polygon(polygon, fill=fill, outline=outline)
|
|
252
|
-
|
|
253
|
-
# Virtual walls (line segments, not polygons) drawn over the zones.
|
|
254
|
-
for wall in map_data.walls or []:
|
|
255
|
-
draw.line(
|
|
256
|
-
[(wall.x0 * scale, wall.y0 * scale), (wall.x1 * scale, wall.y1 * scale)],
|
|
257
|
-
fill=(255, 64, 64, 255),
|
|
258
|
-
width=max(2, scale),
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
for path in map_data.path.path if map_data.path else []:
|
|
262
|
-
if len(path) >= 2:
|
|
263
|
-
draw.line([to_image(point) for point in path], fill=line_color, width=max(1, scale // 2))
|
|
264
|
-
if map_data.charger is not None:
|
|
265
|
-
dx, dy = to_image(map_data.charger)
|
|
266
|
-
draw.ellipse([dx - scale, dy - scale, dx + scale, dy + scale], outline=(40, 200, 40, 255), width=2)
|
|
267
|
-
robot_position = map_data.vacuum_position
|
|
268
|
-
if robot_position is not None:
|
|
269
|
-
cx, cy = to_image(robot_position)
|
|
270
|
-
radius = scale
|
|
271
|
-
draw.ellipse([cx - radius, cy - radius, cx + radius, cy + radius], fill=position_color)
|
|
272
|
-
robot_heading = robot_position.a
|
|
273
|
-
if robot_heading is not None:
|
|
274
|
-
# Heading was projected into image coordinates alongside the robot
|
|
275
|
-
# position, so no calibration state is needed during drawing.
|
|
276
|
-
angle = math.radians(robot_heading)
|
|
277
|
-
tick = 4 * radius
|
|
278
|
-
draw.line(
|
|
279
|
-
[cx, cy, cx + math.cos(angle) * tick, cy + math.sin(angle) * tick],
|
|
280
|
-
fill=position_color,
|
|
281
|
-
width=max(1, scale // 2),
|
|
282
|
-
)
|
|
324
|
+
"""Draw Q10 content with the shared V1 image generator."""
|
|
325
|
+
if map_data.image is None:
|
|
326
|
+
raise RoborockException("Failed to render Q10 map image")
|
|
327
|
+
generator = _create_image_generator(
|
|
328
|
+
MapParserConfig(map_scale=config.map_scale),
|
|
329
|
+
drawables=_Q10_DRAWABLES,
|
|
330
|
+
)
|
|
331
|
+
generator.draw_map(map_data)
|
|
283
332
|
buffer = io.BytesIO()
|
|
284
|
-
|
|
333
|
+
map_data.image.data.save(buffer, format="PNG")
|
|
285
334
|
return buffer.getvalue()
|
|
@@ -5,10 +5,11 @@ import logging
|
|
|
5
5
|
import threading
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
|
|
8
|
-
from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
|
|
8
|
+
from vacuum_map_parser_base.config.color import Color, ColorsPalette, SupportedColor
|
|
9
9
|
from vacuum_map_parser_base.config.drawable import Drawable
|
|
10
10
|
from vacuum_map_parser_base.config.image_config import ImageConfig
|
|
11
11
|
from vacuum_map_parser_base.config.size import Size, Sizes
|
|
12
|
+
from vacuum_map_parser_base.image_generator import ImageGenerator
|
|
12
13
|
from vacuum_map_parser_base.map_data import MapData
|
|
13
14
|
from vacuum_map_parser_roborock.image_parser import RoborockImageParser
|
|
14
15
|
from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser
|
|
@@ -150,8 +151,48 @@ class _AdjacencyAwareRoborockImageParser(RoborockImageParser):
|
|
|
150
151
|
|
|
151
152
|
def _create_map_data_parser(config: MapParserConfig) -> RoborockMapDataParser:
|
|
152
153
|
"""Create a RoborockMapDataParser based on the config entry."""
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
palette, sizes, image_config = _create_rendering_components(config)
|
|
155
|
+
parser = RoborockMapDataParser(
|
|
156
|
+
palette,
|
|
157
|
+
sizes,
|
|
158
|
+
config.drawables,
|
|
159
|
+
image_config,
|
|
160
|
+
[],
|
|
161
|
+
)
|
|
162
|
+
parser._image_parser = _AdjacencyAwareRoborockImageParser(
|
|
163
|
+
palette,
|
|
164
|
+
image_config,
|
|
165
|
+
recolor_rooms=config.show_rooms,
|
|
166
|
+
)
|
|
167
|
+
return parser
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _create_image_generator(
|
|
171
|
+
config: MapParserConfig,
|
|
172
|
+
*,
|
|
173
|
+
drawables: list[Drawable] | None = None,
|
|
174
|
+
) -> ImageGenerator:
|
|
175
|
+
"""Create the image generator used by V1-compatible package renderers.
|
|
176
|
+
|
|
177
|
+
Other protocols should use this factory for shared drawables instead of
|
|
178
|
+
copying V1 colors, sizes, glyphs, or transparency behavior.
|
|
179
|
+
"""
|
|
180
|
+
palette, sizes, image_config = _create_rendering_components(config)
|
|
181
|
+
return ImageGenerator(
|
|
182
|
+
palette,
|
|
183
|
+
sizes,
|
|
184
|
+
config.drawables if drawables is None else drawables,
|
|
185
|
+
image_config,
|
|
186
|
+
[],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _create_rendering_components(
|
|
191
|
+
config: MapParserConfig,
|
|
192
|
+
) -> tuple[ColorsPalette, Sizes, ImageConfig]:
|
|
193
|
+
"""Build the shared V1 palette, scaled sizes, and image configuration."""
|
|
194
|
+
color_dicts: dict[SupportedColor, Color] = {}
|
|
195
|
+
room_colors: dict[str, Color] = {}
|
|
155
196
|
|
|
156
197
|
if not config.show_background:
|
|
157
198
|
color_dicts[SupportedColor.MAP_OUTSIDE] = (0, 0, 0, 0)
|
|
@@ -162,20 +203,10 @@ def _create_map_data_parser(config: MapParserConfig) -> RoborockMapDataParser:
|
|
|
162
203
|
color_dicts[SupportedColor.MAP_WALL_V2] = (0, 0, 0, 0)
|
|
163
204
|
|
|
164
205
|
if not config.show_rooms:
|
|
165
|
-
room_colors = {str(
|
|
206
|
+
room_colors = {str(room_id): (0, 0, 0, 0) for room_id in range(1, 32)}
|
|
166
207
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
parser = RoborockMapDataParser(
|
|
170
|
-
palette,
|
|
208
|
+
return (
|
|
209
|
+
ColorsPalette(color_dicts, room_colors),
|
|
171
210
|
Sizes({k: v * config.map_scale for k, v in Sizes.SIZES.items() if k != Size.MOP_PATH_WIDTH}),
|
|
172
|
-
config.
|
|
173
|
-
image_config,
|
|
174
|
-
[],
|
|
175
|
-
)
|
|
176
|
-
parser._image_parser = _AdjacencyAwareRoborockImageParser(
|
|
177
|
-
palette,
|
|
178
|
-
image_config,
|
|
179
|
-
recolor_rooms=config.show_rooms,
|
|
211
|
+
ImageConfig(scale=config.map_scale),
|
|
180
212
|
)
|
|
181
|
-
return parser
|
|
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-6.2.0 → python_roborock-6.2.1}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/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
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/button_light.py
RENAMED
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/child_lock.py
RENAMED
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/clean_history.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/consumable.py
RENAMED
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/dust_collection.py
RENAMED
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q10/network_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q7/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/b01/q7/map_content.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
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/device_features.py
RENAMED
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/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
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/obstacle_photos.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/roborock/devices/traits/v1/smart_wash_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-6.2.0 → python_roborock-6.2.1}/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
|
|
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
|