From 40ef8f505253d44818b682a1559f9bb4757420ed Mon Sep 17 00:00:00 2001 From: Yoni Revah Date: Sun, 21 Jun 2026 01:30:22 -0400 Subject: [PATCH 1/5] Add low lying area map editing --- .../dreame_vacuum/dreame/device.py | 4 +- custom_components/dreame_vacuum/dreame/map.py | 60 ++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/custom_components/dreame_vacuum/dreame/device.py b/custom_components/dreame_vacuum/dreame/device.py index 7234961c..321be34c 100644 --- a/custom_components/dreame_vacuum/dreame/device.py +++ b/custom_components/dreame_vacuum/dreame/device.py @@ -6521,13 +6521,13 @@ def set_low_lying_area(self, areas=[]) -> dict[str, Any] | None: areas = [] if self._map_manager: - if self.status.current_map and self.status.current_map.low_lying_areas is None: + if not self.capability.low_lying_areas: raise InvalidActionException("Low lying areas are not supported on this device") if self.status.current_map and not self.status.has_saved_map: raise InvalidActionException("Cannot edit low lying areas on current map") - raise InvalidActionException("Low lying area editing not supported yet!") + return self.update_map_data_async(self._map_manager.editor.set_low_lying_areas(areas)) def set_predefined_points(self, points=[]) -> dict[str, Any] | None: """Set predefined points on current saved map.""" diff --git a/custom_components/dreame_vacuum/dreame/map.py b/custom_components/dreame_vacuum/dreame/map.py index 5706dd6f..7ccc821c 100644 --- a/custom_components/dreame_vacuum/dreame/map.py +++ b/custom_components/dreame_vacuum/dreame/map.py @@ -2541,7 +2541,65 @@ def set_curtains(self, curtains) -> None: self.refresh_map(self._selected_map_id) self.refresh_map() - def set_predefined_points(self, predefined_points) -> None: + def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: + map_data = self._map_data + if not map_data or not self._selected_map_id: + return + + low_lying_areas = [] + for index, area in enumerate(areas or [], 1): + x_coords = sorted([area[0], area[2]]) + y_coords = sorted([area[1], area[3]]) + polygon = [ + x_coords[0], + y_coords[0], + x_coords[1], + y_coords[0], + x_coords[1], + y_coords[1], + x_coords[0], + y_coords[1], + ] + low_lying_areas.append( + Polygon( + index, + x_coords[0], + y_coords[0], + x_coords[1], + y_coords[0], + x_coords[1], + y_coords[1], + x_coords[0], + y_coords[1], + polygon, + 1, + ) + ) + + map_data.low_lying_areas = low_lying_areas + + self._set_updated_frame_id(map_data.frame_id) + if ( + self._saved_map_data + and self._selected_map_id is not None + and self._selected_map_id in self._saved_map_data + ): + self._saved_map_data[self._selected_map_id].low_lying_areas = map_data.low_lying_areas + self.refresh_map(self._selected_map_id) + self.refresh_map() + + return { + "sneak_areas": [ + { + "id": area.id, + "roi": area.polygon, + "type": area.type, + } + for area in map_data.low_lying_areas + ] + } + + def set_predefined_points(self, predefined_points) -> None: map_data = self._map_data if not map_data or not self._selected_map_id or map_data.predefined_points is None: return From ddb18c352dd7f43e3a5e8f906991ed115c1b6c86 Mon Sep 17 00:00:00 2001 From: Yoni Revah Date: Sun, 21 Jun 2026 01:40:31 -0400 Subject: [PATCH 2/5] Persist low lying area payload key --- custom_components/dreame_vacuum/dreame/map.py | 32 ++++++++++++------- .../dreame_vacuum/dreame/types.py | 3 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/custom_components/dreame_vacuum/dreame/map.py b/custom_components/dreame_vacuum/dreame/map.py index 7ccc821c..f90ddbc2 100644 --- a/custom_components/dreame_vacuum/dreame/map.py +++ b/custom_components/dreame_vacuum/dreame/map.py @@ -2577,6 +2577,7 @@ def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: ) map_data.low_lying_areas = low_lying_areas + low_lying_area_key = map_data.low_lying_area_key or "sneak_areas_end" self._set_updated_frame_id(map_data.frame_id) if ( @@ -2585,18 +2586,20 @@ def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: and self._selected_map_id in self._saved_map_data ): self._saved_map_data[self._selected_map_id].low_lying_areas = map_data.low_lying_areas + self._saved_map_data[self._selected_map_id].low_lying_area_key = low_lying_area_key self.refresh_map(self._selected_map_id) self.refresh_map() + areas_payload = [ + { + "id": area.id, + "roi": area.polygon, + "type": area.type, + } + for area in map_data.low_lying_areas + ] return { - "sneak_areas": [ - { - "id": area.id, - "roi": area.polygon, - "type": area.type, - } - for area in map_data.low_lying_areas - ] + low_lying_area_key: areas_payload, } def set_predefined_points(self, predefined_points) -> None: @@ -5398,10 +5401,15 @@ def parse_carpets(carpets): ) ) - low_lying_area_key = "sneak_areas_end" if "sneak_areas_end" in data_json else "sneak_areas" - low_lying_areas = ( - changes["sneak_areas"] if changes and "sneak_areas" in changes else data_json.get(low_lying_area_key) - ) + low_lying_area_key = "sneak_areas_end" if "sneak_areas_end" in data_json else "sneak_areas" + map_data.low_lying_area_key = low_lying_area_key + low_lying_areas = ( + changes.get(low_lying_area_key) + or changes.get("sneak_areas_end") + or changes.get("sneak_areas") + if changes + else data_json.get(low_lying_area_key) + ) if low_lying_areas and not map_data.low_lying_areas: map_data.low_lying_areas = [] diff --git a/custom_components/dreame_vacuum/dreame/types.py b/custom_components/dreame_vacuum/dreame/types.py index e3801db7..54b9eb72 100644 --- a/custom_components/dreame_vacuum/dreame/types.py +++ b/custom_components/dreame_vacuum/dreame/types.py @@ -4522,7 +4522,8 @@ def __init__(self) -> None: self.carpets: Optional[List[Carpet]] = None # Data json: vw.addcpt self.deleted_carpets: Optional[List[Carpet]] = None # Data json: vw.nocpt self.detected_carpets: Optional[List[Carpet]] = None # Data json: carpet_info - self.low_lying_areas: Optional[List[Polygon]] = None # Data json: sneak_areas or sneak_areas_end + self.low_lying_areas: Optional[List[Polygon]] = None # Data json: sneak_areas or sneak_areas_end + self.low_lying_area_key: Optional[str] = None # Data json: sneak_areas or sneak_areas_end self.cleaning_sequence: Optional[Dict[int, int]] = None # Data json: cleanareaorder self.mop_type: Optional[Dict[int, str]] = None # Data json: mop_type_with_order self.current_mop_type: Optional[Dict[int, str]] = None # Data json: moptype From 3f44c571b733ee3199ac9e4286e0374ed80e3fbb Mon Sep 17 00:00:00 2001 From: Yoni Revah Date: Sun, 21 Jun 2026 01:50:11 -0400 Subject: [PATCH 3/5] Include saved low lying area fields --- custom_components/dreame_vacuum/dreame/map.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/custom_components/dreame_vacuum/dreame/map.py b/custom_components/dreame_vacuum/dreame/map.py index f90ddbc2..d6180b5e 100644 --- a/custom_components/dreame_vacuum/dreame/map.py +++ b/custom_components/dreame_vacuum/dreame/map.py @@ -2550,6 +2550,7 @@ def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: for index, area in enumerate(areas or [], 1): x_coords = sorted([area[0], area[2]]) y_coords = sorted([area[1], area[3]]) + area_size = round(abs((x_coords[1] - x_coords[0]) * (y_coords[1] - y_coords[0])) / 1000000, 2) polygon = [ x_coords[0], y_coords[0], @@ -2573,6 +2574,9 @@ def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: y_coords[1], polygon, 1, + 0, + None, + area_size, ) ) @@ -2595,6 +2599,8 @@ def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: "id": area.id, "roi": area.polygon, "type": area.type, + "hide": area.hidden, + "area": area.area, } for area in map_data.low_lying_areas ] From cf05797e40316ed67b5d7c7413ea241891e00f59 Mon Sep 17 00:00:00 2001 From: Yoni Revah Date: Sun, 21 Jun 2026 02:08:45 -0400 Subject: [PATCH 4/5] Preserve low lying areas on saved map merge --- custom_components/dreame_vacuum/dreame/map.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/custom_components/dreame_vacuum/dreame/map.py b/custom_components/dreame_vacuum/dreame/map.py index d6180b5e..f28c11fa 100644 --- a/custom_components/dreame_vacuum/dreame/map.py +++ b/custom_components/dreame_vacuum/dreame/map.py @@ -908,11 +908,13 @@ def _add_map_data(self, partial_map: MapDataPartial) -> None: map_data.pixel_type = copy.deepcopy(saved_map_data.pixel_type) map_data.dimensions = copy.deepcopy(saved_map_data.dimensions) map_data.charger_position = copy.deepcopy(saved_map_data.charger_position) - map_data.no_go_areas = saved_map_data.no_go_areas - map_data.no_mopping_areas = saved_map_data.no_mopping_areas - map_data.virtual_walls = saved_map_data.virtual_walls - map_data.robot_position = None - map_data.docked = True + map_data.no_go_areas = saved_map_data.no_go_areas + map_data.no_mopping_areas = saved_map_data.no_mopping_areas + map_data.virtual_walls = saved_map_data.virtual_walls + map_data.low_lying_areas = saved_map_data.low_lying_areas + map_data.low_lying_area_key = saved_map_data.low_lying_area_key + map_data.robot_position = None + map_data.docked = True # map_data.restored_map = True map_data.path = None map_data.need_optimization = False @@ -2606,6 +2608,8 @@ def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: ] return { low_lying_area_key: areas_payload, + "sneak_areas": areas_payload, + "sneak_areas_end": areas_payload, } def set_predefined_points(self, predefined_points) -> None: @@ -4946,11 +4950,13 @@ def decode_map_data_from_partial(partial_map: MapDataPartial, rotation: int = 0) map_data.ramps = saved_map_data.ramps map_data.carpets = saved_map_data.carpets map_data.deleted_carpets = saved_map_data.deleted_carpets - map_data.detected_carpets = saved_map_data.detected_carpets - map_data.router_position = saved_map_data.router_position - map_data.curtains = saved_map_data.curtains - map_data.hidden_segments = saved_map_data.hidden_segments - map_data.predefined_points = saved_map_data.predefined_points + map_data.detected_carpets = saved_map_data.detected_carpets + map_data.router_position = saved_map_data.router_position + map_data.curtains = saved_map_data.curtains + map_data.low_lying_areas = saved_map_data.low_lying_areas + map_data.low_lying_area_key = saved_map_data.low_lying_area_key + map_data.hidden_segments = saved_map_data.hidden_segments + map_data.predefined_points = saved_map_data.predefined_points map_data.cleaning_sequence = saved_map_data.cleaning_sequence if map_data.cleaning_sequence is not None and map_data.version == 1: map_data.version = 2 @@ -5577,9 +5583,11 @@ def parse_carpets(carpets): if deleted_objects: if "ai_obstacle" in deleted_objects: map_data.deleted_obstacles = deleted_objects["ai_obstacle"] - if "sneak_areas" in deleted_objects: - map_data.deleted_low_lying_areas = deleted_objects["sneak_areas"] - if "rec_vw" in deleted_objects: + if "sneak_areas" in deleted_objects: + map_data.deleted_low_lying_areas = deleted_objects["sneak_areas"] + if "sneak_areas_end" in deleted_objects: + map_data.deleted_low_lying_areas = deleted_objects["sneak_areas_end"] + if "rec_vw" in deleted_objects: map_data.deleted_recommended_area_type = deleted_objects["rec_vw"] ## TODO: Handle this if not map_data.walls and walls_info and "storeys" in walls_info: From d837fade79a2e227f05f17b36fc3090323ef4b46 Mon Sep 17 00:00:00 2001 From: Yoni Revah Date: Mon, 22 Jun 2026 15:00:03 -0400 Subject: [PATCH 5/5] Match low lying area payload format --- custom_components/dreame_vacuum/dreame/map.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/custom_components/dreame_vacuum/dreame/map.py b/custom_components/dreame_vacuum/dreame/map.py index f28c11fa..652349ae 100644 --- a/custom_components/dreame_vacuum/dreame/map.py +++ b/custom_components/dreame_vacuum/dreame/map.py @@ -2583,7 +2583,7 @@ def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: ) map_data.low_lying_areas = low_lying_areas - low_lying_area_key = map_data.low_lying_area_key or "sneak_areas_end" + low_lying_area_key = map_data.low_lying_area_key or "sneak_areas" self._set_updated_frame_id(map_data.frame_id) if ( @@ -2602,15 +2602,10 @@ def set_low_lying_areas(self, areas) -> dict[str, list[dict[str, Any]]] | None: "roi": area.polygon, "type": area.type, "hide": area.hidden, - "area": area.area, } for area in map_data.low_lying_areas ] - return { - low_lying_area_key: areas_payload, - "sneak_areas": areas_payload, - "sneak_areas_end": areas_payload, - } + return {low_lying_area_key: areas_payload} def set_predefined_points(self, predefined_points) -> None: map_data = self._map_data