Skip to content
Merged
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
6 changes: 6 additions & 0 deletions SCR/valetudo_map_parser/config/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from ..const import (
ALPHA_BACKGROUND,
ALPHA_CHARGER,
ALPHA_CARPET,
ALPHA_GO_TO,
ALPHA_MOVE,
ALPHA_NO_GO,
Expand All @@ -35,6 +36,7 @@
ALPHA_ZONE_CLEAN,
COLOR_BACKGROUND,
COLOR_CHARGER,
COLOR_CARPET,
COLOR_GO_TO,
COLOR_MOVE,
COLOR_NO_GO,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand All @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
5 changes: 5 additions & 0 deletions SCR/valetudo_map_parser/config/drawable_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -213,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
Expand Down Expand Up @@ -265,6 +269,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,
Expand Down
1 change: 0 additions & 1 deletion SCR/valetudo_map_parser/config/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion SCR/valetudo_map_parser/config/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
6 changes: 6 additions & 0 deletions SCR/valetudo_map_parser/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
"background",
"move",
"charger",
"carpet",
"no_go",
"go_to",
"text",
]

SENSOR_NO_DATA = {
Expand Down Expand Up @@ -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,
Expand All @@ -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],
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
25 changes: 20 additions & 5 deletions SCR/valetudo_map_parser/hypfer_draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
)
Expand All @@ -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))
Expand Down Expand Up @@ -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."""

Expand Down Expand Up @@ -295,6 +299,17 @@ 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 async_draw_virtual_walls(
Expand Down
4 changes: 2 additions & 2 deletions SCR/valetudo_map_parser/hypfer_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand Down
38 changes: 34 additions & 4 deletions SCR/valetudo_map_parser/map_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---

Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <gsca075@gmail.com>"]
license = "Apache-2.0"
Expand Down