From 2b5bd0e31753b22479a8253e4b24da94022d76ff Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:44:56 +0200 Subject: [PATCH 01/14] adding const.py and update types.py to separate const and types Signed-off-by: Sandro Cantarella --- SCR/valetudo_map_parser/hypfer_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SCR/valetudo_map_parser/hypfer_handler.py b/SCR/valetudo_map_parser/hypfer_handler.py index a125d4a..69b1383 100644 --- a/SCR/valetudo_map_parser/hypfer_handler.py +++ b/SCR/valetudo_map_parser/hypfer_handler.py @@ -16,6 +16,7 @@ from .config.async_utils import AsyncPIL from .config.drawable_elements import DrawableElement from .config.shared import CameraShared +from .const import COLORS from .config.types import ( LOGGER, CalibrationPoints, From c6ab4ee95be9efcf6b2dd3fdf129c43cdb997cbb Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:06:54 +0100 Subject: [PATCH 02/14] last files for 12 isort / ruff and lint Signed-off-by: Sandro Cantarella --- SCR/valetudo_map_parser/hypfer_handler.py | 1 - SCR/valetudo_map_parser/rand256_handler.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/SCR/valetudo_map_parser/hypfer_handler.py b/SCR/valetudo_map_parser/hypfer_handler.py index 69b1383..a125d4a 100644 --- a/SCR/valetudo_map_parser/hypfer_handler.py +++ b/SCR/valetudo_map_parser/hypfer_handler.py @@ -16,7 +16,6 @@ from .config.async_utils import AsyncPIL from .config.drawable_elements import DrawableElement from .config.shared import CameraShared -from .const import COLORS from .config.types import ( LOGGER, CalibrationPoints, diff --git a/SCR/valetudo_map_parser/rand256_handler.py b/SCR/valetudo_map_parser/rand256_handler.py index 3ed2a74..8e150e8 100644 --- a/SCR/valetudo_map_parser/rand256_handler.py +++ b/SCR/valetudo_map_parser/rand256_handler.py @@ -15,7 +15,6 @@ from .config.async_utils import AsyncPIL from .config.drawable_elements import DrawableElement -from .const import COLORS, DEFAULT_IMAGE_SIZE, DEFAULT_PIXEL_SIZE from .config.types import ( LOGGER, Colors, @@ -31,6 +30,7 @@ initialize_drawing_config, point_in_polygon, ) +from .const import COLORS, DEFAULT_IMAGE_SIZE, DEFAULT_PIXEL_SIZE from .map_data import RandImageData from .reimg_draw import ImageDraw from .rooms_handler import RandRoomsHandler From e6b006c8ffb6768475f7050f6a9a24e6c500f454 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Fri, 19 Dec 2025 08:07:11 +0100 Subject: [PATCH 03/14] remove duplicate import of const Signed-off-by: Sandro Cantarella --- SCR/valetudo_map_parser/rand256_handler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SCR/valetudo_map_parser/rand256_handler.py b/SCR/valetudo_map_parser/rand256_handler.py index 8e150e8..d1df01c 100644 --- a/SCR/valetudo_map_parser/rand256_handler.py +++ b/SCR/valetudo_map_parser/rand256_handler.py @@ -30,7 +30,6 @@ initialize_drawing_config, point_in_polygon, ) -from .const import COLORS, DEFAULT_IMAGE_SIZE, DEFAULT_PIXEL_SIZE from .map_data import RandImageData from .reimg_draw import ImageDraw from .rooms_handler import RandRoomsHandler From a5ca57172820a8526d0b04f8342001e7621919e9 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:59:48 +0100 Subject: [PATCH 04/14] const was not properly imported Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/rand256_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SCR/valetudo_map_parser/rand256_handler.py b/SCR/valetudo_map_parser/rand256_handler.py index d1df01c..8a68f09 100644 --- a/SCR/valetudo_map_parser/rand256_handler.py +++ b/SCR/valetudo_map_parser/rand256_handler.py @@ -15,6 +15,7 @@ from .config.async_utils import AsyncPIL from .config.drawable_elements import DrawableElement +from .const import COLORS, DEFAULT_IMAGE_SIZE, DEFAULT_PIXEL_SIZE from .config.types import ( LOGGER, Colors, @@ -542,4 +543,4 @@ def get_calibration_data(self, rotation_angle: int = 0) -> Any: calibration_point = {"vacuum": vacuum_point, "map": map_point} self.calibration_data.append(calibration_point) - return self.calibration_data + return self.calibration_data \ No newline at end of file From 0d4f63c615dbe43bd0c2fd80ac26f29fbecf4bca Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:17:49 +0100 Subject: [PATCH 05/14] minor changes in rand256_handler.py, shared.py removed snapshot at init bump version in pyproject.toml added to __init__.py Trims and Floor Data Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/__init__.py | 4 +++- SCR/valetudo_map_parser/config/shared.py | 4 ---- SCR/valetudo_map_parser/rand256_handler.py | 2 +- pyproject.toml | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/SCR/valetudo_map_parser/__init__.py b/SCR/valetudo_map_parser/__init__.py index 37d3cd3..0d72b66 100644 --- a/SCR/valetudo_map_parser/__init__.py +++ b/SCR/valetudo_map_parser/__init__.py @@ -1,5 +1,5 @@ """Valetudo map parser. -Version: 0.1.13""" +Version: 0.1.14""" from pathlib import Path @@ -12,6 +12,7 @@ from .config.status_text.translations import translations as STATUS_TEXT_TRANSLATIONS from .config.types import ( CameraModes, + FloorData, ImageSize, JsonType, NumpyArray, @@ -20,6 +21,7 @@ RoomStore, SnapshotStore, TrimCropData, + TrimsData, UserLanguageStore, ) from .config.utils import ResizeParams, async_resize_image diff --git a/SCR/valetudo_map_parser/config/shared.py b/SCR/valetudo_map_parser/config/shared.py index 77443c5..b2257bd 100755 --- a/SCR/valetudo_map_parser/config/shared.py +++ b/SCR/valetudo_map_parser/config/shared.py @@ -311,10 +311,6 @@ def update_shared_data(self, device_info): instance.vacuum_status_position = device_info.get( CONF_VAC_STAT_POS, DEFAULT_VALUES["vac_status_position"] ) - # If enable_snapshots, check for png in www. - instance.enable_snapshots = device_info.get( - CONF_SNAPSHOTS_ENABLE, DEFAULT_VALUES["enable_www_snapshots"] - ) # Ensure trims are updated correctly trim_data = device_info.get("trims_data", DEFAULT_VALUES["trims_data"]) instance.trims = TrimsData.from_dict(trim_data) diff --git a/SCR/valetudo_map_parser/rand256_handler.py b/SCR/valetudo_map_parser/rand256_handler.py index 8a68f09..3ed2a74 100644 --- a/SCR/valetudo_map_parser/rand256_handler.py +++ b/SCR/valetudo_map_parser/rand256_handler.py @@ -543,4 +543,4 @@ def get_calibration_data(self, rotation_angle: int = 0) -> Any: calibration_point = {"vacuum": vacuum_point, "map": map_point} self.calibration_data.append(calibration_point) - return self.calibration_data \ No newline at end of file + return self.calibration_data diff --git a/pyproject.toml b/pyproject.toml index ecf107d..e2cb060 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "valetudo-map-parser" -version = "0.1.13" +version = "0.1.14" description = "A Python library to parse Valetudo map data returning a PIL Image object." authors = ["Sandro Cantarella "] license = "Apache-2.0" From c412fd7fb0f2dd8c9dbdcb20101e0c65a02e1fa8 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sun, 21 Dec 2025 09:29:15 +0100 Subject: [PATCH 06/14] updated __init__.py Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCR/__init__.py b/SCR/__init__.py index 2217ee2..ebdf05f 100644 --- a/SCR/__init__.py +++ b/SCR/__init__.py @@ -1,2 +1,2 @@ """Valetudo map parser. -Version: 0.1.10""" +Version: 0.1.14""" From 11c09e24c037e90e4d0e355195d2add3f0c70585 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sun, 21 Dec 2025 09:33:41 +0100 Subject: [PATCH 07/14] updated __init__.py all Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SCR/valetudo_map_parser/__init__.py b/SCR/valetudo_map_parser/__init__.py index 0d72b66..b0816d7 100644 --- a/SCR/valetudo_map_parser/__init__.py +++ b/SCR/valetudo_map_parser/__init__.py @@ -150,6 +150,7 @@ def get_default_font_path() -> str: "SENSOR_NO_DATA", # Classes and Handlers "CameraShared", + "FloorData", "CameraSharedManager", "ColorsManagement", "Drawable", @@ -172,6 +173,7 @@ def get_default_font_path() -> str: "RoomStore", "SnapshotStore", "TrimCropData", + "TrimsData", "UserLanguageStore", # Utilities "ResizeParams", From cb056f43243e8bd34480f0ac09a2e8fc36879852 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sun, 21 Dec 2025 11:02:18 +0100 Subject: [PATCH 08/14] updated __init__.py all Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCR/valetudo_map_parser/__init__.py b/SCR/valetudo_map_parser/__init__.py index b0816d7..7fb2898 100644 --- a/SCR/valetudo_map_parser/__init__.py +++ b/SCR/valetudo_map_parser/__init__.py @@ -150,7 +150,6 @@ def get_default_font_path() -> str: "SENSOR_NO_DATA", # Classes and Handlers "CameraShared", - "FloorData", "CameraSharedManager", "ColorsManagement", "Drawable", @@ -165,6 +164,7 @@ def get_default_font_path() -> str: "StatusText", # Types "CameraModes", + "FloorData", "ImageSize", "JsonType", "NumpyArray", From 4e216569dbd2cb808f65623ba4e630cf3c393443 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Wed, 24 Dec 2025 07:32:01 +0100 Subject: [PATCH 09/14] update shared.py to load floor data from the config if present else uses old key trims_data Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/config/shared.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/SCR/valetudo_map_parser/config/shared.py b/SCR/valetudo_map_parser/config/shared.py index b2257bd..03ebc5c 100755 --- a/SCR/valetudo_map_parser/config/shared.py +++ b/SCR/valetudo_map_parser/config/shared.py @@ -311,9 +311,20 @@ def update_shared_data(self, device_info): instance.vacuum_status_position = device_info.get( CONF_VAC_STAT_POS, DEFAULT_VALUES["vac_status_position"] ) - # Ensure trims are updated correctly - trim_data = device_info.get("trims_data", DEFAULT_VALUES["trims_data"]) - instance.trims = TrimsData.from_dict(trim_data) + # Check for new floors_data first + floors_data = device_info.get("floors_data", None) + current_floor = device_info.get("current_floor", "floor_0") # Default fallback + + if floors_data: + # NEW: Use floors_data + floor_trims = floors_data.get(current_floor, DEFAULT_VALUES["trims_data"]) + instance.trims = TrimsData.from_dict(floor_trims) + instance.current_floor = current_floor + else: + # OLD: Backward compatibility + trim_data = device_info.get("trims_data", DEFAULT_VALUES["trims_data"]) + instance.trims = TrimsData.from_dict(trim_data) + instance.current_floor = "floor_0" # Default # Robot size robot_size = device_info.get("robot_size", 25) try: From 0505a8c88ca4e6845d78d950ab258619c10a4c8b Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:47:21 +0100 Subject: [PATCH 10/14] update TrimsCropData to use the floors when init, else default values. Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/config/types.py | 10 +++++++--- SCR/valetudo_map_parser/config/utils.py | 4 +++- SCR/valetudo_map_parser/const.py | 2 +- pyproject.toml | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/SCR/valetudo_map_parser/config/types.py b/SCR/valetudo_map_parser/config/types.py index 79fec70..e0bbc8f 100644 --- a/SCR/valetudo_map_parser/config/types.py +++ b/SCR/valetudo_map_parser/config/types.py @@ -63,7 +63,8 @@ class RoomProperty(TypedDict): @dataclass class TrimCropData: """Dataclass for trim and crop data.""" - + + floor: str trim_left: int trim_up: int trim_right: int @@ -72,6 +73,7 @@ class TrimCropData: def to_dict(self) -> dict: """Convert dataclass to dictionary.""" return { + "floor": self.floor, "trim_left": self.trim_left, "trim_up": self.trim_up, "trim_right": self.trim_right, @@ -82,6 +84,7 @@ def to_dict(self) -> dict: def from_dict(data: dict): """Create dataclass from dictionary.""" return TrimCropData( + floor=data["floor"], trim_left=data["trim_left"], trim_up=data["trim_up"], trim_right=data["trim_right"], @@ -93,9 +96,10 @@ def to_list(self) -> list: return [self.trim_left, self.trim_up, self.trim_right, self.trim_down] @staticmethod - def from_list(data: list): - """Create dataclass from list.""" + def from_list(data: list, floor: Optional[str] = "floor_0"): + """Create dataclass from list [trim_left, trim_up, trim_right, trim_down].""" return TrimCropData( + floor=floor, trim_left=data[0], trim_up=data[1], trim_right=data[2], diff --git a/SCR/valetudo_map_parser/config/utils.py b/SCR/valetudo_map_parser/config/utils.py index 74e93c3..2830a39 100644 --- a/SCR/valetudo_map_parser/config/utils.py +++ b/SCR/valetudo_map_parser/config/utils.py @@ -275,7 +275,9 @@ def prepare_resize_params( def update_trims(self) -> None: """Update the trims.""" - self.shared.trims = TrimsData.from_list(self.crop_area) + self.shared.trims = TrimsData.from_list( + self.crop_area, floor=self.shared.current_floor + ) def get_charger_position(self) -> ChargerPosition | None: """Return the charger position.""" diff --git a/SCR/valetudo_map_parser/const.py b/SCR/valetudo_map_parser/const.py index 2506135..1a47661 100644 --- a/SCR/valetudo_map_parser/const.py +++ b/SCR/valetudo_map_parser/const.py @@ -83,7 +83,7 @@ "vac_status_position": True, "get_svg_file": False, "save_trims": True, - "trims_data": {"trim_left": 0, "trim_up": 0, "trim_right": 0, "trim_down": 0}, + "trims_data": {"floor": "floor_0", "trim_left": 0, "trim_up": 0, "trim_right": 0, "trim_down": 0}, "enable_www_snapshots": False, "color_charger": [255, 128, 0], "color_move": [238, 247, 255], diff --git a/pyproject.toml b/pyproject.toml index e2cb060..5dc3f5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "valetudo-map-parser" -version = "0.1.14" +version = "0.1.15" description = "A Python library to parse Valetudo map data returning a PIL Image object." authors = ["Sandro Cantarella "] license = "Apache-2.0" @@ -18,7 +18,7 @@ python = ">=3.13" numpy = ">=1.26.4" Pillow = ">=10.3.0" scipy = ">=1.12.0" -mvcrender = "==0.0.6" +mvcrender = "==0.0.7" [tool.poetry.group.dev.dependencies] ruff = "*" From 3b9d5b23540b596f25a80da0629565c73ab11acd Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sun, 11 Jan 2026 15:50:24 +0100 Subject: [PATCH 11/14] feat: Add carpet zone detection and rendering support - Add CARPET drawable element with configurable color and alpha - Implement carpet zone parsing from Valetudo JSON (PolygonMapEntity) - Add carpet rendering with customizable color (default: 50% of room_0) - Support disable_carpets flag to toggle carpet visibility - Fix COLORS list order to match base_color_keys mapping - Fix floor rendering to use correct MAP_BACKGROUND color - Add carpet color configuration to device_info Fixes color index mismatch that affected carpet and floor color updates Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/config/colors.py | 6 ++ .../config/drawable_elements.py | 4 + SCR/valetudo_map_parser/config/shared.py | 1 - SCR/valetudo_map_parser/const.py | 6 ++ SCR/valetudo_map_parser/hypfer_draw.py | 76 +++++++++++++++++-- SCR/valetudo_map_parser/hypfer_handler.py | 4 +- SCR/valetudo_map_parser/map_data.py | 38 +++++++++- 7 files changed, 123 insertions(+), 12 deletions(-) diff --git a/SCR/valetudo_map_parser/config/colors.py b/SCR/valetudo_map_parser/config/colors.py index 6640336..904da4d 100644 --- a/SCR/valetudo_map_parser/config/colors.py +++ b/SCR/valetudo_map_parser/config/colors.py @@ -10,6 +10,7 @@ from ..const import ( ALPHA_BACKGROUND, ALPHA_CHARGER, + ALPHA_CARPET, ALPHA_GO_TO, ALPHA_MOVE, ALPHA_NO_GO, @@ -35,6 +36,7 @@ ALPHA_ZONE_CLEAN, COLOR_BACKGROUND, COLOR_CHARGER, + COLOR_CARPET, COLOR_GO_TO, COLOR_MOVE, COLOR_NO_GO, @@ -64,6 +66,7 @@ color_transparent = (0, 0, 0, 0) color_charger = (0, 128, 0, 255) +color_carpet = (67, 103, 125, 255) color_move = (238, 247, 255, 255) color_robot = (255, 255, 204, 255) color_no_go = (255, 0, 0, 255) @@ -149,6 +152,7 @@ class SupportedColor(StrEnum): GO_TO = "color_go_to" NO_GO = "color_no_go" ZONE_CLEAN = "color_zone_clean" + CARPET = "color_carpet" MAP_BACKGROUND = "color_background" TEXT = "color_text" TRANSPARENT = "color_transparent" @@ -171,6 +175,7 @@ class DefaultColors: SupportedColor.GO_TO: (0, 255, 0), SupportedColor.NO_GO: (255, 0, 0), SupportedColor.ZONE_CLEAN: (255, 255, 255), + SupportedColor.CARPET: (67, 103, 125), # 50% of room_0 default color (135, 206, 250) SupportedColor.MAP_BACKGROUND: (0, 125, 255), SupportedColor.TEXT: (0, 0, 0), SupportedColor.TRANSPARENT: (0, 0, 0), @@ -301,6 +306,7 @@ def set_initial_colours(self, device_info: dict) -> None: (COLOR_BACKGROUND, color_background, ALPHA_BACKGROUND), (COLOR_MOVE, color_move, ALPHA_MOVE), (COLOR_CHARGER, color_charger, ALPHA_CHARGER), + (COLOR_CARPET, color_carpet, ALPHA_CARPET), (COLOR_NO_GO, color_no_go, ALPHA_NO_GO), (COLOR_GO_TO, color_go_to, ALPHA_GO_TO), (COLOR_TEXT, color_text, ALPHA_TEXT), diff --git a/SCR/valetudo_map_parser/config/drawable_elements.py b/SCR/valetudo_map_parser/config/drawable_elements.py index f15dbc2..23bf60e 100644 --- a/SCR/valetudo_map_parser/config/drawable_elements.py +++ b/SCR/valetudo_map_parser/config/drawable_elements.py @@ -33,6 +33,7 @@ class DrawableElement(IntEnum): PATH = 9 PREDICTED_PATH = 10 GO_TO_TARGET = 11 + CARPET = 12 # Rooms (101-115 for up to 15 rooms) ROOM_1 = 101 @@ -94,6 +95,7 @@ def _set_default_properties(self): DrawableElement.GO_TO_TARGET: SupportedColor.GO_TO, DrawableElement.NO_MOP_AREA: SupportedColor.NO_GO, # Using NO_GO for no-mop areas DrawableElement.OBSTACLE: SupportedColor.NO_GO, # Using NO_GO for obstacles + DrawableElement.CARPET: SupportedColor.CARPET, } # Set z-index for each element type @@ -105,6 +107,7 @@ def _set_default_properties(self): DrawableElement.VIRTUAL_WALL: 30, DrawableElement.RESTRICTED_AREA: 25, DrawableElement.NO_MOP_AREA: 25, + DrawableElement.CARPET: 15, # Draw carpets above floor but below walls DrawableElement.OBSTACLE: 15, DrawableElement.PATH: 35, DrawableElement.PREDICTED_PATH: 34, @@ -265,6 +268,7 @@ def update_from_device_info(self, device_info: dict) -> None: "disable_virtual_walls": DrawableElement.VIRTUAL_WALL, "disable_restricted_areas": DrawableElement.RESTRICTED_AREA, "disable_no_mop_areas": DrawableElement.NO_MOP_AREA, + "disable_carpets": DrawableElement.CARPET, "disable_obstacles": DrawableElement.OBSTACLE, "disable_path": DrawableElement.PATH, "disable_predicted_path": DrawableElement.PREDICTED_PATH, diff --git a/SCR/valetudo_map_parser/config/shared.py b/SCR/valetudo_map_parser/config/shared.py index 03ebc5c..b106bb2 100755 --- a/SCR/valetudo_map_parser/config/shared.py +++ b/SCR/valetudo_map_parser/config/shared.py @@ -32,7 +32,6 @@ CONF_OFFSET_LEFT, CONF_OFFSET_RIGHT, CONF_OFFSET_TOP, - CONF_SNAPSHOTS_ENABLE, CONF_VAC_STAT, CONF_VAC_STAT_FONT, CONF_VAC_STAT_POS, diff --git a/SCR/valetudo_map_parser/const.py b/SCR/valetudo_map_parser/const.py index 1a47661..6c6508d 100644 --- a/SCR/valetudo_map_parser/const.py +++ b/SCR/valetudo_map_parser/const.py @@ -40,8 +40,10 @@ "background", "move", "charger", + "carpet", "no_go", "go_to", + "text", ] SENSOR_NO_DATA = { @@ -92,6 +94,7 @@ "color_go_to": [0, 255, 0], "color_no_go": [255, 0, 0], "color_zone_clean": [255, 255, 255], + "color_carpet": [67, 103, 125], "color_background": [0, 125, 255], "color_text": [255, 255, 255], "alpha_charger": 255.0, @@ -101,6 +104,7 @@ "alpha_go_to": 255.0, "alpha_no_go": 125.0, "alpha_zone_clean": 125.0, + "alpha_carpet": 255.0, "alpha_background": 255.0, "alpha_text": 255.0, "color_room_0": [135, 206, 250], @@ -229,6 +233,7 @@ """Base Colours RGB""" COLOR_CHARGER = "color_charger" +COLOR_CARPET = "color_carpet" COLOR_MOVE = "color_move" COLOR_ROBOT = "color_robot" COLOR_NO_GO = "color_no_go" @@ -258,6 +263,7 @@ """Alpha for RGBA Colours""" ALPHA_CHARGER = "alpha_charger" +ALPHA_CARPET = "alpha_carpet" ALPHA_MOVE = "alpha_move" ALPHA_ROBOT = "alpha_robot" ALPHA_NO_GO = "alpha_no_go" diff --git a/SCR/valetudo_map_parser/hypfer_draw.py b/SCR/valetudo_map_parser/hypfer_draw.py index 183b60a..9f7204b 100755 --- a/SCR/valetudo_map_parser/hypfer_draw.py +++ b/SCR/valetudo_map_parser/hypfer_draw.py @@ -94,8 +94,6 @@ async def _process_room_layer( # The room_id is 0-based, but DrawableElement.ROOM_x is 1-based current_room_id = room_id + 1 if 1 <= current_room_id <= 15: - # Use the DrawableElement imported at the top of the file - room_element = getattr(DrawableElement, f"ROOM_{current_room_id}", None) if room_element and hasattr(self.img_h.drawing_config, "is_enabled"): draw_room = self.img_h.drawing_config.is_enabled(room_element) @@ -104,7 +102,11 @@ async def _process_room_layer( room_color = self.img_h.shared.rooms_colors[room_id] try: - if layer_type == "segment": + if layer_type == "floor": + # Floor layers always use color_room_0 (rooms_colors[0]) + # This ensures consistency across different vacuum models + room_color = self.img_h.shared.rooms_colors[0] + elif layer_type == "segment": room_color = self._get_active_room_color( room_id, room_color, color_zone_clean ) @@ -115,8 +117,9 @@ async def _process_room_layer( img_np_array, pixels, pixel_size, room_color ) - # Always increment the room_id, even if the room is not drawn - room_id = (room_id + 1) % 16 # Cycle room_id back to 0 after 15 + # Increment room_id only for segment layers, not for floor layers + if layer_type == "segment": + room_id = (room_id + 1) % 16 # Cycle room_id back to 0 after 15 except IndexError as e: _LOGGER.warning("%s: Image Draw Error: %s", self.file_name, str(e)) @@ -262,6 +265,7 @@ async def async_draw_zones( np_array: NumpyArray, color_zone_clean: Color, color_no_go: Color, + color_carpet: Color = None, ) -> NumpyArray: """Get the zone clean from the JSON data with parallel processing.""" @@ -295,6 +299,68 @@ async def async_draw_zones( np_array, no_mop_zones, color_no_go ) + # Carpet zones + carpet_zones = zone_clean.get("carpet") + if ( + carpet_zones + and color_carpet + and self.img_h.drawing_config.is_enabled(DrawableElement.CARPET) + ): + np_array = await self.img_h.draw.zones( + np_array, carpet_zones, color_carpet + ) + + return np_array + + async def _draw_carpets( + self, np_array: NumpyArray, carpet_zones: list + ) -> NumpyArray: + """Draw carpet zones with 50% of room color and alpha 255.""" + from .config.room_store import RoomStore + + # Get room store to map segment IDs to room indices + room_store = RoomStore(self.file_name) + room_keys = list(room_store.get_rooms().keys()) + + for carpet in carpet_zones: + try: + # Get the segment ID from carpet metadata + segment_id = carpet.get("metaData", {}).get("id") + if not segment_id: + continue + + # Find the room index for this segment ID + if segment_id in room_keys: + room_index = room_keys.index(segment_id) + else: + # Default to room 0 if segment ID not found + room_index = 0 + + # Get the room color + if room_index < len(self.img_h.shared.rooms_colors): + room_color = self.img_h.shared.rooms_colors[room_index] + else: + room_color = self.img_h.shared.rooms_colors[0] + + # Calculate carpet color: 50% of room color with alpha 255 + carpet_color = ( + room_color[0] // 2, + room_color[1] // 2, + room_color[2] // 2, + 255, # Full opacity + ) + + # Draw the carpet zone + np_array = await self.img_h.draw.zones( + np_array, [carpet], carpet_color + ) + + except (KeyError, TypeError, IndexError) as e: + _LOGGER.warning( + "%s: Error drawing carpet: %s", self.file_name, str(e) + ) + continue + return np_array async def async_draw_virtual_walls( diff --git a/SCR/valetudo_map_parser/hypfer_handler.py b/SCR/valetudo_map_parser/hypfer_handler.py index a125d4a..04d9a67 100644 --- a/SCR/valetudo_map_parser/hypfer_handler.py +++ b/SCR/valetudo_map_parser/hypfer_handler.py @@ -130,7 +130,7 @@ async def _draw_layer_if_enabled( if is_wall_layer and not self.drawing_config.is_enabled(DrawableElement.WALL): return room_id, img_np_array # Skip walls - # Draw the layer + # Draw the layer (floor layers are always drawn when present) room_id, img_np_array = await self.imd.async_draw_base_layer( img_np_array, compressed_pixels_list, @@ -231,7 +231,7 @@ async def _draw_dynamic_elements( """Draw dynamic elements like zones, paths, and go-to targets.""" if self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA): img_np_array = await self.imd.async_draw_zones( - m_json, img_np_array, colors["zone_clean"], colors["no_go"] + m_json, img_np_array, colors["zone_clean"], colors["no_go"], colors.get("carpet") ) if self.drawing_config.is_enabled(DrawableElement.GO_TO_TARGET): diff --git a/SCR/valetudo_map_parser/map_data.py b/SCR/valetudo_map_parser/map_data.py index 3c74d58..9722482 100755 --- a/SCR/valetudo_map_parser/map_data.py +++ b/SCR/valetudo_map_parser/map_data.py @@ -117,7 +117,22 @@ class PathMapEntity(TypedDict): metaData: dict[str, object] # flexible for now -Entity = PointMapEntity | PathMapEntity +class PolygonMeta(TypedDict, total=False): + """Metadata for polygon entities including ID.""" + + id: str + + +class PolygonMapEntity(TypedDict): + """Polygon-based map entity (zones, carpets, etc.).""" + + __class__: Literal["PolygonMapEntity"] + type: str + points: list[int] + metaData: NotRequired[PolygonMeta] + + +Entity = PointMapEntity | PathMapEntity | PolygonMapEntity # --- Top-level Map --- @@ -259,9 +274,24 @@ def find_layers( layer_type = json_obj.get("type") meta_data = json_obj.get("metaData") or {} if layer_type: - layer_dict.setdefault(layer_type, []).append( - json_obj.get("compressedPixels", []) - ) + # Get compressedPixels, or fall back to pixels if not present + compressed_pixels = json_obj.get("compressedPixels") + if compressed_pixels is None: + # If compressedPixels is missing, convert pixels array to compressed format + # pixels format: [x, y, x, y, ...] (pairs) + # compressedPixels format: [x, y, count, x, y, count, ...] (triplets) + pixels = json_obj.get("pixels", []) + if pixels: + # Convert pairs to triplets by adding count=1 for each pixel + compressed_pixels = [] + for i in range(0, len(pixels), 2): + if i + 1 < len(pixels): + compressed_pixels.extend([pixels[i], pixels[i + 1], 1]) + else: + compressed_pixels = [] + + layer_dict.setdefault(layer_type, []).append(compressed_pixels) + # Safely extract "active" flag if present and convertible to int if layer_type == "segment": try: From 3bea98ea33e739ac80753441bfda49e9782f02c1 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:04:30 +0100 Subject: [PATCH 12/14] feat: Add carpet zone detection and rendering support from_list Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/config/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCR/valetudo_map_parser/config/types.py b/SCR/valetudo_map_parser/config/types.py index e0bbc8f..1501a66 100644 --- a/SCR/valetudo_map_parser/config/types.py +++ b/SCR/valetudo_map_parser/config/types.py @@ -84,7 +84,7 @@ def to_dict(self) -> dict: def from_dict(data: dict): """Create dataclass from dictionary.""" return TrimCropData( - floor=data["floor"], + floor=data.get("floor", "floor_0"), trim_left=data["trim_left"], trim_up=data["trim_up"], trim_right=data["trim_right"], From de0effa9ec475f0442bc1156579bc15c71b398f1 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:12:48 +0100 Subject: [PATCH 13/14] fix: Add missing CARPET to element_color_mapping Fixes carpet color/alpha from device_info not being applied due to missing DrawableElement.CARPET entry in update_from_device_info(). Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/config/drawable_elements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SCR/valetudo_map_parser/config/drawable_elements.py b/SCR/valetudo_map_parser/config/drawable_elements.py index 23bf60e..8982654 100644 --- a/SCR/valetudo_map_parser/config/drawable_elements.py +++ b/SCR/valetudo_map_parser/config/drawable_elements.py @@ -216,6 +216,7 @@ def update_from_device_info(self, device_info: dict) -> None: DrawableElement.GO_TO_TARGET: SupportedColor.GO_TO, DrawableElement.NO_MOP_AREA: SupportedColor.NO_GO, DrawableElement.OBSTACLE: SupportedColor.NO_GO, + DrawableElement.CARPET: SupportedColor.CARPET, } # Update room colors from device info From 644b81e53e360db9e5401472a6830c9db9b0c3ad Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:23:00 +0100 Subject: [PATCH 14/14] Dead code removed. The _draw_carpets method was leftover from the initial implementation attempt and is not needed since we're using the simpler approach of passing color_carpet directly from device_info to the zones() drawing method. Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com> --- SCR/valetudo_map_parser/hypfer_draw.py | 51 -------------------------- pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 52 deletions(-) diff --git a/SCR/valetudo_map_parser/hypfer_draw.py b/SCR/valetudo_map_parser/hypfer_draw.py index 9f7204b..fcba079 100755 --- a/SCR/valetudo_map_parser/hypfer_draw.py +++ b/SCR/valetudo_map_parser/hypfer_draw.py @@ -312,57 +312,6 @@ async def async_draw_zones( return np_array - async def _draw_carpets( - self, np_array: NumpyArray, carpet_zones: list - ) -> NumpyArray: - """Draw carpet zones with 50% of room color and alpha 255.""" - from .config.room_store import RoomStore - - # Get room store to map segment IDs to room indices - room_store = RoomStore(self.file_name) - room_keys = list(room_store.get_rooms().keys()) - - for carpet in carpet_zones: - try: - # Get the segment ID from carpet metadata - segment_id = carpet.get("metaData", {}).get("id") - if not segment_id: - continue - - # Find the room index for this segment ID - if segment_id in room_keys: - room_index = room_keys.index(segment_id) - else: - # Default to room 0 if segment ID not found - room_index = 0 - - # Get the room color - if room_index < len(self.img_h.shared.rooms_colors): - room_color = self.img_h.shared.rooms_colors[room_index] - else: - room_color = self.img_h.shared.rooms_colors[0] - - # Calculate carpet color: 50% of room color with alpha 255 - carpet_color = ( - room_color[0] // 2, - room_color[1] // 2, - room_color[2] // 2, - 255, # Full opacity - ) - - # Draw the carpet zone - np_array = await self.img_h.draw.zones( - np_array, [carpet], carpet_color - ) - - except (KeyError, TypeError, IndexError) as e: - _LOGGER.warning( - "%s: Error drawing carpet: %s", self.file_name, str(e) - ) - continue - - return np_array - async def async_draw_virtual_walls( self, m_json: JsonType, np_array: NumpyArray, color_no_go: Color ) -> NumpyArray: diff --git a/pyproject.toml b/pyproject.toml index 5dc3f5a..428cf97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "valetudo-map-parser" -version = "0.1.15" +version = "0.1.16" description = "A Python library to parse Valetudo map data returning a PIL Image object." authors = ["Sandro Cantarella "] license = "Apache-2.0"