python-roborock 7.4.3__py3-none-any.whl → 7.5.0__py3-none-any.whl
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.4.3.dist-info → python_roborock-7.5.0.dist-info}/METADATA +1 -1
- {python_roborock-7.4.3.dist-info → python_roborock-7.5.0.dist-info}/RECORD +8 -8
- roborock/map/b01_map_parser.py +266 -16
- roborock/map/proto/b01_scmap.proto +82 -1
- roborock/map/proto/b01_scmap_pb2.py +29 -5
- {python_roborock-7.4.3.dist-info → python_roborock-7.5.0.dist-info}/WHEEL +0 -0
- {python_roborock-7.4.3.dist-info → python_roborock-7.5.0.dist-info}/entry_points.txt +0 -0
- {python_roborock-7.4.3.dist-info → python_roborock-7.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.5.0
|
|
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/
|
|
@@ -100,15 +100,15 @@ roborock/devices/transport/local_channel.py,sha256=xnCzhIMa6V01Okaoa5KlUFw7k0col
|
|
|
100
100
|
roborock/devices/transport/mqtt_channel.py,sha256=dpL1mZIoI4Glk3hfUO9JweWFHG1tZWLrWbt9f4RbvhQ,4789
|
|
101
101
|
roborock/map/__init__.py,sha256=L3WHujuxUHCTTPaOElkJsTN_FkYhw01drfR-W4blgFc,146
|
|
102
102
|
roborock/map/b01_grid_layers.py,sha256=b87MsGXslqLR_5H_4z4aj1wQqYgeTuULWqt4_txOF-M,12303
|
|
103
|
-
roborock/map/b01_map_parser.py,sha256=
|
|
103
|
+
roborock/map/b01_map_parser.py,sha256=y4Qlgiwre3bn5_UpL6R-ytjg8kQqwkbvhIYFOtpAM3s,14814
|
|
104
104
|
roborock/map/b01_q10_map_parser.py,sha256=Pt-goedXV60hg-mwHOgOF0f4TQBQmQXe6otpWaa99ps,29996
|
|
105
105
|
roborock/map/b01_q10_overlays.py,sha256=JmriUbT4-oe6NiE94VttMplFc1HHQrAGNQIiZHj5QKk,6640
|
|
106
106
|
roborock/map/b01_q10_render.py,sha256=b7YUBr53WXxOJhXbfb5IVmgi82iGcC0lJ_j9Ef3kV7M,13502
|
|
107
107
|
roborock/map/map_parser.py,sha256=EcJcz9yld_wu7d5QrmxR2BTfJ801F6SaEgJkbIIap4w,5111
|
|
108
108
|
roborock/map/room_colors.py,sha256=5Q85oR_7xsUyzHYGOJPuzsZ6LQSCpJwfxfWql1v6T1E,2152
|
|
109
109
|
roborock/map/proto/__init__.py,sha256=XcH_s-E67_hcQNwqSkdtY7DgYE0mRBBJwctzsf8zng4,60
|
|
110
|
-
roborock/map/proto/b01_scmap.proto,sha256=
|
|
111
|
-
roborock/map/proto/b01_scmap_pb2.py,sha256=
|
|
110
|
+
roborock/map/proto/b01_scmap.proto,sha256=lQtOruroUfrDK9EJ99cnGTi7avEK3CQ1haLIopDrS_A,3725
|
|
111
|
+
roborock/map/proto/b01_scmap_pb2.py,sha256=HI7xxxpvV0cFl80B7T7RN74C3cP8YNqfukL_FW324ss,7493
|
|
112
112
|
roborock/mqtt/__init__.py,sha256=Lplt5aS3lCJW09KX34tf2r6NHU8penM-R07FI56Z5CE,415
|
|
113
113
|
roborock/mqtt/health_manager.py,sha256=i_3vbslHMebVd0VNBWalPaEnZi8gcRI3IcKPt2wlQF8,2208
|
|
114
114
|
roborock/mqtt/roborock_session.py,sha256=ir7HXs4gtoineiwlc6ixyAueBTCSPtNU8zPGgY9hGOE,21027
|
|
@@ -125,8 +125,8 @@ roborock/testing/channel.py,sha256=vjulT5MHBe8lAq6XITUqcxJVcCILWx7ZEcw5imTx0Tk,7
|
|
|
125
125
|
roborock/testing/cloud.py,sha256=L2L0KlZDYaMuoV19KUQUoq5EAUDeNQ1lJ8Ew5YaOgjo,11946
|
|
126
126
|
roborock/testing/simulator.py,sha256=g8ZUWVSjgAzuRRxWOCeOq-7deodyEItwUpqEw9RzCmQ,4347
|
|
127
127
|
roborock/testing/v1_simulator.py,sha256=_LX_vsBMcB2Uu2QDt8QnQymgtVtbnwD5Xa7tmhiPAnY,14439
|
|
128
|
-
python_roborock-7.
|
|
129
|
-
python_roborock-7.
|
|
130
|
-
python_roborock-7.
|
|
131
|
-
python_roborock-7.
|
|
132
|
-
python_roborock-7.
|
|
128
|
+
python_roborock-7.5.0.dist-info/METADATA,sha256=UJyDy8lpgf3x32vXVYE46C2rlW0PSY3cTtToPhv_Jyk,5278
|
|
129
|
+
python_roborock-7.5.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
130
|
+
python_roborock-7.5.0.dist-info/entry_points.txt,sha256=EvC1nMqi9ZXKgZnqlNXA33v_3nzgPNjnM0mzWlrehnY,47
|
|
131
|
+
python_roborock-7.5.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
132
|
+
python_roborock-7.5.0.dist-info/RECORD,,
|
roborock/map/b01_map_parser.py
CHANGED
|
@@ -5,20 +5,36 @@ The inner SCMap blob is parsed with protobuf messages generated from
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import io
|
|
8
|
+
import math
|
|
9
|
+
from collections import deque
|
|
8
10
|
from dataclasses import dataclass
|
|
9
11
|
|
|
10
12
|
from google.protobuf.message import DecodeError
|
|
11
13
|
from PIL import Image
|
|
14
|
+
from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
|
|
15
|
+
from vacuum_map_parser_base.config.drawable import Drawable
|
|
12
16
|
from vacuum_map_parser_base.config.image_config import ImageConfig
|
|
13
|
-
from vacuum_map_parser_base.map_data import ImageData, MapData
|
|
17
|
+
from vacuum_map_parser_base.map_data import Area, ImageData, MapData, Path, Point, Room
|
|
14
18
|
|
|
15
19
|
from roborock.exceptions import RoborockException
|
|
16
20
|
from roborock.map.proto.b01_scmap_pb2 import RobotMap # type: ignore[attr-defined]
|
|
17
21
|
|
|
18
|
-
from .map_parser import ParsedMapData
|
|
22
|
+
from .map_parser import MapParserConfig, ParsedMapData, _create_image_generator
|
|
23
|
+
from .room_colors import adjacency_aware_room_colors
|
|
19
24
|
|
|
20
25
|
_MAP_FILE_FORMAT = "PNG"
|
|
21
26
|
|
|
27
|
+
_FLOOR = 127
|
|
28
|
+
_WALL = 128
|
|
29
|
+
|
|
30
|
+
_B01_DRAWABLES = [
|
|
31
|
+
Drawable.CHARGER,
|
|
32
|
+
Drawable.NO_GO_AREAS,
|
|
33
|
+
Drawable.PATH,
|
|
34
|
+
Drawable.ROOM_NAMES,
|
|
35
|
+
Drawable.VACUUM_POSITION,
|
|
36
|
+
]
|
|
37
|
+
|
|
22
38
|
|
|
23
39
|
@dataclass
|
|
24
40
|
class B01MapParserConfig:
|
|
@@ -40,7 +56,11 @@ class B01MapParser:
|
|
|
40
56
|
size_x, size_y, grid = _extract_grid(parsed)
|
|
41
57
|
room_names = _extract_room_names(parsed)
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
room_pixels = _assign_room_pixels(parsed, grid, size_x=size_x, size_y=size_y)
|
|
60
|
+
carpet_pixels = _carpet_pixel_indices(parsed, grid, size_x=size_x, size_y=size_y)
|
|
61
|
+
image = _render_occupancy_image(
|
|
62
|
+
grid, room_pixels, carpet_pixels, size_x=size_x, size_y=size_y, scale=self._config.map_scale
|
|
63
|
+
)
|
|
44
64
|
|
|
45
65
|
map_data = MapData()
|
|
46
66
|
map_data.image = ImageData(
|
|
@@ -51,11 +71,30 @@ class B01MapParser:
|
|
|
51
71
|
width=size_x,
|
|
52
72
|
image_config=ImageConfig(scale=self._config.map_scale),
|
|
53
73
|
data=image,
|
|
54
|
-
|
|
74
|
+
# Overlay points are stored in the rendered image's top-down pixel
|
|
75
|
+
# space. ImageDimensions applies V1's bottom-up flip before drawing,
|
|
76
|
+
# so this adapter cancels it (same approach as the Q10 renderer).
|
|
77
|
+
img_transformation=lambda p: Point(p.x, size_y - p.y - 1, p.a),
|
|
55
78
|
)
|
|
56
79
|
if room_names:
|
|
57
80
|
map_data.additional_parameters["room_names"] = room_names
|
|
58
81
|
|
|
82
|
+
projector = _WorldToPixel(parsed)
|
|
83
|
+
has_drawables = _place_poses(map_data, parsed, projector)
|
|
84
|
+
map_data.rooms = _extract_rooms(parsed, projector, room_names)
|
|
85
|
+
has_drawables = has_drawables or bool(map_data.rooms)
|
|
86
|
+
if carpet_pixels:
|
|
87
|
+
# Same contract as the Q10 parser: flat top-down grid indices.
|
|
88
|
+
map_data.carpet_map = {(size_y - 1 - index // size_x) * size_x + index % size_x for index in carpet_pixels}
|
|
89
|
+
|
|
90
|
+
if has_drawables:
|
|
91
|
+
generator = _create_image_generator(
|
|
92
|
+
MapParserConfig(map_scale=self._config.map_scale),
|
|
93
|
+
drawables=_B01_DRAWABLES,
|
|
94
|
+
)
|
|
95
|
+
generator.draw_map(map_data)
|
|
96
|
+
image = map_data.image.data
|
|
97
|
+
|
|
59
98
|
image_bytes = io.BytesIO()
|
|
60
99
|
image.save(image_bytes, format=_MAP_FILE_FORMAT)
|
|
61
100
|
|
|
@@ -101,6 +140,100 @@ def _extract_grid(parsed: RobotMap) -> tuple[int, int, bytes]:
|
|
|
101
140
|
return size_x, size_y, map_data[:expected_len]
|
|
102
141
|
|
|
103
142
|
|
|
143
|
+
class _WorldToPixel:
|
|
144
|
+
"""Project SCMap world coordinates (meters) into top-down image pixels."""
|
|
145
|
+
|
|
146
|
+
def __init__(self, parsed: RobotMap) -> None:
|
|
147
|
+
head = parsed.mapHead
|
|
148
|
+
self._min_x = head.minX
|
|
149
|
+
self._min_y = head.minY
|
|
150
|
+
self._max_x = head.maxX
|
|
151
|
+
self._max_y = head.maxY
|
|
152
|
+
self._resolution = head.resolution or 0.05
|
|
153
|
+
self._size_y = head.sizeY
|
|
154
|
+
|
|
155
|
+
def in_bounds(self, x: float, y: float) -> bool:
|
|
156
|
+
"""Whether a world point lies inside the map (rejects placeholder poses)."""
|
|
157
|
+
return self._min_x <= x <= self._max_x and self._min_y <= y <= self._max_y
|
|
158
|
+
|
|
159
|
+
def to_pixel(self, x: float, y: float) -> tuple[float, float]:
|
|
160
|
+
"""World meters to top-down image pixel coordinates."""
|
|
161
|
+
px = (x - self._min_x) / self._resolution
|
|
162
|
+
py = self._size_y - 1 - (y - self._min_y) / self._resolution
|
|
163
|
+
return px, py
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _place_poses(map_data: MapData, parsed: RobotMap, projector: _WorldToPixel) -> bool:
|
|
167
|
+
"""Populate charger, robot position and path from the decoded SCMap."""
|
|
168
|
+
has_drawables = False
|
|
169
|
+
|
|
170
|
+
if parsed.HasField("chargeStation") and projector.in_bounds(parsed.chargeStation.x, parsed.chargeStation.y):
|
|
171
|
+
px, py = projector.to_pixel(parsed.chargeStation.x, parsed.chargeStation.y)
|
|
172
|
+
map_data.charger = Point(px, py, math.degrees(parsed.chargeStation.phi))
|
|
173
|
+
has_drawables = True
|
|
174
|
+
|
|
175
|
+
if parsed.HasField("currentPose") and projector.in_bounds(parsed.currentPose.x, parsed.currentPose.y):
|
|
176
|
+
px, py = projector.to_pixel(parsed.currentPose.x, parsed.currentPose.y)
|
|
177
|
+
map_data.vacuum_position = Point(px, py, math.degrees(parsed.currentPose.phi))
|
|
178
|
+
has_drawables = True
|
|
179
|
+
elif map_data.charger is not None:
|
|
180
|
+
# A saved map carries no live pose; show the robot at its dock.
|
|
181
|
+
map_data.vacuum_position = Point(map_data.charger.x, map_data.charger.y, map_data.charger.a)
|
|
182
|
+
|
|
183
|
+
areas = [
|
|
184
|
+
Area(*(coord for point in area.points for coord in projector.to_pixel(point.x, point.y)))
|
|
185
|
+
for area in parsed.areaInfo
|
|
186
|
+
if len(area.points) == 4
|
|
187
|
+
]
|
|
188
|
+
if areas:
|
|
189
|
+
# areaInfo type semantics are not yet mapped per zone kind; render all
|
|
190
|
+
# restricted areas through the no-go drawable for now.
|
|
191
|
+
map_data.no_go_areas = areas
|
|
192
|
+
has_drawables = True
|
|
193
|
+
|
|
194
|
+
if parsed.HasField("historyPose"):
|
|
195
|
+
pixels = [
|
|
196
|
+
Point(*projector.to_pixel(point.x, point.y))
|
|
197
|
+
for point in parsed.historyPose.points
|
|
198
|
+
if projector.in_bounds(point.x, point.y)
|
|
199
|
+
]
|
|
200
|
+
if pixels:
|
|
201
|
+
map_data.path = Path(len(pixels), 1, 0, [pixels])
|
|
202
|
+
has_drawables = True
|
|
203
|
+
|
|
204
|
+
return has_drawables
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _extract_rooms(parsed: RobotMap, projector: _WorldToPixel, room_names: dict[int, str]) -> dict[int, Room] | None:
|
|
208
|
+
"""Build room bounding boxes (image-pixel space) from room outlines."""
|
|
209
|
+
rooms: dict[int, Room] = {}
|
|
210
|
+
label_positions = {
|
|
211
|
+
room.roomId: projector.to_pixel(room.roomNamePost.x, room.roomNamePost.y)
|
|
212
|
+
for room in parsed.roomDataInfo
|
|
213
|
+
if room.HasField("roomNamePost")
|
|
214
|
+
}
|
|
215
|
+
size_y = parsed.mapHead.sizeY
|
|
216
|
+
for outline in parsed.roomOutline:
|
|
217
|
+
if not outline.points:
|
|
218
|
+
continue
|
|
219
|
+
room_id = outline.roomId
|
|
220
|
+
# Outline points are top-down after the same vertical flip as the raster.
|
|
221
|
+
xs = [point.x for point in outline.points]
|
|
222
|
+
ys = [size_y - 1 - point.y for point in outline.points]
|
|
223
|
+
pos = label_positions.get(room_id)
|
|
224
|
+
rooms[room_id] = Room(
|
|
225
|
+
min(xs),
|
|
226
|
+
min(ys),
|
|
227
|
+
max(xs),
|
|
228
|
+
max(ys),
|
|
229
|
+
room_id,
|
|
230
|
+
room_names.get(room_id),
|
|
231
|
+
pos[0] if pos else None,
|
|
232
|
+
pos[1] if pos else None,
|
|
233
|
+
)
|
|
234
|
+
return rooms or None
|
|
235
|
+
|
|
236
|
+
|
|
104
237
|
def _extract_room_names(parsed: RobotMap) -> dict[int, str]:
|
|
105
238
|
# Expose room id/name mapping without inventing room geometry/polygons.
|
|
106
239
|
room_names: dict[int, str] = {}
|
|
@@ -111,21 +244,138 @@ def _extract_room_names(parsed: RobotMap) -> dict[int, str]:
|
|
|
111
244
|
return room_names
|
|
112
245
|
|
|
113
246
|
|
|
114
|
-
def
|
|
115
|
-
"""
|
|
247
|
+
def _assign_room_pixels(parsed: RobotMap, grid: bytes, *, size_x: int, size_y: int) -> bytearray:
|
|
248
|
+
"""Assign a room id to each floor pixel by flood-filling from room labels.
|
|
249
|
+
|
|
250
|
+
The grid itself carries no room ids; room geometry arrives as boundary
|
|
251
|
+
pixel chains (``roomOutline``). Each room is filled from its label
|
|
252
|
+
position, bounded by walls and by any room's outline pixels, all in the
|
|
253
|
+
raw (bottom-up) grid space.
|
|
254
|
+
"""
|
|
255
|
+
assignment = bytearray(len(grid))
|
|
256
|
+
outlines = {outline.roomId: outline for outline in parsed.roomOutline if outline.points}
|
|
257
|
+
if not outlines:
|
|
258
|
+
return assignment
|
|
259
|
+
|
|
260
|
+
barrier = {
|
|
261
|
+
point.y * size_x + point.x
|
|
262
|
+
for outline in outlines.values()
|
|
263
|
+
for point in outline.points
|
|
264
|
+
if point.x < size_x and point.y < size_y
|
|
265
|
+
}
|
|
266
|
+
floor_count = grid.count(_FLOOR)
|
|
267
|
+
# ponytail: leak guard — a gapped outline would flood the whole floor, so a
|
|
268
|
+
# fill larger than half of it is discarded instead of tracing outline gaps.
|
|
269
|
+
max_fill = floor_count // 2
|
|
270
|
+
|
|
271
|
+
head = parsed.mapHead
|
|
272
|
+
label_positions = {
|
|
273
|
+
room.roomId: (
|
|
274
|
+
int((room.roomNamePost.x - head.minX) / head.resolution),
|
|
275
|
+
int((room.roomNamePost.y - head.minY) / head.resolution),
|
|
276
|
+
)
|
|
277
|
+
for room in parsed.roomDataInfo
|
|
278
|
+
if room.HasField("roomNamePost")
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for room_id, outline in outlines.items():
|
|
282
|
+
seed = label_positions.get(room_id)
|
|
283
|
+
if seed is None:
|
|
284
|
+
continue
|
|
285
|
+
col, row = seed
|
|
286
|
+
start = row * size_x + col
|
|
287
|
+
if not (0 <= col < size_x and 0 <= row < size_y) or grid[start] != _FLOOR:
|
|
288
|
+
continue
|
|
289
|
+
filled: list[int] = []
|
|
290
|
+
queue = deque([start])
|
|
291
|
+
seen = {start}
|
|
292
|
+
while queue and len(filled) <= max_fill:
|
|
293
|
+
index = queue.popleft()
|
|
294
|
+
filled.append(index)
|
|
295
|
+
for neighbor in (index - 1, index + 1, index - size_x, index + size_x):
|
|
296
|
+
if (
|
|
297
|
+
0 <= neighbor < len(grid)
|
|
298
|
+
and neighbor not in seen
|
|
299
|
+
and grid[neighbor] == _FLOOR
|
|
300
|
+
and assignment[neighbor] == 0
|
|
301
|
+
and neighbor not in barrier
|
|
302
|
+
# Row-wrap guard for the horizontal neighbors.
|
|
303
|
+
and abs(neighbor % size_x - index % size_x) <= 1
|
|
304
|
+
):
|
|
305
|
+
seen.add(neighbor)
|
|
306
|
+
queue.append(neighbor)
|
|
307
|
+
if len(filled) > max_fill:
|
|
308
|
+
continue
|
|
309
|
+
for index in filled:
|
|
310
|
+
assignment[index] = room_id
|
|
311
|
+
# Color the room's own boundary ring too where it sits on floor.
|
|
312
|
+
for point in outline.points:
|
|
313
|
+
index = point.y * size_x + point.x
|
|
314
|
+
if index < len(grid) and grid[index] == _FLOOR and assignment[index] == 0:
|
|
315
|
+
assignment[index] = room_id
|
|
316
|
+
|
|
317
|
+
return assignment
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _carpet_pixel_indices(parsed: RobotMap, grid: bytes, *, size_x: int, size_y: int) -> set[int]:
|
|
321
|
+
"""Raw-grid indices of floor pixels covered by enabled carpets."""
|
|
322
|
+
head = parsed.mapHead
|
|
323
|
+
resolution = head.resolution or 0.05
|
|
324
|
+
indices: set[int] = set()
|
|
325
|
+
for carpet in parsed.carpetInfo:
|
|
326
|
+
if not carpet.points or (carpet.HasField("enabled") and not carpet.enabled):
|
|
327
|
+
continue
|
|
328
|
+
cols = [int((point.x - head.minX) / resolution) for point in carpet.points]
|
|
329
|
+
rows = [int((point.y - head.minY) / resolution) for point in carpet.points]
|
|
330
|
+
for row in range(max(min(rows), 0), min(max(rows), size_y - 1) + 1):
|
|
331
|
+
for col in range(max(min(cols), 0), min(max(cols), size_x - 1) + 1):
|
|
332
|
+
index = row * size_x + col
|
|
333
|
+
if grid[index] == _FLOOR:
|
|
334
|
+
indices.add(index)
|
|
335
|
+
return indices
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _render_occupancy_image(
|
|
339
|
+
grid: bytes, room_pixels: bytearray, carpet_pixels: set[int], *, size_x: int, size_y: int, scale: int
|
|
340
|
+
) -> Image.Image:
|
|
341
|
+
"""Render the B01 occupancy grid with per-room colors."""
|
|
342
|
+
|
|
343
|
+
colors = ColorsPalette()
|
|
344
|
+
room_colors = {
|
|
345
|
+
room_id: tuple(color[:3]) + (255,)
|
|
346
|
+
for room_id, color in adjacency_aware_room_colors(
|
|
347
|
+
room_pixels, size_x, colors, lambda value: value or None
|
|
348
|
+
).items()
|
|
349
|
+
}
|
|
116
350
|
|
|
117
351
|
# The observed occupancy grid contains only:
|
|
118
352
|
# - 0: outside/unknown
|
|
119
|
-
# - 127:
|
|
120
|
-
# - 128:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
353
|
+
# - 127: floor/free
|
|
354
|
+
# - 128: wall/obstacle
|
|
355
|
+
# Same V1 palette roles as the Q10 renderer: transparent outside, grey
|
|
356
|
+
# walls/obstacles, MAP_INSIDE for floor not assigned to any room.
|
|
357
|
+
outside = (0, 0, 0, 0)
|
|
358
|
+
floor = tuple(colors.get_color(SupportedColor.MAP_INSIDE)[:3]) + (255,)
|
|
359
|
+
base_colors = {
|
|
360
|
+
0: outside,
|
|
361
|
+
_FLOOR: floor,
|
|
362
|
+
_WALL: tuple(colors.get_color(SupportedColor.GREY_WALL)[:3]) + (255,),
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
rgba = bytearray()
|
|
366
|
+
for index, value in enumerate(grid):
|
|
367
|
+
if value == _FLOOR and (room_id := room_pixels[index]):
|
|
368
|
+
color = room_colors.get(room_id, floor)
|
|
369
|
+
else:
|
|
370
|
+
color = base_colors.get(value, floor)
|
|
371
|
+
if index in carpet_pixels and (index // size_x + index % size_x) % 2 == 0:
|
|
372
|
+
# Checkerboard stipple, like the V1 carpet texture.
|
|
373
|
+
color = tuple(min(channel + 60, 255) for channel in color[:3]) + (255,)
|
|
374
|
+
rgba.extend(color)
|
|
375
|
+
|
|
376
|
+
# RGBA so the shared V1 ImageGenerator can alpha-composite overlay glyphs.
|
|
377
|
+
img = Image.frombytes("RGBA", (size_x, size_y), bytes(rgba))
|
|
378
|
+
img = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
|
|
129
379
|
|
|
130
380
|
if scale > 1:
|
|
131
381
|
img = img.resize((size_x * scale, size_y * scale), resample=Image.Resampling.NEAREST)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Checked-in B01/Q7 SCMap schema for the generated runtime protobuf module.
|
|
2
2
|
// Regenerate the checked-in Python module after edits with:
|
|
3
|
-
// python -m grpc_tools.protoc -I
|
|
3
|
+
// python -m grpc_tools.protoc -I. --python_out=. roborock/map/proto/b01_scmap.proto
|
|
4
4
|
// The generated file `b01_scmap_pb2.py` is checked in for runtime use and should
|
|
5
5
|
// not be edited by hand.
|
|
6
6
|
syntax = "proto2";
|
|
@@ -47,6 +47,79 @@ message MapDataInfo {
|
|
|
47
47
|
optional bytes mapData = 1;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
message MapInfo {
|
|
51
|
+
optional uint32 mapId = 1;
|
|
52
|
+
optional string mapName = 2;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
message DevicePoseDataInfo {
|
|
56
|
+
optional uint32 update = 1;
|
|
57
|
+
optional float x = 2;
|
|
58
|
+
optional float y = 3;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
message DeviceHistoryPoseInfo {
|
|
62
|
+
optional uint32 poseId = 1;
|
|
63
|
+
repeated DevicePoseDataInfo points = 2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
message DevicePoseInfo {
|
|
67
|
+
optional float x = 1;
|
|
68
|
+
optional float y = 2;
|
|
69
|
+
optional float phi = 3;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
message DeviceCurrentPoseInfo {
|
|
73
|
+
optional uint32 poseId = 1;
|
|
74
|
+
optional uint32 update = 2;
|
|
75
|
+
optional float x = 3;
|
|
76
|
+
optional float y = 4;
|
|
77
|
+
optional float phi = 5;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
message DeviceAreaDataInfo {
|
|
81
|
+
optional uint32 status = 1;
|
|
82
|
+
optional uint32 type = 2;
|
|
83
|
+
optional uint32 areaIndex = 3;
|
|
84
|
+
repeated DevicePointInfo points = 4;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
message RoomMatrixInfo {
|
|
88
|
+
optional bytes matrix = 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
message RoomOutlinePointInfo {
|
|
92
|
+
optional uint32 x = 1;
|
|
93
|
+
optional uint32 y = 2;
|
|
94
|
+
optional uint32 value = 3;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
message RoomBorderPointInfo {
|
|
98
|
+
optional uint32 x = 1;
|
|
99
|
+
optional uint32 y = 2;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
message RoomBorderInfo {
|
|
103
|
+
repeated RoomBorderPointInfo points = 1;
|
|
104
|
+
repeated uint32 roomIds = 2;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
message RoomOutlineInfo {
|
|
108
|
+
optional uint32 roomId = 1;
|
|
109
|
+
repeated RoomOutlinePointInfo points = 2;
|
|
110
|
+
repeated RoomBorderInfo borders = 3;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
message CarpetDataInfo {
|
|
114
|
+
optional uint32 carpetId = 1;
|
|
115
|
+
optional uint32 type = 2;
|
|
116
|
+
optional uint32 unknown3 = 3;
|
|
117
|
+
repeated DevicePointInfo points = 4;
|
|
118
|
+
optional uint32 enabled = 6;
|
|
119
|
+
optional uint32 unknown7 = 7;
|
|
120
|
+
optional uint32 unknown8 = 8;
|
|
121
|
+
}
|
|
122
|
+
|
|
50
123
|
message RoomDataInfo {
|
|
51
124
|
optional uint32 roomId = 1;
|
|
52
125
|
optional string roomName = 2;
|
|
@@ -66,5 +139,13 @@ message RobotMap {
|
|
|
66
139
|
optional MapExtInfo mapExtInfo = 2;
|
|
67
140
|
optional MapHeadInfo mapHead = 3;
|
|
68
141
|
optional MapDataInfo mapData = 4;
|
|
142
|
+
repeated MapInfo mapInfo = 5;
|
|
143
|
+
optional DeviceHistoryPoseInfo historyPose = 6;
|
|
144
|
+
optional DevicePoseInfo chargeStation = 7;
|
|
145
|
+
optional DeviceCurrentPoseInfo currentPose = 8;
|
|
146
|
+
repeated DeviceAreaDataInfo areaInfo = 9;
|
|
69
147
|
repeated RoomDataInfo roomDataInfo = 12;
|
|
148
|
+
optional RoomMatrixInfo roomMatrix = 13;
|
|
149
|
+
repeated RoomOutlineInfo roomOutline = 14;
|
|
150
|
+
repeated CarpetDataInfo carpetInfo = 20;
|
|
70
151
|
}
|
|
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
|
|
|
24
24
|
|
|
25
25
|
|
|
26
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\"\
|
|
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\")\n\x07MapInfo\x12\r\n\x05mapId\x18\x01 \x01(\r\x12\x0f\n\x07mapName\x18\x02 \x01(\t\":\n\x12\x44\x65vicePoseDataInfo\x12\x0e\n\x06update\x18\x01 \x01(\r\x12\t\n\x01x\x18\x02 \x01(\x02\x12\t\n\x01y\x18\x03 \x01(\x02\"V\n\x15\x44\x65viceHistoryPoseInfo\x12\x0e\n\x06poseId\x18\x01 \x01(\r\x12-\n\x06points\x18\x02 \x03(\x0b\x32\x1d.b01.scmap.DevicePoseDataInfo\"3\n\x0e\x44\x65vicePoseInfo\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\x0b\n\x03phi\x18\x03 \x01(\x02\"Z\n\x15\x44\x65viceCurrentPoseInfo\x12\x0e\n\x06poseId\x18\x01 \x01(\r\x12\x0e\n\x06update\x18\x02 \x01(\r\x12\t\n\x01x\x18\x03 \x01(\x02\x12\t\n\x01y\x18\x04 \x01(\x02\x12\x0b\n\x03phi\x18\x05 \x01(\x02\"q\n\x12\x44\x65viceAreaDataInfo\x12\x0e\n\x06status\x18\x01 \x01(\r\x12\x0c\n\x04type\x18\x02 \x01(\r\x12\x11\n\tareaIndex\x18\x03 \x01(\r\x12*\n\x06points\x18\x04 \x03(\x0b\x32\x1a.b01.scmap.DevicePointInfo\" \n\x0eRoomMatrixInfo\x12\x0e\n\x06matrix\x18\x01 \x01(\x0c\";\n\x14RoomOutlinePointInfo\x12\t\n\x01x\x18\x01 \x01(\r\x12\t\n\x01y\x18\x02 \x01(\r\x12\r\n\x05value\x18\x03 \x01(\r\"+\n\x13RoomBorderPointInfo\x12\t\n\x01x\x18\x01 \x01(\r\x12\t\n\x01y\x18\x02 \x01(\r\"Q\n\x0eRoomBorderInfo\x12.\n\x06points\x18\x01 \x03(\x0b\x32\x1e.b01.scmap.RoomBorderPointInfo\x12\x0f\n\x07roomIds\x18\x02 \x03(\r\"~\n\x0fRoomOutlineInfo\x12\x0e\n\x06roomId\x18\x01 \x01(\r\x12/\n\x06points\x18\x02 \x03(\x0b\x32\x1f.b01.scmap.RoomOutlinePointInfo\x12*\n\x07\x62orders\x18\x03 \x03(\x0b\x32\x19.b01.scmap.RoomBorderInfo\"\xa3\x01\n\x0e\x43\x61rpetDataInfo\x12\x10\n\x08\x63\x61rpetId\x18\x01 \x01(\r\x12\x0c\n\x04type\x18\x02 \x01(\r\x12\x10\n\x08unknown3\x18\x03 \x01(\r\x12*\n\x06points\x18\x04 \x03(\x0b\x32\x1a.b01.scmap.DevicePointInfo\x12\x0f\n\x07\x65nabled\x18\x06 \x01(\r\x12\x10\n\x08unknown7\x18\x07 \x01(\r\x12\x10\n\x08unknown8\x18\x08 \x01(\r\"\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\"\xcc\x04\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\x07mapInfo\x18\x05 \x03(\x0b\x32\x12.b01.scmap.MapInfo\x12\x35\n\x0bhistoryPose\x18\x06 \x01(\x0b\x32 .b01.scmap.DeviceHistoryPoseInfo\x12\x30\n\rchargeStation\x18\x07 \x01(\x0b\x32\x19.b01.scmap.DevicePoseInfo\x12\x35\n\x0b\x63urrentPose\x18\x08 \x01(\x0b\x32 .b01.scmap.DeviceCurrentPoseInfo\x12/\n\x08\x61reaInfo\x18\t \x03(\x0b\x32\x1d.b01.scmap.DeviceAreaDataInfo\x12-\n\x0croomDataInfo\x18\x0c \x03(\x0b\x32\x17.b01.scmap.RoomDataInfo\x12-\n\nroomMatrix\x18\r \x01(\x0b\x32\x19.b01.scmap.RoomMatrixInfo\x12/\n\x0broomOutline\x18\x0e \x03(\x0b\x32\x1a.b01.scmap.RoomOutlineInfo\x12-\n\ncarpetInfo\x18\x14 \x03(\x0b\x32\x19.b01.scmap.CarpetDataInfo')
|
|
28
28
|
|
|
29
29
|
_globals = globals()
|
|
30
30
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -41,8 +41,32 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
|
|
41
41
|
_globals['_MAPHEADINFO']._serialized_end=544
|
|
42
42
|
_globals['_MAPDATAINFO']._serialized_start=546
|
|
43
43
|
_globals['_MAPDATAINFO']._serialized_end=576
|
|
44
|
-
_globals['
|
|
45
|
-
_globals['
|
|
46
|
-
_globals['
|
|
47
|
-
_globals['
|
|
44
|
+
_globals['_MAPINFO']._serialized_start=578
|
|
45
|
+
_globals['_MAPINFO']._serialized_end=619
|
|
46
|
+
_globals['_DEVICEPOSEDATAINFO']._serialized_start=621
|
|
47
|
+
_globals['_DEVICEPOSEDATAINFO']._serialized_end=679
|
|
48
|
+
_globals['_DEVICEHISTORYPOSEINFO']._serialized_start=681
|
|
49
|
+
_globals['_DEVICEHISTORYPOSEINFO']._serialized_end=767
|
|
50
|
+
_globals['_DEVICEPOSEINFO']._serialized_start=769
|
|
51
|
+
_globals['_DEVICEPOSEINFO']._serialized_end=820
|
|
52
|
+
_globals['_DEVICECURRENTPOSEINFO']._serialized_start=822
|
|
53
|
+
_globals['_DEVICECURRENTPOSEINFO']._serialized_end=912
|
|
54
|
+
_globals['_DEVICEAREADATAINFO']._serialized_start=914
|
|
55
|
+
_globals['_DEVICEAREADATAINFO']._serialized_end=1027
|
|
56
|
+
_globals['_ROOMMATRIXINFO']._serialized_start=1029
|
|
57
|
+
_globals['_ROOMMATRIXINFO']._serialized_end=1061
|
|
58
|
+
_globals['_ROOMOUTLINEPOINTINFO']._serialized_start=1063
|
|
59
|
+
_globals['_ROOMOUTLINEPOINTINFO']._serialized_end=1122
|
|
60
|
+
_globals['_ROOMBORDERPOINTINFO']._serialized_start=1124
|
|
61
|
+
_globals['_ROOMBORDERPOINTINFO']._serialized_end=1167
|
|
62
|
+
_globals['_ROOMBORDERINFO']._serialized_start=1169
|
|
63
|
+
_globals['_ROOMBORDERINFO']._serialized_end=1250
|
|
64
|
+
_globals['_ROOMOUTLINEINFO']._serialized_start=1252
|
|
65
|
+
_globals['_ROOMOUTLINEINFO']._serialized_end=1378
|
|
66
|
+
_globals['_CARPETDATAINFO']._serialized_start=1381
|
|
67
|
+
_globals['_CARPETDATAINFO']._serialized_end=1544
|
|
68
|
+
_globals['_ROOMDATAINFO']._serialized_start=1547
|
|
69
|
+
_globals['_ROOMDATAINFO']._serialized_end=1810
|
|
70
|
+
_globals['_ROBOTMAP']._serialized_start=1813
|
|
71
|
+
_globals['_ROBOTMAP']._serialized_end=2401
|
|
48
72
|
# @@protoc_insertion_point(module_scope)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|