Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 1 addition & 57 deletions roborock/map/map_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io
import logging
import threading
from dataclasses import dataclass, field

from vacuum_map_parser_base.config.color import Color, ColorsPalette, SupportedColor
Expand All @@ -11,13 +10,10 @@
from vacuum_map_parser_base.config.size import Size, Sizes
from vacuum_map_parser_base.image_generator import ImageGenerator
from vacuum_map_parser_base.map_data import MapData
from vacuum_map_parser_roborock.image_parser import RoborockImageParser
from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser

from roborock.exceptions import RoborockException

from .room_colors import adjacency_aware_room_colors

_LOGGER = logging.getLogger(__name__)

DEFAULT_DRAWABLES = {
Expand Down Expand Up @@ -103,68 +99,16 @@ def parse(self, map_bytes: bytes) -> ParsedMapData | None:
return ParsedMapData(image_content=img_byte_arr.getvalue(), map_data=parsed_map)


class _AdjacencyAwareRoborockImageParser(RoborockImageParser):
"""Apply the shared adjacency color policy to V1 room cells."""

def __init__(
self,
palette: ColorsPalette,
image_config: ImageConfig,
*,
recolor_rooms: bool = True,
) -> None:
super().__init__(palette, image_config)
self._room_palette = palette
self._base_room_colors = palette.cached_room_colors.copy()
self._recolor_rooms = recolor_rooms
self._palette_lock = threading.Lock()

def parse(
self,
raw_data: bytes,
width: int,
height: int,
carpet_map: set[int] | None,
removed_map: set[int] | None = None,
):
"""Assign non-conflicting room colors before the V1 image pass."""
with self._palette_lock:
# cached_room_colors is a read-only property, so reset its dict in place.
cached_room_colors = self._room_palette.cached_room_colors
cached_room_colors.clear()
cached_room_colors.update(self._base_room_colors)

if self._recolor_rooms:

def room_id(value: int) -> int | None:
if value in (self.MAP_OUTSIDE, self.MAP_WALL, self.MAP_INSIDE, self.MAP_SCAN):
return None
return self._get_room_number(value) if value & 0x07 == 0x07 else None

room_colors = adjacency_aware_room_colors(raw_data, width, self._room_palette, room_id)
for number, color in room_colors.items():
# ColorsPalette caches both forms for get_room_color(str | int).
cached_room_colors[number] = color
cached_room_colors[str(number)] = color
return super().parse(raw_data, width, height, carpet_map, removed_map)


def _create_map_data_parser(config: MapParserConfig) -> RoborockMapDataParser:
"""Create a RoborockMapDataParser based on the config entry."""
palette, sizes, image_config = _create_rendering_components(config)
parser = RoborockMapDataParser(
return RoborockMapDataParser(
palette,
sizes,
config.drawables,
image_config,
[],
)
parser._image_parser = _AdjacencyAwareRoborockImageParser(
palette,
image_config,
recolor_rooms=config.show_rooms,
)
return parser


def _create_image_generator(
Expand Down
43 changes: 1 addition & 42 deletions tests/map/test_map_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@
from pathlib import Path

import pytest
from vacuum_map_parser_base.config.color import ColorsPalette
from vacuum_map_parser_base.config.image_config import ImageConfig

from roborock.exceptions import RoborockException
from roborock.map.map_parser import (
MapParser,
MapParserConfig,
_AdjacencyAwareRoborockImageParser,
)
from roborock.map.map_parser import MapParser, MapParserConfig

MAP_DATA_FILE = Path(__file__).parent / "raw_map_data"
DEFAULT_MAP_CONFIG = MapParserConfig()
Expand All @@ -25,39 +19,4 @@ def test_invalid_map_content(map_content: bytes):
parser.parse(map_content)


def test_v1_parser_gives_adjacent_rooms_distinct_palette_colors() -> None:
"""Repeated palette entries do not merge neighboring V1 rooms."""
palette = ColorsPalette()
original_room_12 = palette.get_room_color(12)
image_parser = _AdjacencyAwareRoborockImageParser(palette, ImageConfig())
raw_data = bytes([(2 << 3) | 7, (12 << 3) | 7])

image, _rooms = image_parser.parse(raw_data, 2, 1, None)

assert image is not None
assert image.getpixel((0, 0)) != image.getpixel((1, 0))
assert palette.get_room_color(12) == palette.get_room_color("12")

isolated_image, _rooms = image_parser.parse(bytes([(12 << 3) | 7]), 1, 1, None)

assert isolated_image is not None
assert isolated_image.getpixel((0, 0))[: len(original_room_12)] == original_room_12


def test_v1_parser_keeps_adjacent_rooms_hidden_when_rooms_disabled() -> None:
"""Adjacency conflict handling cannot override intentional transparency."""
hidden_rooms = {str(room_id): (0, 0, 0, 0) for room_id in range(1, 32)}
parser = _AdjacencyAwareRoborockImageParser(
ColorsPalette({}, hidden_rooms),
ImageConfig(),
recolor_rooms=False,
)
raw_data = bytes([(2 << 3) | 7, (12 << 3) | 7])

image, _rooms = parser.parse(raw_data, 2, 1, None)

assert image is not None
assert [image.getpixel((x, 0)) for x in range(2)] == [(0, 0, 0, 0)] * 2


# We can add additional tests here in the future that actually parse valid map data
Loading