|
| 1 | +"""Module for parsing v1 Roborock map content.""" |
| 2 | + |
| 3 | +import io |
| 4 | +import logging |
| 5 | +from dataclasses import dataclass, field |
| 6 | + |
| 7 | +from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor |
| 8 | +from vacuum_map_parser_base.config.drawable import Drawable |
| 9 | +from vacuum_map_parser_base.config.image_config import ImageConfig |
| 10 | +from vacuum_map_parser_base.config.size import Size, Sizes |
| 11 | +from vacuum_map_parser_base.map_data import MapData |
| 12 | +from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser |
| 13 | + |
| 14 | +from roborock.exceptions import RoborockException |
| 15 | + |
| 16 | +_LOGGER = logging.getLogger(__name__) |
| 17 | + |
| 18 | +DEFAULT_DRAWABLES = { |
| 19 | + Drawable.CHARGER: True, |
| 20 | + Drawable.CLEANED_AREA: False, |
| 21 | + Drawable.GOTO_PATH: False, |
| 22 | + Drawable.IGNORED_OBSTACLES: False, |
| 23 | + Drawable.IGNORED_OBSTACLES_WITH_PHOTO: False, |
| 24 | + Drawable.MOP_PATH: False, |
| 25 | + Drawable.NO_CARPET_AREAS: False, |
| 26 | + Drawable.NO_GO_AREAS: False, |
| 27 | + Drawable.NO_MOPPING_AREAS: False, |
| 28 | + Drawable.OBSTACLES: False, |
| 29 | + Drawable.OBSTACLES_WITH_PHOTO: False, |
| 30 | + Drawable.PATH: True, |
| 31 | + Drawable.PREDICTED_PATH: False, |
| 32 | + Drawable.VACUUM_POSITION: True, |
| 33 | + Drawable.VIRTUAL_WALLS: False, |
| 34 | + Drawable.ZONES: False, |
| 35 | +} |
| 36 | +DEFAULT_MAP_SCALE = 4 |
| 37 | +MAP_FILE_FORMAT = "PNG" |
| 38 | + |
| 39 | + |
| 40 | +def _default_drawable_factory() -> list[Drawable]: |
| 41 | + return [drawable for drawable, default_value in DEFAULT_DRAWABLES.items() if default_value] |
| 42 | + |
| 43 | + |
| 44 | +@dataclass |
| 45 | +class MapParserConfig: |
| 46 | + """Configuration for the Roborock map parser.""" |
| 47 | + |
| 48 | + drawables: list[Drawable] = field(default_factory=_default_drawable_factory) |
| 49 | + """List of drawables to include in the map rendering.""" |
| 50 | + |
| 51 | + show_background: bool = True |
| 52 | + """Whether to show the background of the map.""" |
| 53 | + |
| 54 | + map_scale: int = DEFAULT_MAP_SCALE |
| 55 | + """Scale factor for the map.""" |
| 56 | + |
| 57 | + |
| 58 | +@dataclass |
| 59 | +class ParsedMapData: |
| 60 | + """Roborock Map Data. |
| 61 | +
|
| 62 | + This class holds the parsed map data and the rendered image. |
| 63 | + """ |
| 64 | + |
| 65 | + image_content: bytes | None |
| 66 | + """The rendered image of the map in PNG format.""" |
| 67 | + |
| 68 | + map_data: MapData | None |
| 69 | + """The parsed map data which contains metadata for points on the map.""" |
| 70 | + |
| 71 | + |
| 72 | +class MapParser: |
| 73 | + """Roborock Map Parser. |
| 74 | +
|
| 75 | + This class is used to parse the map data from the device and render it into an image. |
| 76 | + """ |
| 77 | + |
| 78 | + def __init__(self, config: MapParserConfig) -> None: |
| 79 | + """Initialize the MapParser.""" |
| 80 | + self._map_parser = _create_map_data_parser(config) |
| 81 | + |
| 82 | + def parse(self, map_bytes: bytes) -> ParsedMapData | None: |
| 83 | + """Parse map_bytes and return MapData and the image.""" |
| 84 | + try: |
| 85 | + parsed_map = self._map_parser.parse(map_bytes) |
| 86 | + except (IndexError, ValueError) as err: |
| 87 | + raise RoborockException("Failed to parse map data") from err |
| 88 | + if parsed_map.image is None: |
| 89 | + raise RoborockException("Failed to render map image") |
| 90 | + img_byte_arr = io.BytesIO() |
| 91 | + parsed_map.image.data.save(img_byte_arr, format=MAP_FILE_FORMAT) |
| 92 | + return ParsedMapData(image_content=img_byte_arr.getvalue(), map_data=parsed_map) |
| 93 | + |
| 94 | + |
| 95 | +def _create_map_data_parser(config: MapParserConfig) -> RoborockMapDataParser: |
| 96 | + """Create a RoborockMapDataParser based on the config entry.""" |
| 97 | + colors = ColorsPalette() |
| 98 | + if not config.show_background: |
| 99 | + colors = ColorsPalette({SupportedColor.MAP_OUTSIDE: (0, 0, 0, 0)}) |
| 100 | + return RoborockMapDataParser( |
| 101 | + colors, |
| 102 | + Sizes({k: v * config.map_scale for k, v in Sizes.SIZES.items() if k != Size.MOP_PATH_WIDTH}), |
| 103 | + config.drawables, |
| 104 | + ImageConfig(scale=config.map_scale), |
| 105 | + [], |
| 106 | + ) |
0 commit comments