valetudo-map-parser 0.1.9b65__py3-none-any.whl → 0.1.9b68__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/config/shared.py +11 -8
- valetudo_map_parser/config/utils.py +62 -0
- valetudo_map_parser/hypfer_handler.py +6 -16
- valetudo_map_parser/rand256_handler.py +10 -9
- {valetudo_map_parser-0.1.9b65.dist-info → valetudo_map_parser-0.1.9b68.dist-info}/METADATA +1 -1
- {valetudo_map_parser-0.1.9b65.dist-info → valetudo_map_parser-0.1.9b68.dist-info}/RECORD +9 -10
- valetudo_map_parser/config/rand25_parser.py +0 -412
- {valetudo_map_parser-0.1.9b65.dist-info → valetudo_map_parser-0.1.9b68.dist-info}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b65.dist-info → valetudo_map_parser-0.1.9b68.dist-info}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b65.dist-info → valetudo_map_parser-0.1.9b68.dist-info}/WHEEL +0 -0
@@ -39,6 +39,7 @@ from .types import (
|
|
39
39
|
CameraModes,
|
40
40
|
Colors,
|
41
41
|
TrimsData,
|
42
|
+
PilPNG,
|
42
43
|
)
|
43
44
|
|
44
45
|
|
@@ -58,9 +59,10 @@ class CameraShared:
|
|
58
59
|
self.rand256_active_zone: list = [] # Active zone for rand256
|
59
60
|
self.is_rand: bool = False # MQTT rand data
|
60
61
|
self._new_mqtt_message = False # New MQTT message
|
61
|
-
self.last_image = None # Last image received
|
62
|
-
self.
|
63
|
-
self.binary_image = None # Current image in binary format
|
62
|
+
self.last_image = PilPNG | None # Last image received
|
63
|
+
self.new_image: PilPNG | None = None # New image received
|
64
|
+
self.binary_image: bytes | None = None # Current image in binary format
|
65
|
+
self.image_last_updated: float = 0.0 # Last image update time
|
64
66
|
self.image_format = "image/pil" # Image format
|
65
67
|
self.image_size = None # Image size
|
66
68
|
self.image_auto_zoom: bool = False # Auto zoom image
|
@@ -115,7 +117,7 @@ class CameraShared:
|
|
115
117
|
|
116
118
|
|
117
119
|
|
118
|
-
def
|
120
|
+
def vacuum_bat_charged(self) -> bool:
|
119
121
|
"""Check if the vacuum is charging."""
|
120
122
|
return (self.vacuum_state == "docked") and (int(self.vacuum_battery) < 100)
|
121
123
|
|
@@ -193,7 +195,7 @@ class CameraShared:
|
|
193
195
|
attrs = {
|
194
196
|
ATTR_CAMERA_MODE: self.camera_mode,
|
195
197
|
ATTR_VACUUM_BATTERY: f"{self.vacuum_battery}%",
|
196
|
-
ATTR_VACUUM_CHARGING: self.
|
198
|
+
ATTR_VACUUM_CHARGING: self.vacuum_bat_charged,
|
197
199
|
ATTR_VACUUM_POSITION: self.current_room,
|
198
200
|
ATTR_VACUUM_STATUS: self.vacuum_state,
|
199
201
|
ATTR_VACUUM_JSON_ID: self.vac_json_id,
|
@@ -228,12 +230,13 @@ class CameraShared:
|
|
228
230
|
class CameraSharedManager:
|
229
231
|
"""Camera Shared Manager class."""
|
230
232
|
|
231
|
-
def __init__(self, file_name, device_info):
|
233
|
+
def __init__(self, file_name: str, device_info: dict = None):
|
232
234
|
self._instances = {}
|
233
235
|
self._lock = asyncio.Lock()
|
234
236
|
self.file_name = file_name
|
235
|
-
|
236
|
-
|
237
|
+
if device_info:
|
238
|
+
self.device_info = device_info
|
239
|
+
self.update_shared_data(device_info)
|
237
240
|
|
238
241
|
# Automatically initialize shared data for the instance
|
239
242
|
# self._init_shared_data(device_info)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Utility code for the valetudo map parser."""
|
2
2
|
|
3
|
+
import datetime
|
3
4
|
import hashlib
|
4
5
|
import json
|
5
6
|
from dataclasses import dataclass
|
@@ -79,6 +80,67 @@ class BaseHandler:
|
|
79
80
|
"""Return the robot position."""
|
80
81
|
return self.robot_pos
|
81
82
|
|
83
|
+
async def async_get_pil_image(
|
84
|
+
self,
|
85
|
+
m_json: dict | None,
|
86
|
+
destinations: list | None = None,
|
87
|
+
) -> PilPNG | None:
|
88
|
+
"""
|
89
|
+
Unified async function to get PIL image from JSON data for both Hypfer and Rand256 handlers.
|
90
|
+
|
91
|
+
This function:
|
92
|
+
1. Calls the appropriate async_get_image_from_json method
|
93
|
+
2. Stores the processed data in shared.new_image
|
94
|
+
3. Backs up previous data to shared.last_image
|
95
|
+
4. Updates shared.image_last_updated with current datetime
|
96
|
+
|
97
|
+
@param m_json: The JSON data to use to draw the image
|
98
|
+
@param destinations: MQTT destinations for labels (used by Rand256)
|
99
|
+
@return: PIL Image or None
|
100
|
+
"""
|
101
|
+
try:
|
102
|
+
# Backup current image to last_image before processing new one
|
103
|
+
if hasattr(self.shared, 'new_image') and self.shared.new_image is not None:
|
104
|
+
self.shared.last_image = self.shared.new_image
|
105
|
+
|
106
|
+
# Call the appropriate handler method based on handler type
|
107
|
+
if hasattr(self, 'get_image_from_rrm'):
|
108
|
+
# This is a Rand256 handler
|
109
|
+
new_image = await self.get_image_from_rrm(
|
110
|
+
m_json=m_json,
|
111
|
+
destinations=destinations,
|
112
|
+
return_webp=False # Always return PIL Image
|
113
|
+
)
|
114
|
+
elif hasattr(self, 'async_get_image_from_json'):
|
115
|
+
# This is a Hypfer handler
|
116
|
+
new_image = await self.async_get_image_from_json(
|
117
|
+
m_json=m_json,
|
118
|
+
return_webp=False # Always return PIL Image
|
119
|
+
)
|
120
|
+
else:
|
121
|
+
LOGGER.warning("%s: Handler type not recognized for async_get_pil_image", self.file_name)
|
122
|
+
return None
|
123
|
+
|
124
|
+
# Store the new image in shared data
|
125
|
+
if new_image is not None:
|
126
|
+
self.shared.new_image = new_image
|
127
|
+
# Update the timestamp with current datetime
|
128
|
+
self.shared.image_last_updated = datetime.datetime.now().timestamp()
|
129
|
+
LOGGER.debug("%s: Image processed and stored in shared data", self.file_name)
|
130
|
+
else:
|
131
|
+
LOGGER.warning("%s: Failed to generate image from JSON data", self.file_name)
|
132
|
+
|
133
|
+
return new_image
|
134
|
+
|
135
|
+
except Exception as e:
|
136
|
+
LOGGER.error(
|
137
|
+
"%s: Error in async_get_pil_image: %s",
|
138
|
+
self.file_name,
|
139
|
+
str(e),
|
140
|
+
exc_info=True
|
141
|
+
)
|
142
|
+
return None
|
143
|
+
|
82
144
|
def get_charger_position(self) -> ChargerPosition | None:
|
83
145
|
"""Return the charger position."""
|
84
146
|
return self.charger_pos
|
@@ -8,11 +8,10 @@ Version: 0.1.9
|
|
8
8
|
from __future__ import annotations
|
9
9
|
|
10
10
|
import asyncio
|
11
|
-
import json
|
12
11
|
|
13
12
|
from PIL import Image
|
14
13
|
|
15
|
-
from .config.async_utils import AsyncNumPy, AsyncPIL
|
14
|
+
from .config.async_utils import AsyncNumPy, AsyncPIL
|
16
15
|
from .config.auto_crop import AutoCrop
|
17
16
|
from .config.drawable_elements import DrawableElement
|
18
17
|
from .config.shared import CameraShared
|
@@ -25,6 +24,7 @@ from .config.types import (
|
|
25
24
|
RoomsProperties,
|
26
25
|
RoomStore,
|
27
26
|
WebPBytes,
|
27
|
+
JsonType,
|
28
28
|
)
|
29
29
|
from .config.utils import (
|
30
30
|
BaseHandler,
|
@@ -100,7 +100,7 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
100
100
|
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable
|
101
101
|
async def async_get_image_from_json(
|
102
102
|
self,
|
103
|
-
m_json:
|
103
|
+
m_json: JsonType | None,
|
104
104
|
return_webp: bool = False,
|
105
105
|
) -> WebPBytes | Image.Image | None:
|
106
106
|
"""Get the image from the JSON data.
|
@@ -232,13 +232,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
232
232
|
disabled_rooms if layer_type == "wall" else None,
|
233
233
|
)
|
234
234
|
|
235
|
-
# Update element map for this layer
|
236
|
-
if is_room_layer and 0 < room_id <= 15:
|
237
|
-
# Mark the room in the element map
|
238
|
-
room_element = getattr(
|
239
|
-
DrawableElement, f"ROOM_{room_id}", None
|
240
|
-
)
|
241
|
-
|
242
235
|
# Draw the virtual walls if enabled
|
243
236
|
if self.drawing_config.is_enabled(DrawableElement.VIRTUAL_WALL):
|
244
237
|
img_np_array = await self.imd.async_draw_virtual_walls(
|
@@ -313,10 +306,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
313
306
|
LOGGER.info("%s: Drawing path", self.file_name)
|
314
307
|
data_tasks.append(self._prepare_path_data(m_json))
|
315
308
|
|
316
|
-
# Execute data preparation in parallel if we have tasks
|
317
|
-
if data_tasks:
|
318
|
-
prepared_data = await AsyncParallel.parallel_data_preparation(*data_tasks)
|
319
|
-
|
320
309
|
# Process drawing operations sequentially (since they modify the same array)
|
321
310
|
# Draw zones if enabled
|
322
311
|
if self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
|
@@ -500,7 +489,8 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
500
489
|
except (ValueError, KeyError):
|
501
490
|
return None
|
502
491
|
|
503
|
-
|
492
|
+
@staticmethod
|
493
|
+
async def _prepare_goto_data(entity_dict):
|
504
494
|
"""Prepare go-to flag data for parallel processing."""
|
505
495
|
await asyncio.sleep(0) # Yield control
|
506
496
|
# Extract go-to target data from entity_dict
|
@@ -510,6 +500,6 @@ class HypferMapImageHandler(BaseHandler, AutoCrop):
|
|
510
500
|
"""Prepare path data for parallel processing."""
|
511
501
|
await asyncio.sleep(0) # Yield control
|
512
502
|
try:
|
513
|
-
return self.data.
|
503
|
+
return self.data.find_paths_entities(m_json)
|
514
504
|
except (ValueError, KeyError):
|
515
505
|
return None
|
@@ -12,8 +12,8 @@ import uuid
|
|
12
12
|
from typing import Any
|
13
13
|
|
14
14
|
import numpy as np
|
15
|
-
from PIL import Image
|
16
15
|
|
16
|
+
from .config.async_utils import AsyncNumPy, AsyncPIL
|
17
17
|
from .config.auto_crop import AutoCrop
|
18
18
|
from .config.drawable_elements import DrawableElement
|
19
19
|
from .config.types import (
|
@@ -145,7 +145,7 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
145
145
|
m_json: JsonType, # json data
|
146
146
|
destinations: None = None, # MQTT destinations for labels
|
147
147
|
return_webp: bool = False,
|
148
|
-
) -> WebPBytes |
|
148
|
+
) -> WebPBytes | PilPNG | None:
|
149
149
|
"""Generate Images from the json data.
|
150
150
|
@param m_json: The JSON data to use to draw the image.
|
151
151
|
@param destinations: MQTT destinations for labels (unused).
|
@@ -195,8 +195,8 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
195
195
|
del img_np_array # free memory
|
196
196
|
return webp_bytes
|
197
197
|
else:
|
198
|
-
# Convert to PIL Image
|
199
|
-
pil_img =
|
198
|
+
# Convert to PIL Image using async utilities
|
199
|
+
pil_img = await AsyncPIL.async_fromarray(img_np_array, mode="RGBA")
|
200
200
|
del img_np_array # free memory
|
201
201
|
return await self._finalize_image(pil_img)
|
202
202
|
|
@@ -297,11 +297,6 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
297
297
|
original_rooms_pos = self.rooms_pos
|
298
298
|
self.rooms_pos = temp_rooms_pos
|
299
299
|
|
300
|
-
# Perform robot room detection to check active zones
|
301
|
-
robot_room_result = await self.async_get_robot_in_room(
|
302
|
-
robot_position[0], robot_position[1], robot_position_angle
|
303
|
-
)
|
304
|
-
|
305
300
|
# Restore original rooms_pos
|
306
301
|
self.rooms_pos = original_rooms_pos
|
307
302
|
|
@@ -673,3 +668,9 @@ class ReImageHandler(BaseHandler, AutoCrop):
|
|
673
668
|
property_name=property_name,
|
674
669
|
value=value,
|
675
670
|
)
|
671
|
+
|
672
|
+
async def async_copy_array(self, original_array):
|
673
|
+
"""Copy the array using async utilities."""
|
674
|
+
return await AsyncNumPy.async_copy(original_array)
|
675
|
+
|
676
|
+
|
@@ -9,20 +9,19 @@ valetudo_map_parser/config/drawable_elements.py,sha256=o-5oiXmfqPwNQLzKIhkEcZD_A
|
|
9
9
|
valetudo_map_parser/config/enhanced_drawable.py,sha256=QlGxlUMVgECUXPtFwIslyjubWxQuhIixsRymWV3lEvk,12586
|
10
10
|
valetudo_map_parser/config/optimized_element_map.py,sha256=52BCnkvVv9bre52LeVIfT8nhnEIpc0TuWTv1xcNu0Rk,15744
|
11
11
|
valetudo_map_parser/config/rand256_parser.py,sha256=LU3y7XvRRQxVen9iwom0dOaDnJJvhZdg97NqOYRZFas,16279
|
12
|
-
valetudo_map_parser/config/
|
13
|
-
valetudo_map_parser/config/shared.py,sha256=J_66BuhgqRJUjXKBwKh8qih1iyiTq9ZiaZwduIROYmE,12560
|
12
|
+
valetudo_map_parser/config/shared.py,sha256=AQ73878TGCxbHQhgrAxSROLqFE-Zz4fJhTdoi9gBvJo,12736
|
14
13
|
valetudo_map_parser/config/types.py,sha256=saL7pULKAdTRQ_ShR2arT8IV472e9MBC_SohTthlGp8,17567
|
15
|
-
valetudo_map_parser/config/utils.py,sha256=
|
14
|
+
valetudo_map_parser/config/utils.py,sha256=MR1UIOwHWYJ8lFrPYhfyi9IV7C08S-DqCyIPppsB2tM,33913
|
16
15
|
valetudo_map_parser/hypfer_draw.py,sha256=ZK_WybvukHd8nNk2mq5icrOu1Ue3SVCIC6_Hc9bTg0Q,29396
|
17
|
-
valetudo_map_parser/hypfer_handler.py,sha256=
|
16
|
+
valetudo_map_parser/hypfer_handler.py,sha256=f9SCphArA-LO2ySrTKpxn6k4htM-JRrbrxKFh3AjnD8,23171
|
18
17
|
valetudo_map_parser/hypfer_rooms_handler.py,sha256=NkpOA6Gdq-2D3lLAxvtNuuWMvPXHxeMY2TO5RZLSHlU,22652
|
19
18
|
valetudo_map_parser/map_data.py,sha256=Op0LTCakcTJ1Q0rxQhl6BpgSby_6nJenCQS2Y2FHtRk,17243
|
20
19
|
valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
valetudo_map_parser/rand256_handler.py,sha256=
|
20
|
+
valetudo_map_parser/rand256_handler.py,sha256=6Qt-xFJ3PITQNSAJTaEo6jl6TdgFms7ay1rnk4vekgk,27653
|
22
21
|
valetudo_map_parser/reimg_draw.py,sha256=1q8LkNTPHEA9Tsapc_JnVw51kpPYNhaBU-KmHkefCQY,12507
|
23
22
|
valetudo_map_parser/rooms_handler.py,sha256=ovqQtAjauAqwUNPR0aX27P2zhheQmqfaFhDE3_AwYWk,17821
|
24
|
-
valetudo_map_parser-0.1.
|
25
|
-
valetudo_map_parser-0.1.
|
26
|
-
valetudo_map_parser-0.1.
|
27
|
-
valetudo_map_parser-0.1.
|
28
|
-
valetudo_map_parser-0.1.
|
23
|
+
valetudo_map_parser-0.1.9b68.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
|
24
|
+
valetudo_map_parser-0.1.9b68.dist-info/METADATA,sha256=hBV8Inc5QpIw-TcGWC-sLqhRb60TlFq1OVkMmm1iBek,3353
|
25
|
+
valetudo_map_parser-0.1.9b68.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
|
26
|
+
valetudo_map_parser-0.1.9b68.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
27
|
+
valetudo_map_parser-0.1.9b68.dist-info/RECORD,,
|
@@ -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
|
{valetudo_map_parser-0.1.9b65.dist-info → valetudo_map_parser-0.1.9b68.dist-info}/NOTICE.txt
RENAMED
File without changes
|
File without changes
|