valetudo-map-parser 0.1.9b64__tar.gz → 0.1.9b67__tar.gz
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-0.1.9b64 → valetudo_map_parser-0.1.9b67}/PKG-INFO +1 -1
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/async_utils.py +1 -4
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/drawable.py +87 -6
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/hypfer_handler.py +9 -3
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/rand256_handler.py +40 -4
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/pyproject.toml +1 -1
- valetudo_map_parser-0.1.9b64/SCR/valetudo_map_parser/config/rand25_parser.py +0 -412
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/README.md +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/__init__.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/__init__.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/color_utils.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/colors.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/drawable_elements.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/enhanced_drawable.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/optimized_element_map.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/rand256_parser.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/shared.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/types.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/config/utils.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/hypfer_draw.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/hypfer_rooms_handler.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/map_data.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/py.typed +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/reimg_draw.py +0 -0
- {valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/rooms_handler.py +0 -0
@@ -11,10 +11,7 @@ from PIL import Image
|
|
11
11
|
|
12
12
|
async def make_async(func: Callable, *args, **kwargs) -> Any:
|
13
13
|
"""Convert a synchronous function to async by yielding control to the event loop."""
|
14
|
-
await asyncio.
|
15
|
-
result = func(*args, **kwargs)
|
16
|
-
await asyncio.sleep(0)
|
17
|
-
return result
|
14
|
+
return await asyncio.to_thread(func, *args, **kwargs)
|
18
15
|
|
19
16
|
|
20
17
|
class AsyncNumPy:
|
@@ -14,6 +14,7 @@ import logging
|
|
14
14
|
import math
|
15
15
|
import asyncio
|
16
16
|
import inspect
|
17
|
+
import threading
|
17
18
|
|
18
19
|
import numpy as np
|
19
20
|
from PIL import ImageDraw, ImageFont
|
@@ -26,6 +27,72 @@ from .types import Color, NumpyArray, PilPNG, Point, Tuple, Union
|
|
26
27
|
_LOGGER = logging.getLogger(__name__)
|
27
28
|
|
28
29
|
|
30
|
+
class ImageArrayPool:
|
31
|
+
"""Thread-safe memory pool for reusing image arrays to reduce allocation overhead."""
|
32
|
+
|
33
|
+
def __init__(self, max_arrays_per_size: int = 3):
|
34
|
+
self._pools = {} # {(width, height): [array1, array2, ...]}
|
35
|
+
self._lock = threading.Lock()
|
36
|
+
self._max_arrays_per_size = max_arrays_per_size
|
37
|
+
|
38
|
+
def get_array(self, width: int, height: int, background_color: Color) -> NumpyArray:
|
39
|
+
"""Get a reusable array or create a new one if none available."""
|
40
|
+
key = (width, height)
|
41
|
+
|
42
|
+
with self._lock:
|
43
|
+
if key in self._pools and self._pools[key]:
|
44
|
+
# Reuse existing array
|
45
|
+
array = self._pools[key].pop()
|
46
|
+
_LOGGER.debug("Reused array from pool for size %dx%d", width, height)
|
47
|
+
else:
|
48
|
+
# Create new array
|
49
|
+
array = np.empty((height, width, 4), dtype=np.uint8)
|
50
|
+
_LOGGER.debug("Created new array for size %dx%d", width, height)
|
51
|
+
|
52
|
+
# Fill with background color (outside lock for better performance)
|
53
|
+
array[:] = background_color
|
54
|
+
return array
|
55
|
+
|
56
|
+
def return_array(self, array: NumpyArray) -> None:
|
57
|
+
"""Return an array to the pool for reuse."""
|
58
|
+
if array is None:
|
59
|
+
return
|
60
|
+
|
61
|
+
height, width = array.shape[:2]
|
62
|
+
key = (width, height)
|
63
|
+
|
64
|
+
with self._lock:
|
65
|
+
if key not in self._pools:
|
66
|
+
self._pools[key] = []
|
67
|
+
|
68
|
+
# Only keep up to max_arrays_per_size arrays per size
|
69
|
+
if len(self._pools[key]) < self._max_arrays_per_size:
|
70
|
+
self._pools[key].append(array)
|
71
|
+
_LOGGER.debug("Returned array to pool for size %dx%d (pool size: %d)",
|
72
|
+
width, height, len(self._pools[key]))
|
73
|
+
else:
|
74
|
+
_LOGGER.debug("Pool full for size %dx%d, discarding array", width, height)
|
75
|
+
|
76
|
+
def clear_pool(self) -> None:
|
77
|
+
"""Clear all arrays from the pool."""
|
78
|
+
with self._lock:
|
79
|
+
total_arrays = sum(len(arrays) for arrays in self._pools.values())
|
80
|
+
self._pools.clear()
|
81
|
+
_LOGGER.debug("Cleared image array pool (%d arrays freed)", total_arrays)
|
82
|
+
|
83
|
+
def get_pool_stats(self) -> dict:
|
84
|
+
"""Get statistics about the current pool state."""
|
85
|
+
with self._lock:
|
86
|
+
stats = {}
|
87
|
+
for (width, height), arrays in self._pools.items():
|
88
|
+
stats[f"{width}x{height}"] = len(arrays)
|
89
|
+
return stats
|
90
|
+
|
91
|
+
|
92
|
+
# Global shared pool instance for both Hypfer and Rand256 handlers
|
93
|
+
_image_pool = ImageArrayPool()
|
94
|
+
|
95
|
+
|
29
96
|
class Drawable:
|
30
97
|
"""
|
31
98
|
Collection of drawing utility functions for the image handlers.
|
@@ -45,13 +112,27 @@ class Drawable:
|
|
45
112
|
async def create_empty_image(
|
46
113
|
width: int, height: int, background_color: Color
|
47
114
|
) -> NumpyArray:
|
48
|
-
"""Create the empty background image NumPy array.
|
115
|
+
"""Create the empty background image NumPy array using memory pool for better performance.
|
49
116
|
Background color is specified as an RGBA tuple.
|
50
|
-
Optimized: Uses
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
117
|
+
Optimized: Uses shared memory pool to reuse arrays and reduce allocation overhead."""
|
118
|
+
# Get array from shared pool (reuses memory when possible)
|
119
|
+
return _image_pool.get_array(width, height, background_color)
|
120
|
+
|
121
|
+
@staticmethod
|
122
|
+
def return_image_to_pool(image_array: NumpyArray) -> None:
|
123
|
+
"""Return an image array to the memory pool for reuse.
|
124
|
+
Call this when you're done with an image array to enable memory reuse."""
|
125
|
+
_image_pool.return_array(image_array)
|
126
|
+
|
127
|
+
@staticmethod
|
128
|
+
def get_pool_stats() -> dict:
|
129
|
+
"""Get statistics about the current memory pool state."""
|
130
|
+
return _image_pool.get_pool_stats()
|
131
|
+
|
132
|
+
@staticmethod
|
133
|
+
def clear_image_pool() -> None:
|
134
|
+
"""Clear all arrays from the memory pool."""
|
135
|
+
_image_pool.clear_pool()
|
55
136
|
|
56
137
|
@staticmethod
|
57
138
|
async def from_json_to_image(
|
@@ -390,7 +390,9 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
390
390
|
if self.check_zoom_and_aspect_ratio():
|
391
391
|
# Convert to PIL for resizing
|
392
392
|
pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
|
393
|
-
|
393
|
+
# Return array to pool for reuse instead of just deleting
|
394
|
+
from .config.drawable import Drawable
|
395
|
+
Drawable.return_image_to_pool(img_np_array)
|
394
396
|
resize_params = prepare_resize_params(self, pil_img, False)
|
395
397
|
resized_image = await self.async_resize_images(resize_params)
|
396
398
|
|
@@ -405,13 +407,17 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
405
407
|
if return_webp:
|
406
408
|
# Convert directly from NumPy to WebP for better performance
|
407
409
|
webp_bytes = await numpy_to_webp_bytes(img_np_array)
|
408
|
-
|
410
|
+
# Return array to pool for reuse instead of just deleting
|
411
|
+
from .config.drawable import Drawable
|
412
|
+
Drawable.return_image_to_pool(img_np_array)
|
409
413
|
LOGGER.debug("%s: Frame Completed.", self.file_name)
|
410
414
|
return webp_bytes
|
411
415
|
else:
|
412
416
|
# Convert to PIL Image (original behavior)
|
413
417
|
pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
|
414
|
-
|
418
|
+
# Return array to pool for reuse instead of just deleting
|
419
|
+
from .config.drawable import Drawable
|
420
|
+
Drawable.return_image_to_pool(img_np_array)
|
415
421
|
LOGGER.debug("%s: Frame Completed.", self.file_name)
|
416
422
|
return pil_img
|
417
423
|
except (RuntimeError, RuntimeWarning) as e:
|
@@ -7,6 +7,7 @@ Version: 0.1.9.a6
|
|
7
7
|
|
8
8
|
from __future__ import annotations
|
9
9
|
|
10
|
+
import asyncio
|
10
11
|
import logging
|
11
12
|
import uuid
|
12
13
|
from typing import Any
|
@@ -14,6 +15,7 @@ from typing import Any
|
|
14
15
|
import numpy as np
|
15
16
|
from PIL import Image
|
16
17
|
|
18
|
+
from .config.async_utils import AsyncNumPy, AsyncPIL, AsyncParallel
|
17
19
|
from .config.auto_crop import AutoCrop
|
18
20
|
from .config.drawable_elements import DrawableElement
|
19
21
|
from .config.types import (
|
@@ -166,6 +168,14 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
166
168
|
self.json_id = str(uuid.uuid4()) # image id
|
167
169
|
_LOGGER.info("Vacuum Data ID: %s", self.json_id)
|
168
170
|
|
171
|
+
# Prepare parallel data extraction tasks
|
172
|
+
data_tasks = []
|
173
|
+
data_tasks.append(self._prepare_zone_data(m_json))
|
174
|
+
data_tasks.append(self._prepare_path_data(m_json))
|
175
|
+
|
176
|
+
# Execute data preparation tasks in parallel
|
177
|
+
zone_data, path_data = await asyncio.gather(*data_tasks, return_exceptions=True)
|
178
|
+
|
169
179
|
(
|
170
180
|
img_np_array,
|
171
181
|
robot_position,
|
@@ -192,12 +202,16 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
192
202
|
if return_webp:
|
193
203
|
# Convert directly to WebP bytes for better performance
|
194
204
|
webp_bytes = await numpy_to_webp_bytes(img_np_array)
|
195
|
-
|
205
|
+
# Return array to pool for reuse instead of just deleting
|
206
|
+
from .config.drawable import Drawable
|
207
|
+
Drawable.return_image_to_pool(img_np_array)
|
196
208
|
return webp_bytes
|
197
209
|
else:
|
198
|
-
# Convert to PIL Image
|
199
|
-
pil_img =
|
200
|
-
|
210
|
+
# Convert to PIL Image using async utilities
|
211
|
+
pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
|
212
|
+
# Return array to pool for reuse instead of just deleting
|
213
|
+
from .config.drawable import Drawable
|
214
|
+
Drawable.return_image_to_pool(img_np_array)
|
201
215
|
return await self._finalize_image(pil_img)
|
202
216
|
|
203
217
|
except (RuntimeError, RuntimeWarning) as e:
|
@@ -673,3 +687,25 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
673
687
|
property_name=property_name,
|
674
688
|
value=value,
|
675
689
|
)
|
690
|
+
|
691
|
+
async def async_copy_array(self, original_array):
|
692
|
+
"""Copy the array using async utilities."""
|
693
|
+
return await AsyncNumPy.async_copy(original_array)
|
694
|
+
|
695
|
+
async def _prepare_zone_data(self, m_json):
|
696
|
+
"""Prepare zone data for parallel processing."""
|
697
|
+
await asyncio.sleep(0) # Yield control
|
698
|
+
try:
|
699
|
+
return self.data.find_zone_entities(m_json)
|
700
|
+
except (ValueError, KeyError):
|
701
|
+
return None
|
702
|
+
|
703
|
+
async def _prepare_path_data(self, m_json):
|
704
|
+
"""Prepare path data for parallel processing."""
|
705
|
+
await asyncio.sleep(0) # Yield control
|
706
|
+
try:
|
707
|
+
return self.data.find_path_entities(m_json)
|
708
|
+
except (ValueError, KeyError):
|
709
|
+
return None
|
710
|
+
|
711
|
+
|
@@ -1,412 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Version: v2024.08.2
|
3
|
-
- This parser is the python version of @rand256 valetudo_mapper.
|
4
|
-
- This class is extracting the vacuum binary map_data.
|
5
|
-
- Additional functions are to get in our image_handler the images datas.
|
6
|
-
"""
|
7
|
-
|
8
|
-
import math
|
9
|
-
import struct
|
10
|
-
from enum import Enum
|
11
|
-
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
12
|
-
|
13
|
-
|
14
|
-
_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
|
15
|
-
|
16
|
-
|
17
|
-
def callback(func: _CallableT) -> _CallableT:
|
18
|
-
"""Annotation to mark method as safe to call from within the event loop."""
|
19
|
-
setattr(func, "_hass_callback", True) # Attach a custom attribute to the function
|
20
|
-
return func # Return the function without modifying its behavior
|
21
|
-
|
22
|
-
|
23
|
-
# noinspection PyTypeChecker
|
24
|
-
class RRMapParser:
|
25
|
-
"""Parse the map data from the Rand256 vacuum."""
|
26
|
-
|
27
|
-
def __init__(self):
|
28
|
-
self.map_data = None
|
29
|
-
|
30
|
-
class Tools:
|
31
|
-
"""Tools for the RRMapParser."""
|
32
|
-
|
33
|
-
DIMENSION_PIXELS = 1024
|
34
|
-
DIMENSION_MM = 50 * 1024
|
35
|
-
|
36
|
-
class Types(Enum):
|
37
|
-
"""Types of blocks in the RRMapParser."""
|
38
|
-
|
39
|
-
CHARGER_LOCATION = 1
|
40
|
-
IMAGE = 2
|
41
|
-
PATH = 3
|
42
|
-
GOTO_PATH = 4
|
43
|
-
GOTO_PREDICTED_PATH = 5
|
44
|
-
CURRENTLY_CLEANED_ZONES = 6
|
45
|
-
GOTO_TARGET = 7
|
46
|
-
ROBOT_POSITION = 8
|
47
|
-
FORBIDDEN_ZONES = 9
|
48
|
-
VIRTUAL_WALLS = 10
|
49
|
-
CURRENTLY_CLEANED_BLOCKS = 11
|
50
|
-
FORBIDDEN_MOP_ZONES = 12
|
51
|
-
DIGEST = 1024
|
52
|
-
|
53
|
-
@staticmethod
|
54
|
-
def parse_block(
|
55
|
-
buf: bytes,
|
56
|
-
offset: int,
|
57
|
-
result: Optional[Dict[int, Any]] = None,
|
58
|
-
pixels: bool = False,
|
59
|
-
) -> Dict[int, Any]:
|
60
|
-
"""Parse a block of data from the map data."""
|
61
|
-
result = result or {}
|
62
|
-
if len(buf) <= offset:
|
63
|
-
return result
|
64
|
-
|
65
|
-
type_ = struct.unpack("<H", buf[offset : offset + 2])[0]
|
66
|
-
hlength = struct.unpack("<H", buf[offset + 2 : offset + 4])[0]
|
67
|
-
length = struct.unpack("<I", buf[offset + 4 : offset + 8])[0]
|
68
|
-
|
69
|
-
if type_ in (
|
70
|
-
RRMapParser.Types.ROBOT_POSITION.value,
|
71
|
-
RRMapParser.Types.CHARGER_LOCATION.value,
|
72
|
-
):
|
73
|
-
result[type_] = {
|
74
|
-
"position": [
|
75
|
-
int.from_bytes(buf[offset + 8 : offset + 10], byteorder="little"),
|
76
|
-
int.from_bytes(buf[offset + 12 : offset + 14], byteorder="little"),
|
77
|
-
],
|
78
|
-
"angle": (
|
79
|
-
struct.unpack("<i", buf[offset + 16 : offset + 20])[0]
|
80
|
-
if length >= 12
|
81
|
-
else 0
|
82
|
-
),
|
83
|
-
}
|
84
|
-
elif type_ == RRMapParser.Types.IMAGE.value:
|
85
|
-
RRMapParser._parse_image_block(buf, offset, length, hlength, result, pixels)
|
86
|
-
elif type_ in (
|
87
|
-
RRMapParser.Types.PATH.value,
|
88
|
-
RRMapParser.Types.GOTO_PATH.value,
|
89
|
-
RRMapParser.Types.GOTO_PREDICTED_PATH.value,
|
90
|
-
):
|
91
|
-
result[type_] = RRMapParser._parse_path_block(buf, offset, length)
|
92
|
-
elif type_ == RRMapParser.Types.GOTO_TARGET.value:
|
93
|
-
result[type_] = {
|
94
|
-
"position": [
|
95
|
-
struct.unpack("<H", buf[offset + 8 : offset + 10])[0],
|
96
|
-
struct.unpack("<H", buf[offset + 10 : offset + 12])[0],
|
97
|
-
]
|
98
|
-
}
|
99
|
-
elif type_ == RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value:
|
100
|
-
result[type_] = RRMapParser._parse_cleaned_zones(buf, offset, length)
|
101
|
-
elif type_ in (
|
102
|
-
RRMapParser.Types.FORBIDDEN_ZONES.value,
|
103
|
-
RRMapParser.Types.FORBIDDEN_MOP_ZONES.value,
|
104
|
-
RRMapParser.Types.VIRTUAL_WALLS.value,
|
105
|
-
):
|
106
|
-
result[type_] = RRMapParser._parse_forbidden_zones(buf, offset, length)
|
107
|
-
return RRMapParser.parse_block(buf, offset + length + hlength, result, pixels)
|
108
|
-
|
109
|
-
@staticmethod
|
110
|
-
def _parse_image_block(
|
111
|
-
buf: bytes,
|
112
|
-
offset: int,
|
113
|
-
length: int,
|
114
|
-
hlength: int,
|
115
|
-
result: Dict[int, Any],
|
116
|
-
pixels: bool,
|
117
|
-
) -> None:
|
118
|
-
"""Parse the image block of the map data."""
|
119
|
-
g3offset = 4 if hlength > 24 else 0
|
120
|
-
parameters = {
|
121
|
-
"segments": {
|
122
|
-
"count": (
|
123
|
-
struct.unpack("<i", buf[offset + 8 : offset + 12])[0]
|
124
|
-
if g3offset
|
125
|
-
else 0
|
126
|
-
),
|
127
|
-
"id": [],
|
128
|
-
},
|
129
|
-
"position": {
|
130
|
-
"top": struct.unpack(
|
131
|
-
"<i", buf[offset + 8 + g3offset : offset + 12 + g3offset]
|
132
|
-
)[0],
|
133
|
-
"left": struct.unpack(
|
134
|
-
"<i", buf[offset + 12 + g3offset : offset + 16 + g3offset]
|
135
|
-
)[0],
|
136
|
-
},
|
137
|
-
"dimensions": {
|
138
|
-
"height": struct.unpack(
|
139
|
-
"<i", buf[offset + 16 + g3offset : offset + 20 + g3offset]
|
140
|
-
)[0],
|
141
|
-
"width": struct.unpack(
|
142
|
-
"<i", buf[offset + 20 + g3offset : offset + 24 + g3offset]
|
143
|
-
)[0],
|
144
|
-
},
|
145
|
-
"pixels": {"floor": [], "walls": [], "segments": {}},
|
146
|
-
}
|
147
|
-
parameters["position"]["top"] = (
|
148
|
-
RRMapParser.Tools.DIMENSION_PIXELS
|
149
|
-
- parameters["position"]["top"]
|
150
|
-
- parameters["dimensions"]["height"]
|
151
|
-
)
|
152
|
-
if (
|
153
|
-
parameters["dimensions"]["height"] > 0
|
154
|
-
and parameters["dimensions"]["width"] > 0
|
155
|
-
):
|
156
|
-
for i in range(length):
|
157
|
-
segment_type = (
|
158
|
-
struct.unpack(
|
159
|
-
"<B",
|
160
|
-
buf[offset + 24 + g3offset + i : offset + 25 + g3offset + i],
|
161
|
-
)[0]
|
162
|
-
& 0x07
|
163
|
-
)
|
164
|
-
if segment_type == 0:
|
165
|
-
continue
|
166
|
-
if segment_type == 1 and pixels:
|
167
|
-
parameters["pixels"]["walls"].append(i)
|
168
|
-
else:
|
169
|
-
s = (
|
170
|
-
struct.unpack(
|
171
|
-
"<B",
|
172
|
-
buf[
|
173
|
-
offset + 24 + g3offset + i : offset + 25 + g3offset + i
|
174
|
-
],
|
175
|
-
)[0]
|
176
|
-
>> 3
|
177
|
-
)
|
178
|
-
if s == 0 and pixels:
|
179
|
-
parameters["pixels"]["floor"].append(i)
|
180
|
-
elif s != 0:
|
181
|
-
if s not in parameters["segments"]["id"]:
|
182
|
-
parameters["segments"]["id"].append(s)
|
183
|
-
parameters["segments"]["pixels_seg_" + str(s)] = []
|
184
|
-
if pixels:
|
185
|
-
parameters["segments"]["pixels_seg_" + str(s)].append(i)
|
186
|
-
result[RRMapParser.Types.IMAGE.value] = parameters
|
187
|
-
|
188
|
-
@staticmethod
|
189
|
-
def _parse_path_block(buf: bytes, offset: int, length: int) -> Dict[str, Any]:
|
190
|
-
"""Parse a path block of the map data."""
|
191
|
-
points = [
|
192
|
-
[
|
193
|
-
struct.unpack("<H", buf[offset + 20 + i : offset + 22 + i])[0],
|
194
|
-
struct.unpack("<H", buf[offset + 22 + i : offset + 24 + i])[0],
|
195
|
-
]
|
196
|
-
for i in range(0, length, 4)
|
197
|
-
]
|
198
|
-
return {
|
199
|
-
"current_angle": struct.unpack("<I", buf[offset + 16 : offset + 20])[0],
|
200
|
-
"points": points,
|
201
|
-
}
|
202
|
-
|
203
|
-
@staticmethod
|
204
|
-
def _parse_cleaned_zones(buf: bytes, offset: int, length: int) -> List[List[int]]:
|
205
|
-
"""Parse the cleaned zones block of the map data."""
|
206
|
-
zone_count = struct.unpack("<I", buf[offset + 8 : offset + 12])[0]
|
207
|
-
return (
|
208
|
-
[
|
209
|
-
[
|
210
|
-
struct.unpack("<H", buf[offset + 12 + i : offset + 14 + i])[0],
|
211
|
-
struct.unpack("<H", buf[offset + 14 + i : offset + 16 + i])[0],
|
212
|
-
struct.unpack("<H", buf[offset + 16 + i : offset + 18 + i])[0],
|
213
|
-
struct.unpack("<H", buf[offset + 18 + i : offset + 20 + i])[0],
|
214
|
-
]
|
215
|
-
for i in range(0, length, 8)
|
216
|
-
]
|
217
|
-
if zone_count > 0
|
218
|
-
else []
|
219
|
-
)
|
220
|
-
|
221
|
-
@staticmethod
|
222
|
-
def _parse_forbidden_zones(buf: bytes, offset: int, length: int) -> List[List[int]]:
|
223
|
-
"""Parse the forbidden zones block of the map data."""
|
224
|
-
zone_count = struct.unpack("<I", buf[offset + 8 : offset + 12])[0]
|
225
|
-
return (
|
226
|
-
[
|
227
|
-
[
|
228
|
-
struct.unpack("<H", buf[offset + 12 + i : offset + 14 + i])[0],
|
229
|
-
struct.unpack("<H", buf[offset + 14 + i : offset + 16 + i])[0],
|
230
|
-
struct.unpack("<H", buf[offset + 16 + i : offset + 18 + i])[0],
|
231
|
-
struct.unpack("<H", buf[offset + 18 + i : offset + 20 + i])[0],
|
232
|
-
struct.unpack("<H", buf[offset + 20 + i : offset + 22 + i])[0],
|
233
|
-
struct.unpack("<H", buf[offset + 22 + i : offset + 24 + i])[0],
|
234
|
-
struct.unpack("<H", buf[offset + 24 + i : offset + 26 + i])[0],
|
235
|
-
struct.unpack("<H", buf[offset + 26 + i : offset + 28 + i])[0],
|
236
|
-
]
|
237
|
-
for i in range(0, length, 16)
|
238
|
-
]
|
239
|
-
if zone_count > 0
|
240
|
-
else []
|
241
|
-
)
|
242
|
-
|
243
|
-
@callback
|
244
|
-
def parse(self, map_buf: bytes) -> Dict[str, Any]:
|
245
|
-
"""Parse the map data."""
|
246
|
-
if map_buf[0:2] == b"rr":
|
247
|
-
return {
|
248
|
-
"header_length": struct.unpack("<H", map_buf[2:4])[0],
|
249
|
-
"data_length": struct.unpack("<H", map_buf[4:6])[0],
|
250
|
-
"version": {
|
251
|
-
"major": struct.unpack("<H", map_buf[8:10])[0],
|
252
|
-
"minor": struct.unpack("<H", map_buf[10:12])[0],
|
253
|
-
},
|
254
|
-
"map_index": struct.unpack("<H", map_buf[12:14])[0],
|
255
|
-
"map_sequence": struct.unpack("<H", map_buf[16:18])[0],
|
256
|
-
}
|
257
|
-
return {}
|
258
|
-
|
259
|
-
@callback
|
260
|
-
def parse_rrm_data(
|
261
|
-
self, map_buf: bytes, pixels: bool = False
|
262
|
-
) -> Optional[Dict[str, Any]]:
|
263
|
-
"""Parse the complete map data."""
|
264
|
-
if not self.parse(map_buf).get("map_index"):
|
265
|
-
return None
|
266
|
-
|
267
|
-
parsed_map_data = {}
|
268
|
-
blocks = self.parse_block(map_buf, 0x14, None, pixels)
|
269
|
-
|
270
|
-
self._parse_image_data(parsed_map_data, blocks)
|
271
|
-
self._parse_charger_data(parsed_map_data, blocks)
|
272
|
-
self._parse_robot_data(parsed_map_data, blocks)
|
273
|
-
self._parse_zones_data(parsed_map_data, blocks)
|
274
|
-
self._parse_virtual_walls_data(parsed_map_data, blocks)
|
275
|
-
self._parse_misc_data(parsed_map_data, blocks)
|
276
|
-
|
277
|
-
return parsed_map_data
|
278
|
-
|
279
|
-
@staticmethod
|
280
|
-
def _parse_image_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
|
281
|
-
"""Parse image-related data."""
|
282
|
-
if RRMapParser.Types.IMAGE.value in blocks:
|
283
|
-
parsed_map_data["image"] = blocks[RRMapParser.Types.IMAGE.value]
|
284
|
-
for item in [
|
285
|
-
{"type": RRMapParser.Types.PATH.value, "path": "path"},
|
286
|
-
{
|
287
|
-
"type": RRMapParser.Types.GOTO_PREDICTED_PATH.value,
|
288
|
-
"path": "goto_predicted_path",
|
289
|
-
},
|
290
|
-
]:
|
291
|
-
if item["type"] in blocks:
|
292
|
-
parsed_map_data[item["path"]] = blocks[item["type"]]
|
293
|
-
parsed_map_data[item["path"]]["points"] = [
|
294
|
-
[point[0], RRMapParser.Tools.DIMENSION_MM - point[1]]
|
295
|
-
for point in parsed_map_data[item["path"]]["points"]
|
296
|
-
]
|
297
|
-
if len(parsed_map_data[item["path"]]["points"]) >= 2:
|
298
|
-
parsed_map_data[item["path"]]["current_angle"] = math.degrees(
|
299
|
-
math.atan2(
|
300
|
-
parsed_map_data[item["path"]]["points"][-1][1]
|
301
|
-
- parsed_map_data[item["path"]]["points"][-2][1],
|
302
|
-
parsed_map_data[item["path"]]["points"][-1][0]
|
303
|
-
- parsed_map_data[item["path"]]["points"][-2][0],
|
304
|
-
)
|
305
|
-
)
|
306
|
-
|
307
|
-
@staticmethod
|
308
|
-
def _parse_charger_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
|
309
|
-
"""Parse charger location data."""
|
310
|
-
if RRMapParser.Types.CHARGER_LOCATION.value in blocks:
|
311
|
-
charger = blocks[RRMapParser.Types.CHARGER_LOCATION.value]["position"]
|
312
|
-
parsed_map_data["charger"] = charger
|
313
|
-
|
314
|
-
@staticmethod
|
315
|
-
def _parse_robot_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
|
316
|
-
"""Parse robot position data."""
|
317
|
-
if RRMapParser.Types.ROBOT_POSITION.value in blocks:
|
318
|
-
robot = blocks[RRMapParser.Types.ROBOT_POSITION.value]["position"]
|
319
|
-
rob_angle = blocks[RRMapParser.Types.ROBOT_POSITION.value]["angle"]
|
320
|
-
parsed_map_data["robot"] = robot
|
321
|
-
parsed_map_data["robot_angle"] = rob_angle
|
322
|
-
|
323
|
-
@staticmethod
|
324
|
-
def _parse_zones_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
|
325
|
-
"""Parse zones and forbidden zones data."""
|
326
|
-
if RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value in blocks:
|
327
|
-
parsed_map_data["currently_cleaned_zones"] = [
|
328
|
-
[
|
329
|
-
zone[0],
|
330
|
-
RRMapParser.Tools.DIMENSION_MM - zone[1],
|
331
|
-
zone[2],
|
332
|
-
RRMapParser.Tools.DIMENSION_MM - zone[3],
|
333
|
-
]
|
334
|
-
for zone in blocks[RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value]
|
335
|
-
]
|
336
|
-
|
337
|
-
if RRMapParser.Types.FORBIDDEN_ZONES.value in blocks:
|
338
|
-
parsed_map_data["forbidden_zones"] = [
|
339
|
-
[
|
340
|
-
zone[0],
|
341
|
-
RRMapParser.Tools.DIMENSION_MM - zone[1],
|
342
|
-
zone[2],
|
343
|
-
RRMapParser.Tools.DIMENSION_MM - zone[3],
|
344
|
-
zone[4],
|
345
|
-
RRMapParser.Tools.DIMENSION_MM - zone[5],
|
346
|
-
zone[6],
|
347
|
-
RRMapParser.Tools.DIMENSION_MM - zone[7],
|
348
|
-
]
|
349
|
-
for zone in blocks[RRMapParser.Types.FORBIDDEN_ZONES.value]
|
350
|
-
]
|
351
|
-
|
352
|
-
@staticmethod
|
353
|
-
def _parse_virtual_walls_data(
|
354
|
-
parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]
|
355
|
-
):
|
356
|
-
"""Parse virtual walls data."""
|
357
|
-
if RRMapParser.Types.VIRTUAL_WALLS.value in blocks:
|
358
|
-
parsed_map_data["virtual_walls"] = [
|
359
|
-
[
|
360
|
-
wall[0],
|
361
|
-
RRMapParser.Tools.DIMENSION_MM - wall[1],
|
362
|
-
wall[2],
|
363
|
-
RRMapParser.Tools.DIMENSION_MM - wall[3],
|
364
|
-
]
|
365
|
-
for wall in blocks[RRMapParser.Types.VIRTUAL_WALLS.value]
|
366
|
-
]
|
367
|
-
|
368
|
-
@staticmethod
|
369
|
-
def _parse_misc_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
|
370
|
-
"""Parse miscellaneous data like cleaned blocks and mop zones."""
|
371
|
-
if RRMapParser.Types.CURRENTLY_CLEANED_BLOCKS.value in blocks:
|
372
|
-
parsed_map_data["currently_cleaned_blocks"] = blocks[
|
373
|
-
RRMapParser.Types.CURRENTLY_CLEANED_BLOCKS.value
|
374
|
-
]
|
375
|
-
|
376
|
-
if RRMapParser.Types.FORBIDDEN_MOP_ZONES.value in blocks:
|
377
|
-
parsed_map_data["forbidden_mop_zones"] = [
|
378
|
-
[
|
379
|
-
zone[0],
|
380
|
-
RRMapParser.Tools.DIMENSION_MM - zone[1],
|
381
|
-
zone[2],
|
382
|
-
RRMapParser.Tools.DIMENSION_MM - zone[3],
|
383
|
-
zone[4],
|
384
|
-
RRMapParser.Tools.DIMENSION_MM - zone[5],
|
385
|
-
zone[6],
|
386
|
-
RRMapParser.Tools.DIMENSION_MM - zone[7],
|
387
|
-
]
|
388
|
-
for zone in blocks[RRMapParser.Types.FORBIDDEN_MOP_ZONES.value]
|
389
|
-
]
|
390
|
-
|
391
|
-
if RRMapParser.Types.GOTO_TARGET.value in blocks:
|
392
|
-
parsed_map_data["goto_target"] = blocks[
|
393
|
-
RRMapParser.Types.GOTO_TARGET.value
|
394
|
-
]["position"]
|
395
|
-
|
396
|
-
def parse_data(
|
397
|
-
self, payload: Optional[bytes] = None, pixels: bool = False
|
398
|
-
) -> Optional[Dict[str, Any]]:
|
399
|
-
"""Get the map data from MQTT and return the json."""
|
400
|
-
if payload:
|
401
|
-
self.map_data = self.parse(payload)
|
402
|
-
self.map_data.update(self.parse_rrm_data(payload, pixels) or {})
|
403
|
-
return self.map_data
|
404
|
-
|
405
|
-
def get_image(self) -> Dict[str, Any]:
|
406
|
-
"""Get the image data from the map data."""
|
407
|
-
return self.map_data.get("image", {})
|
408
|
-
|
409
|
-
@staticmethod
|
410
|
-
def get_int32(data: bytes, address: int) -> int:
|
411
|
-
"""Get a 32-bit integer from the data."""
|
412
|
-
return struct.unpack_from("<i", data, address)[0]
|
File without changes
|
File without changes
|
File without changes
|
{valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/hypfer_draw.py
RENAMED
File without changes
|
File without changes
|
{valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/map_data.py
RENAMED
File without changes
|
{valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/py.typed
RENAMED
File without changes
|
{valetudo_map_parser-0.1.9b64 → valetudo_map_parser-0.1.9b67}/SCR/valetudo_map_parser/reimg_draw.py
RENAMED
File without changes
|
File without changes
|