Skip to content

Commit f3d5dee

Browse files
andigclaude
andcommitted
feat: decode and render Q7 carpets
Carpets arrive as RobotMap field 20 (id, type, four vertices in meters, enabled flag), confirmed live by adding a carpet in the app and diffing map frames. Enabled carpet rectangles are stippled into the raster with a checkerboard texture like the V1 carpet look, and exposed as the same flat top-down carpet_map contract the Q10 parser uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1628aca commit f3d5dee

3 files changed

Lines changed: 50 additions & 9 deletions

File tree

roborock/map/b01_map_parser.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ def parse(self, payload: bytes) -> ParsedMapData:
5757
room_names = _extract_room_names(parsed)
5858

5959
room_pixels = _assign_room_pixels(parsed, grid, size_x=size_x, size_y=size_y)
60-
image = _render_occupancy_image(grid, room_pixels, size_x=size_x, size_y=size_y, scale=self._config.map_scale)
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+
)
6164

6265
map_data = MapData()
6366
map_data.image = ImageData(
@@ -80,6 +83,9 @@ def parse(self, payload: bytes) -> ParsedMapData:
8083
has_drawables = _place_poses(map_data, parsed, projector)
8184
map_data.rooms = _extract_rooms(parsed, projector, room_names)
8285
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}
8389

8490
if has_drawables:
8591
generator = _create_image_generator(
@@ -302,8 +308,26 @@ def _assign_room_pixels(parsed: RobotMap, grid: bytes, *, size_x: int, size_y: i
302308
return assignment
303309

304310

311+
def _carpet_pixel_indices(parsed: RobotMap, grid: bytes, *, size_x: int, size_y: int) -> set[int]:
312+
"""Raw-grid indices of floor pixels covered by enabled carpets."""
313+
head = parsed.mapHead
314+
resolution = head.resolution or 0.05
315+
indices: set[int] = set()
316+
for carpet in parsed.carpetInfo:
317+
if not carpet.points or (carpet.HasField("enabled") and not carpet.enabled):
318+
continue
319+
cols = [int((point.x - head.minX) / resolution) for point in carpet.points]
320+
rows = [int((point.y - head.minY) / resolution) for point in carpet.points]
321+
for row in range(max(min(rows), 0), min(max(rows), size_y - 1) + 1):
322+
for col in range(max(min(cols), 0), min(max(cols), size_x - 1) + 1):
323+
index = row * size_x + col
324+
if grid[index] == _FLOOR:
325+
indices.add(index)
326+
return indices
327+
328+
305329
def _render_occupancy_image(
306-
grid: bytes, room_pixels: bytearray, *, size_x: int, size_y: int, scale: int
330+
grid: bytes, room_pixels: bytearray, carpet_pixels: set[int], *, size_x: int, size_y: int, scale: int
307331
) -> Image.Image:
308332
"""Render the B01 occupancy grid with per-room colors."""
309333

@@ -332,9 +356,13 @@ def _render_occupancy_image(
332356
rgba = bytearray()
333357
for index, value in enumerate(grid):
334358
if value == _FLOOR and (room_id := room_pixels[index]):
335-
rgba.extend(room_colors.get(room_id, floor))
359+
color = room_colors.get(room_id, floor)
336360
else:
337-
rgba.extend(base_colors.get(value, floor))
361+
color = base_colors.get(value, floor)
362+
if index in carpet_pixels and (index // size_x + index % size_x) % 2 == 0:
363+
# Checkerboard stipple, like the V1 carpet texture.
364+
color = tuple(min(channel + 60, 255) for channel in color[:3]) + (255,)
365+
rgba.extend(color)
338366

339367
# RGBA so the shared V1 ImageGenerator can alpha-composite overlay glyphs.
340368
img = Image.frombytes("RGBA", (size_x, size_y), bytes(rgba))

roborock/map/proto/b01_scmap.proto

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ message RoomOutlineInfo {
110110
repeated RoomBorderInfo borders = 3;
111111
}
112112

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+
113123
message RoomDataInfo {
114124
optional uint32 roomId = 1;
115125
optional string roomName = 2;
@@ -137,4 +147,5 @@ message RobotMap {
137147
repeated RoomDataInfo roomDataInfo = 12;
138148
optional RoomMatrixInfo roomMatrix = 13;
139149
repeated RoomOutlineInfo roomOutline = 14;
150+
repeated CarpetDataInfo carpetInfo = 20;
140151
}

roborock/map/proto/b01_scmap_pb2.py

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)