valetudo-map-parser 0.1.10rc6__py3-none-any.whl → 0.1.11b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- valetudo_map_parser/__init__.py +8 -10
- valetudo_map_parser/config/drawable.py +91 -329
- valetudo_map_parser/config/drawable_elements.py +0 -2
- valetudo_map_parser/config/rand256_parser.py +162 -44
- valetudo_map_parser/config/shared.py +30 -12
- valetudo_map_parser/config/status_text/status_text.py +1 -0
- valetudo_map_parser/config/types.py +12 -3
- valetudo_map_parser/config/utils.py +44 -136
- valetudo_map_parser/hypfer_draw.py +0 -2
- valetudo_map_parser/hypfer_handler.py +14 -22
- valetudo_map_parser/map_data.py +17 -11
- valetudo_map_parser/rand256_handler.py +79 -53
- valetudo_map_parser/reimg_draw.py +13 -18
- valetudo_map_parser/rooms_handler.py +10 -10
- {valetudo_map_parser-0.1.10rc6.dist-info → valetudo_map_parser-0.1.11b0.dist-info}/METADATA +2 -2
- valetudo_map_parser-0.1.11b0.dist-info/RECORD +32 -0
- valetudo_map_parser/config/auto_crop.py +0 -452
- valetudo_map_parser/config/color_utils.py +0 -105
- valetudo_map_parser/config/enhanced_drawable.py +0 -324
- valetudo_map_parser/hypfer_rooms_handler.py +0 -599
- valetudo_map_parser-0.1.10rc6.dist-info/RECORD +0 -36
- {valetudo_map_parser-0.1.10rc6.dist-info → valetudo_map_parser-0.1.11b0.dist-info}/WHEEL +0 -0
- {valetudo_map_parser-0.1.10rc6.dist-info → valetudo_map_parser-0.1.11b0.dist-info}/licenses/LICENSE +0 -0
- {valetudo_map_parser-0.1.10rc6.dist-info → valetudo_map_parser-0.1.11b0.dist-info}/licenses/NOTICE.txt +0 -0
@@ -1,32 +1,30 @@
|
|
1
1
|
"""Utility code for the valetudo map parser."""
|
2
2
|
|
3
3
|
import datetime
|
4
|
-
from time import time
|
5
4
|
import hashlib
|
5
|
+
import io
|
6
6
|
import json
|
7
7
|
from dataclasses import dataclass
|
8
|
+
from time import time
|
8
9
|
from typing import Callable, List, Optional, Tuple
|
9
|
-
import io
|
10
10
|
|
11
11
|
import numpy as np
|
12
12
|
from PIL import Image, ImageOps
|
13
13
|
|
14
|
+
from ..map_data import HyperMapData
|
15
|
+
from .async_utils import AsyncNumPy
|
14
16
|
from .drawable import Drawable
|
15
17
|
from .drawable_elements import DrawingConfig
|
16
|
-
from .enhanced_drawable import EnhancedDrawable
|
17
18
|
from .status_text.status_text import StatusText
|
18
|
-
|
19
19
|
from .types import (
|
20
20
|
LOGGER,
|
21
21
|
ChargerPosition,
|
22
|
-
|
22
|
+
Destinations,
|
23
23
|
NumpyArray,
|
24
24
|
PilPNG,
|
25
25
|
RobotPosition,
|
26
|
-
|
26
|
+
Size,
|
27
27
|
)
|
28
|
-
from ..map_data import HyperMapData
|
29
|
-
from .async_utils import AsyncNumPy
|
30
28
|
|
31
29
|
|
32
30
|
@dataclass
|
@@ -79,7 +77,6 @@ class BaseHandler:
|
|
79
77
|
# Drawing components are initialized by initialize_drawing_config in handlers
|
80
78
|
self.drawing_config: Optional[DrawingConfig] = None
|
81
79
|
self.draw: Optional[Drawable] = None
|
82
|
-
self.enhanced_draw: Optional[EnhancedDrawable] = None
|
83
80
|
|
84
81
|
def get_frame_number(self) -> int:
|
85
82
|
"""Return the frame number of the image."""
|
@@ -114,6 +111,12 @@ class BaseHandler:
|
|
114
111
|
try:
|
115
112
|
# Backup current image to last_image before processing new one
|
116
113
|
if hasattr(self.shared, "new_image") and self.shared.new_image is not None:
|
114
|
+
# Close old last_image to free memory before replacing it
|
115
|
+
if hasattr(self.shared, "last_image") and self.shared.last_image is not None:
|
116
|
+
try:
|
117
|
+
self.shared.last_image.close()
|
118
|
+
except Exception:
|
119
|
+
pass # Ignore errors if image is already closed
|
117
120
|
self.shared.last_image = self.shared.new_image
|
118
121
|
|
119
122
|
# Call the appropriate handler method based on handler type
|
@@ -174,13 +177,11 @@ class BaseHandler:
|
|
174
177
|
LOGGER.warning(
|
175
178
|
"%s: Failed to generate image from JSON data", self.file_name
|
176
179
|
)
|
177
|
-
if bytes_format and hasattr(self.shared, "last_image"):
|
178
|
-
return pil_to_png_bytes(self.shared.last_image), {}
|
179
180
|
return (
|
180
181
|
self.shared.last_image
|
181
182
|
if hasattr(self.shared, "last_image")
|
182
183
|
else None
|
183
|
-
),
|
184
|
+
), self.shared.to_dict()
|
184
185
|
|
185
186
|
except Exception as e:
|
186
187
|
LOGGER.warning(
|
@@ -197,26 +198,28 @@ class BaseHandler:
|
|
197
198
|
"""Update the shared data with the latest information."""
|
198
199
|
|
199
200
|
if hasattr(self, "get_rooms_attributes") and (
|
200
|
-
|
201
|
+
self.shared.map_rooms is None and destinations is not None
|
201
202
|
):
|
202
|
-
(
|
203
|
-
self.shared.map_rooms,
|
204
|
-
self.shared.map_pred_zones,
|
205
|
-
self.shared.map_pred_points,
|
206
|
-
) = await self.get_rooms_attributes(destinations)
|
203
|
+
self.shared.map_rooms = await self.get_rooms_attributes(destinations)
|
207
204
|
if self.shared.map_rooms:
|
208
205
|
LOGGER.debug("%s: Rand256 attributes rooms updated", self.file_name)
|
209
206
|
|
207
|
+
|
210
208
|
if hasattr(self, "async_get_rooms_attributes") and (
|
211
|
-
|
209
|
+
self.shared.map_rooms is None
|
212
210
|
):
|
213
211
|
if self.shared.map_rooms is None:
|
214
212
|
self.shared.map_rooms = await self.async_get_rooms_attributes()
|
215
213
|
if self.shared.map_rooms:
|
216
214
|
LOGGER.debug("%s: Hyper attributes rooms updated", self.file_name)
|
217
215
|
|
218
|
-
if
|
219
|
-
self
|
216
|
+
if (
|
217
|
+
hasattr(self, "get_calibration_data")
|
218
|
+
and self.shared.attr_calibration_points is None
|
219
|
+
):
|
220
|
+
self.shared.attr_calibration_points = self.get_calibration_data(
|
221
|
+
self.shared.image_rotate
|
222
|
+
)
|
220
223
|
|
221
224
|
if not self.shared.image_size:
|
222
225
|
self.shared.image_size = self.get_img_size()
|
@@ -228,14 +231,12 @@ class BaseHandler:
|
|
228
231
|
|
229
232
|
self.shared.current_room = self.get_robot_position()
|
230
233
|
|
231
|
-
def prepare_resize_params(
|
234
|
+
def prepare_resize_params(
|
235
|
+
self, pil_img: PilPNG, rand: bool = False
|
236
|
+
) -> ResizeParams:
|
232
237
|
"""Prepare resize parameters for image resizing."""
|
233
|
-
|
234
|
-
|
235
|
-
else:
|
236
|
-
height, width = pil_img.size
|
237
|
-
LOGGER.debug("Shared PIL image size: %s x %s", self.shared.image_ref_width,
|
238
|
-
self.shared.image_ref_height)
|
238
|
+
width, height = pil_size_rotation(self.shared.image_rotate, pil_img)
|
239
|
+
|
239
240
|
return ResizeParams(
|
240
241
|
pil_img=pil_img,
|
241
242
|
width=width,
|
@@ -656,9 +657,6 @@ class BaseHandler:
|
|
656
657
|
|
657
658
|
async def async_resize_image(params: ResizeParams):
|
658
659
|
"""Resize the image to the given dimensions and aspect ratio."""
|
659
|
-
LOGGER.debug("Resizing image to aspect ratio: %s", params.aspect_ratio)
|
660
|
-
LOGGER.debug("Original image size: %s x %s", params.width, params.height)
|
661
|
-
LOGGER.debug("Image crop size: %s", params.crop_size)
|
662
660
|
if params.aspect_ratio == "None":
|
663
661
|
return params.pil_img
|
664
662
|
if params.aspect_ratio != "None":
|
@@ -695,6 +693,17 @@ async def async_resize_image(params: ResizeParams):
|
|
695
693
|
return params.pil_img
|
696
694
|
|
697
695
|
|
696
|
+
def pil_size_rotation(image_rotate, pil_img):
|
697
|
+
"""Return the size of the image."""
|
698
|
+
if not pil_img:
|
699
|
+
return 0, 0
|
700
|
+
if image_rotate in [0, 180]:
|
701
|
+
width, height = pil_img.size
|
702
|
+
else:
|
703
|
+
height, width = pil_img.size
|
704
|
+
return width, height
|
705
|
+
|
706
|
+
|
698
707
|
def initialize_drawing_config(handler):
|
699
708
|
"""
|
700
709
|
Initialize drawing configuration from device_info.
|
@@ -703,7 +712,7 @@ def initialize_drawing_config(handler):
|
|
703
712
|
handler: The handler instance with shared data and file_name attributes
|
704
713
|
|
705
714
|
Returns:
|
706
|
-
Tuple of (DrawingConfig, Drawable
|
715
|
+
Tuple of (DrawingConfig, Drawable)
|
707
716
|
"""
|
708
717
|
|
709
718
|
# Initialize drawing configuration
|
@@ -715,98 +724,10 @@ def initialize_drawing_config(handler):
|
|
715
724
|
):
|
716
725
|
drawing_config.update_from_device_info(handler.shared.device_info)
|
717
726
|
|
718
|
-
# Initialize
|
719
|
-
draw = Drawable()
|
720
|
-
enhanced_draw = EnhancedDrawable(drawing_config) # New enhanced drawing system
|
721
|
-
|
722
|
-
return drawing_config, draw, enhanced_draw
|
727
|
+
# Initialize drawing utilities
|
728
|
+
draw = Drawable()
|
723
729
|
|
724
|
-
|
725
|
-
def blend_colors(base_color, overlay_color):
|
726
|
-
"""
|
727
|
-
Blend two RGBA colors using alpha compositing.
|
728
|
-
|
729
|
-
Args:
|
730
|
-
base_color: Base RGBA color tuple (r, g, b, a)
|
731
|
-
overlay_color: Overlay RGBA color tuple (r, g, b, a)
|
732
|
-
|
733
|
-
Returns:
|
734
|
-
Blended RGBA color tuple (r, g, b, a)
|
735
|
-
"""
|
736
|
-
r1, g1, b1, a1 = base_color
|
737
|
-
r2, g2, b2, a2 = overlay_color
|
738
|
-
|
739
|
-
# Convert alpha to 0-1 range
|
740
|
-
a1 = a1 / 255.0
|
741
|
-
a2 = a2 / 255.0
|
742
|
-
|
743
|
-
# Calculate resulting alpha
|
744
|
-
a_out = a1 + a2 * (1 - a1)
|
745
|
-
|
746
|
-
# Avoid division by zero
|
747
|
-
if a_out < 0.0001:
|
748
|
-
return [0, 0, 0, 0]
|
749
|
-
|
750
|
-
# Calculate blended RGB components
|
751
|
-
r_out = (r1 * a1 + r2 * a2 * (1 - a1)) / a_out
|
752
|
-
g_out = (g1 * a1 + g2 * a2 * (1 - a1)) / a_out
|
753
|
-
b_out = (b1 * a1 + b2 * a2 * (1 - a1)) / a_out
|
754
|
-
|
755
|
-
# Convert back to 0-255 range and return as tuple
|
756
|
-
return (
|
757
|
-
int(max(0, min(255, r_out))),
|
758
|
-
int(max(0, min(255, g_out))),
|
759
|
-
int(max(0, min(255, b_out))),
|
760
|
-
int(max(0, min(255, a_out * 255))),
|
761
|
-
)
|
762
|
-
|
763
|
-
|
764
|
-
def blend_pixel(array, x, y, color, element, element_map=None, drawing_config=None):
|
765
|
-
"""
|
766
|
-
Blend a pixel color with the existing color at the specified position.
|
767
|
-
Also updates the element map if the new element has higher z-index.
|
768
|
-
|
769
|
-
Args:
|
770
|
-
array: The image array to modify
|
771
|
-
x: X coordinate
|
772
|
-
y: Y coordinate
|
773
|
-
color: RGBA color tuple to blend
|
774
|
-
element: Element code for the pixel
|
775
|
-
element_map: Optional element map to update
|
776
|
-
drawing_config: Optional drawing configuration for z-index lookup
|
777
|
-
|
778
|
-
Returns:
|
779
|
-
None
|
780
|
-
"""
|
781
|
-
# Check bounds
|
782
|
-
if not (0 <= y < array.shape[0] and 0 <= x < array.shape[1]):
|
783
|
-
return
|
784
|
-
|
785
|
-
# Get current element at this position
|
786
|
-
current_element = None
|
787
|
-
if element_map is not None:
|
788
|
-
current_element = element_map[y, x]
|
789
|
-
|
790
|
-
# Get z-index values for comparison
|
791
|
-
current_z = 0
|
792
|
-
new_z = 0
|
793
|
-
|
794
|
-
if drawing_config is not None:
|
795
|
-
current_z = (
|
796
|
-
drawing_config.get_property(current_element, "z_index", 0)
|
797
|
-
if current_element
|
798
|
-
else 0
|
799
|
-
)
|
800
|
-
new_z = drawing_config.get_property(element, "z_index", 0)
|
801
|
-
|
802
|
-
# Update element map if new element has higher z-index
|
803
|
-
if element_map is not None and new_z >= current_z:
|
804
|
-
element_map[y, x] = element
|
805
|
-
|
806
|
-
# Blend colors
|
807
|
-
base_color = array[y, x]
|
808
|
-
blended_color = blend_colors(base_color, color)
|
809
|
-
array[y, x] = blended_color
|
730
|
+
return drawing_config, draw
|
810
731
|
|
811
732
|
|
812
733
|
def manage_drawable_elements(
|
@@ -990,12 +911,6 @@ async def async_extract_room_outline(
|
|
990
911
|
|
991
912
|
# If we found too few boundary points, use the rectangular outline
|
992
913
|
if len(boundary_points) < 8: # Need at least 8 points for a meaningful shape
|
993
|
-
LOGGER.debug(
|
994
|
-
"%s: Room %s has too few boundary points (%d), using rectangular outline",
|
995
|
-
file_name,
|
996
|
-
str(room_id_int),
|
997
|
-
len(boundary_points),
|
998
|
-
)
|
999
914
|
return rect_outline
|
1000
915
|
|
1001
916
|
# Use a more sophisticated algorithm to create a coherent outline
|
@@ -1031,13 +946,6 @@ async def async_extract_room_outline(
|
|
1031
946
|
# Convert NumPy int64 values to regular Python integers
|
1032
947
|
simplified_outline = [(int(x), int(y)) for x, y in simplified_outline]
|
1033
948
|
|
1034
|
-
LOGGER.debug(
|
1035
|
-
"%s: Room %s outline has %d points",
|
1036
|
-
file_name,
|
1037
|
-
str(room_id_int),
|
1038
|
-
len(simplified_outline),
|
1039
|
-
)
|
1040
|
-
|
1041
949
|
return simplified_outline
|
1042
950
|
|
1043
951
|
except (ValueError, IndexError, TypeError, ArithmeticError) as e:
|
@@ -269,8 +269,6 @@ class ImageDraw:
|
|
269
269
|
zone_clean = self.img_h.data.find_zone_entities(m_json)
|
270
270
|
except (ValueError, KeyError):
|
271
271
|
zone_clean = None
|
272
|
-
else:
|
273
|
-
_LOGGER.info("%s: Got zones.", self.file_name)
|
274
272
|
|
275
273
|
if zone_clean:
|
276
274
|
# Process zones sequentially to avoid memory-intensive array copies
|
@@ -8,25 +8,22 @@ Version: 0.1.10
|
|
8
8
|
from __future__ import annotations
|
9
9
|
|
10
10
|
import asyncio
|
11
|
-
import numpy as np
|
12
11
|
|
12
|
+
import numpy as np
|
13
|
+
from mvcrender.autocrop import AutoCrop
|
13
14
|
from PIL import Image
|
14
15
|
|
15
16
|
from .config.async_utils import AsyncPIL
|
16
|
-
|
17
|
-
# from .config.auto_crop import AutoCrop
|
18
|
-
from mvcrender.autocrop import AutoCrop
|
19
17
|
from .config.drawable_elements import DrawableElement
|
20
18
|
from .config.shared import CameraShared
|
21
|
-
|
22
19
|
from .config.types import (
|
23
20
|
COLORS,
|
24
21
|
LOGGER,
|
25
22
|
CalibrationPoints,
|
26
23
|
Colors,
|
24
|
+
JsonType,
|
27
25
|
RoomsProperties,
|
28
26
|
RoomStore,
|
29
|
-
JsonType,
|
30
27
|
)
|
31
28
|
from .config.utils import (
|
32
29
|
BaseHandler,
|
@@ -49,9 +46,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
49
46
|
self.calibration_data = None # camera shared data.
|
50
47
|
self.data = ImageData # imported Image Data Module.
|
51
48
|
# Initialize drawing configuration using the shared utility function
|
52
|
-
self.drawing_config, self.draw
|
53
|
-
self
|
54
|
-
)
|
49
|
+
self.drawing_config, self.draw = initialize_drawing_config(self)
|
55
50
|
|
56
51
|
self.go_to = None # vacuum go to data
|
57
52
|
self.img_hash = None # hash of the image calculated to check differences.
|
@@ -60,7 +55,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
60
55
|
None # persistent working buffer to avoid per-frame allocations
|
61
56
|
)
|
62
57
|
self.active_zones = [] # vacuum active zones.
|
63
|
-
self.svg_wait = False # SVG image creation wait.
|
64
58
|
self.imd = ImDraw(self) # Image Draw class.
|
65
59
|
self.color_grey = (128, 128, 128, 255)
|
66
60
|
self.file_name = self.shared.file_name # file name of the vacuum.
|
@@ -79,7 +73,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
79
73
|
json_data
|
80
74
|
)
|
81
75
|
if room_properties:
|
82
|
-
|
76
|
+
_ = RoomStore(self.file_name, room_properties)
|
83
77
|
# Convert room_properties to the format expected by async_get_robot_in_room
|
84
78
|
self.rooms_pos = []
|
85
79
|
for room_id, room_data in room_properties.items():
|
@@ -260,7 +254,12 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
260
254
|
)
|
261
255
|
LOGGER.info("%s: Completed base Layers", self.file_name)
|
262
256
|
# Copy the new array in base layer.
|
257
|
+
# Delete old base layer before creating new one to free memory
|
258
|
+
if self.img_base_layer is not None:
|
259
|
+
del self.img_base_layer
|
263
260
|
self.img_base_layer = await self.async_copy_array(img_np_array)
|
261
|
+
# Delete source array after copying to free memory
|
262
|
+
del img_np_array
|
264
263
|
|
265
264
|
self.shared.frame_number = self.frame_number
|
266
265
|
self.frame_number += 1
|
@@ -274,6 +273,9 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
274
273
|
or self.img_work_layer.shape != self.img_base_layer.shape
|
275
274
|
or self.img_work_layer.dtype != self.img_base_layer.dtype
|
276
275
|
):
|
276
|
+
# Delete old buffer before creating new one to free memory
|
277
|
+
if self.img_work_layer is not None:
|
278
|
+
del self.img_work_layer
|
277
279
|
self.img_work_layer = np.empty_like(self.img_base_layer)
|
278
280
|
|
279
281
|
# Copy the base layer into the persistent working buffer (no new allocation per frame)
|
@@ -348,21 +350,11 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
348
350
|
robot_state=self.shared.vacuum_state,
|
349
351
|
)
|
350
352
|
|
351
|
-
# Update element map for robot position
|
352
|
-
if (
|
353
|
-
hasattr(self.shared, "element_map")
|
354
|
-
and self.shared.element_map is not None
|
355
|
-
):
|
356
|
-
update_element_map_with_robot(
|
357
|
-
self.shared.element_map,
|
358
|
-
robot_position,
|
359
|
-
DrawableElement.ROBOT,
|
360
|
-
)
|
361
353
|
# Synchronize zooming state from ImageDraw to handler before auto-crop
|
362
354
|
self.zooming = self.imd.img_h.zooming
|
363
355
|
|
364
356
|
# Resize the image
|
365
|
-
img_np_array = self.
|
357
|
+
img_np_array = self.auto_trim_and_zoom_image(
|
366
358
|
img_np_array,
|
367
359
|
colors["background"],
|
368
360
|
int(self.shared.margins),
|
valetudo_map_parser/map_data.py
CHANGED
@@ -8,22 +8,22 @@ Version: v0.1.10
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
-
import
|
11
|
+
from dataclasses import asdict, dataclass, field
|
12
12
|
from typing import (
|
13
|
-
List,
|
14
|
-
Sequence,
|
15
|
-
TypeVar,
|
16
13
|
Any,
|
17
|
-
TypedDict,
|
18
|
-
NotRequired,
|
19
14
|
Literal,
|
15
|
+
NotRequired,
|
20
16
|
Optional,
|
17
|
+
Sequence,
|
18
|
+
TypedDict,
|
19
|
+
TypeVar,
|
21
20
|
)
|
22
21
|
|
23
|
-
|
22
|
+
import numpy as np
|
24
23
|
|
25
24
|
from .config.types import ImageSize, JsonType
|
26
25
|
|
26
|
+
|
27
27
|
T = TypeVar("T")
|
28
28
|
|
29
29
|
# --- Common Nested Structures ---
|
@@ -373,6 +373,11 @@ class ImageData:
|
|
373
373
|
Else:
|
374
374
|
(min_x_mm, min_y_mm, max_x_mm, max_y_mm)
|
375
375
|
"""
|
376
|
+
|
377
|
+
def to_mm(coord):
|
378
|
+
"""Convert pixel coordinates to millimeters."""
|
379
|
+
return round(coord * pixel_size * 10)
|
380
|
+
|
376
381
|
if not pixels:
|
377
382
|
raise ValueError("Pixels list cannot be empty.")
|
378
383
|
|
@@ -393,7 +398,6 @@ class ImageData:
|
|
393
398
|
min_y = min(min_y, y)
|
394
399
|
|
395
400
|
if rand:
|
396
|
-
to_mm = lambda v: v * pixel_size * 10
|
397
401
|
return (to_mm(max_x), to_mm(max_y)), (to_mm(min_x), to_mm(min_y))
|
398
402
|
|
399
403
|
return (
|
@@ -539,16 +543,18 @@ class RandImageData:
|
|
539
543
|
return None
|
540
544
|
|
541
545
|
@staticmethod
|
542
|
-
def get_rrm_currently_cleaned_zones(json_data: JsonType) -> dict:
|
546
|
+
def get_rrm_currently_cleaned_zones(json_data: JsonType) -> list[dict[str, Any]]:
|
543
547
|
"""Get the currently cleaned zones from the json."""
|
544
548
|
re_zones = json_data.get("currently_cleaned_zones", [])
|
545
549
|
formatted_zones = RandImageData._rrm_valetudo_format_zone(re_zones)
|
546
550
|
return formatted_zones
|
547
551
|
|
548
552
|
@staticmethod
|
549
|
-
def get_rrm_forbidden_zones(json_data: JsonType) -> dict:
|
553
|
+
def get_rrm_forbidden_zones(json_data: JsonType) -> list[dict[str, Any]]:
|
550
554
|
"""Get the forbidden zones from the json."""
|
551
|
-
re_zones = json_data.get("forbidden_zones", [])
|
555
|
+
re_zones = json_data.get("forbidden_zones", []) + json_data.get(
|
556
|
+
"forbidden_mop_zones", []
|
557
|
+
)
|
552
558
|
formatted_zones = RandImageData._rrm_valetudo_format_zone(re_zones)
|
553
559
|
return formatted_zones
|
554
560
|
|