valetudo-map-parser 0.1.9b50__py3-none-any.whl → 0.1.9b52__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 +2 -0
- valetudo_map_parser/config/color_utils.py +69 -13
- valetudo_map_parser/config/colors.py +55 -70
- valetudo_map_parser/config/drawable_elements.py +0 -676
- valetudo_map_parser/config/enhanced_drawable.py +75 -193
- valetudo_map_parser/config/shared.py +0 -1
- valetudo_map_parser/config/types.py +19 -33
- valetudo_map_parser/config/utils.py +0 -67
- valetudo_map_parser/hypfer_draw.py +222 -57
- valetudo_map_parser/hypfer_handler.py +48 -155
- valetudo_map_parser/hypfer_rooms_handler.py +406 -0
- valetudo_map_parser/map_data.py +0 -30
- valetudo_map_parser/rand25_handler.py +2 -84
- {valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/METADATA +1 -1
- valetudo_map_parser-0.1.9b52.dist-info/RECORD +26 -0
- valetudo_map_parser-0.1.9b50.dist-info/RECORD +0 -25
- {valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b50.dist-info → valetudo_map_parser-0.1.9b52.dist-info}/WHEEL +0 -0
@@ -14,7 +14,11 @@ from typing import Optional, Tuple
|
|
14
14
|
import numpy as np
|
15
15
|
|
16
16
|
from .drawable import Drawable
|
17
|
-
from .drawable_elements import
|
17
|
+
from .drawable_elements import (
|
18
|
+
DrawableElement,
|
19
|
+
DrawingConfig,
|
20
|
+
)
|
21
|
+
from .colors import ColorsManagement
|
18
22
|
|
19
23
|
|
20
24
|
# Type aliases
|
@@ -32,97 +36,13 @@ class EnhancedDrawable(Drawable):
|
|
32
36
|
super().__init__()
|
33
37
|
self.drawing_config = drawing_config or DrawingConfig()
|
34
38
|
|
35
|
-
|
36
|
-
def blend_colors(base_color: Color, overlay_color: Color) -> Color:
|
37
|
-
"""
|
38
|
-
Blend two RGBA colors, considering alpha channels.
|
39
|
-
|
40
|
-
Args:
|
41
|
-
base_color: The base RGBA color
|
42
|
-
overlay_color: The overlay RGBA color to blend on top
|
43
|
-
|
44
|
-
Returns:
|
45
|
-
The blended RGBA color
|
46
|
-
"""
|
47
|
-
# Extract components
|
48
|
-
r1, g1, b1, a1 = base_color
|
49
|
-
r2, g2, b2, a2 = overlay_color
|
50
|
-
|
51
|
-
# Convert alpha to 0-1 range
|
52
|
-
a1 = a1 / 255.0
|
53
|
-
a2 = a2 / 255.0
|
54
|
-
|
55
|
-
# Calculate resulting alpha
|
56
|
-
a_out = a1 + a2 * (1 - a1)
|
57
|
-
|
58
|
-
# Avoid division by zero
|
59
|
-
if a_out < 0.0001:
|
60
|
-
return (0, 0, 0, 0)
|
61
|
-
|
62
|
-
# Calculate blended RGB components
|
63
|
-
r_out = (r1 * a1 + r2 * a2 * (1 - a1)) / a_out
|
64
|
-
g_out = (g1 * a1 + g2 * a2 * (1 - a1)) / a_out
|
65
|
-
b_out = (b1 * a1 + b2 * a2 * (1 - a1)) / a_out
|
66
|
-
|
67
|
-
# Convert back to 0-255 range and return as tuple
|
68
|
-
return (
|
69
|
-
int(max(0, min(255, int(r_out)))),
|
70
|
-
int(max(0, min(255, int(g_out)))),
|
71
|
-
int(max(0, min(255, int(b_out)))),
|
72
|
-
int(max(0, min(255, int(a_out * 255)))),
|
73
|
-
)
|
74
|
-
|
75
|
-
def blend_pixel(
|
76
|
-
self,
|
77
|
-
array: NumpyArray,
|
78
|
-
x: int,
|
79
|
-
y: int,
|
80
|
-
color: Color,
|
81
|
-
element: DrawableElement,
|
82
|
-
element_map: NumpyArray,
|
83
|
-
) -> None:
|
84
|
-
"""
|
85
|
-
Blend a pixel color with the existing color at the specified position.
|
86
|
-
Also updates the element map if the new element has higher z-index.
|
87
|
-
|
88
|
-
Args:
|
89
|
-
array: The image array to modify
|
90
|
-
x, y: Pixel coordinates
|
91
|
-
color: RGBA color to blend
|
92
|
-
element: The element being drawn
|
93
|
-
element_map: The element map to update
|
94
|
-
"""
|
95
|
-
# Check bounds
|
96
|
-
if not (0 <= y < array.shape[0] and 0 <= x < array.shape[1]):
|
97
|
-
return
|
98
|
-
|
99
|
-
# Get current element at this position
|
100
|
-
current_element = element_map[y, x]
|
101
|
-
|
102
|
-
# Get z-indices for comparison
|
103
|
-
current_z = (
|
104
|
-
self.drawing_config.get_property(current_element, "z_index", 0)
|
105
|
-
if current_element
|
106
|
-
else 0
|
107
|
-
)
|
108
|
-
new_z = self.drawing_config.get_property(element, "z_index", 0)
|
109
|
-
|
110
|
-
# Get current color at this position
|
111
|
-
current_color = tuple(array[y, x])
|
112
|
-
|
113
|
-
# Blend colors
|
114
|
-
blended_color = self.blend_colors(current_color, color)
|
39
|
+
# Color blending methods have been moved to ColorsManagement class in colors.py
|
115
40
|
|
116
|
-
|
117
|
-
array[y, x] = blended_color
|
118
|
-
|
119
|
-
# Update element map if new element has higher z-index
|
120
|
-
if new_z >= current_z:
|
121
|
-
element_map[y, x] = element
|
41
|
+
# Pixel blending methods have been moved to ColorsManagement class in colors.py
|
122
42
|
|
123
43
|
async def draw_map(
|
124
44
|
self, map_data: dict, base_array: Optional[NumpyArray] = None
|
125
|
-
) ->
|
45
|
+
) -> NumpyArray:
|
126
46
|
"""
|
127
47
|
Draw the map with selected elements.
|
128
48
|
|
@@ -131,7 +51,7 @@ class EnhancedDrawable(Drawable):
|
|
131
51
|
base_array: Optional base array to draw on
|
132
52
|
|
133
53
|
Returns:
|
134
|
-
|
54
|
+
The image array with all elements drawn
|
135
55
|
"""
|
136
56
|
# Get map dimensions
|
137
57
|
size_x = map_data.get("size", {}).get("x", 1024)
|
@@ -144,81 +64,48 @@ class EnhancedDrawable(Drawable):
|
|
144
64
|
)
|
145
65
|
base_array = await self.create_empty_image(size_x, size_y, background_color)
|
146
66
|
|
147
|
-
# Create a 2D map for element identification
|
148
|
-
element_map = np.zeros(
|
149
|
-
(base_array.shape[0], base_array.shape[1]), dtype=np.int32
|
150
|
-
)
|
151
|
-
|
152
67
|
# Draw elements in order of z-index
|
153
68
|
for element in self.drawing_config.get_drawing_order():
|
154
69
|
if element == DrawableElement.FLOOR:
|
155
|
-
base_array
|
156
|
-
map_data, base_array, element_map
|
157
|
-
)
|
70
|
+
base_array = await self._draw_floor(map_data, base_array)
|
158
71
|
elif element == DrawableElement.WALL:
|
159
|
-
base_array
|
160
|
-
map_data, base_array, element_map
|
161
|
-
)
|
72
|
+
base_array = await self._draw_walls(map_data, base_array)
|
162
73
|
elif element == DrawableElement.ROBOT:
|
163
|
-
base_array
|
164
|
-
map_data, base_array, element_map
|
165
|
-
)
|
74
|
+
base_array = await self._draw_robot(map_data, base_array)
|
166
75
|
elif element == DrawableElement.CHARGER:
|
167
|
-
base_array
|
168
|
-
map_data, base_array, element_map
|
169
|
-
)
|
76
|
+
base_array = await self._draw_charger(map_data, base_array)
|
170
77
|
elif element == DrawableElement.VIRTUAL_WALL:
|
171
|
-
base_array
|
172
|
-
map_data, base_array, element_map
|
173
|
-
)
|
78
|
+
base_array = await self._draw_virtual_walls(map_data, base_array)
|
174
79
|
elif element == DrawableElement.RESTRICTED_AREA:
|
175
|
-
base_array
|
176
|
-
map_data, base_array, element_map
|
177
|
-
)
|
80
|
+
base_array = await self._draw_restricted_areas(map_data, base_array)
|
178
81
|
elif element == DrawableElement.NO_MOP_AREA:
|
179
|
-
base_array
|
180
|
-
map_data, base_array, element_map
|
181
|
-
)
|
82
|
+
base_array = await self._draw_no_mop_areas(map_data, base_array)
|
182
83
|
elif element == DrawableElement.PATH:
|
183
|
-
base_array
|
184
|
-
map_data, base_array, element_map
|
185
|
-
)
|
84
|
+
base_array = await self._draw_path(map_data, base_array)
|
186
85
|
elif element == DrawableElement.PREDICTED_PATH:
|
187
|
-
base_array
|
188
|
-
map_data, base_array, element_map
|
189
|
-
)
|
86
|
+
base_array = await self._draw_predicted_path(map_data, base_array)
|
190
87
|
elif element == DrawableElement.GO_TO_TARGET:
|
191
|
-
base_array
|
192
|
-
map_data, base_array, element_map
|
193
|
-
)
|
88
|
+
base_array = await self._draw_go_to_target(map_data, base_array)
|
194
89
|
elif DrawableElement.ROOM_1 <= element <= DrawableElement.ROOM_15:
|
195
90
|
room_id = element - DrawableElement.ROOM_1 + 1
|
196
|
-
base_array
|
197
|
-
map_data, room_id, base_array, element_map
|
198
|
-
)
|
91
|
+
base_array = await self._draw_room(map_data, room_id, base_array)
|
199
92
|
|
200
|
-
return base_array
|
93
|
+
return base_array
|
201
94
|
|
202
|
-
async def _draw_floor(
|
203
|
-
self, map_data: dict, array: NumpyArray, element_map: NumpyArray
|
204
|
-
) -> Tuple[NumpyArray, NumpyArray]:
|
95
|
+
async def _draw_floor(self, map_data: dict, array: NumpyArray) -> NumpyArray:
|
205
96
|
"""Draw the floor layer."""
|
206
97
|
if not self.drawing_config.is_enabled(DrawableElement.FLOOR):
|
207
|
-
return array
|
98
|
+
return array
|
208
99
|
|
209
100
|
# Implementation depends on the map data format
|
210
101
|
# This is a placeholder - actual implementation would use map_data to draw floor
|
211
|
-
# For now, we'll just mark the entire map as floor in the element map
|
212
|
-
element_map[:] = DrawableElement.FLOOR
|
213
102
|
|
214
|
-
return array
|
103
|
+
return array
|
215
104
|
|
216
|
-
async def _draw_walls(
|
217
|
-
self, map_data: dict, array: NumpyArray, element_map: NumpyArray
|
218
|
-
) -> Tuple[NumpyArray, NumpyArray]:
|
105
|
+
async def _draw_walls(self, map_data: dict, array: NumpyArray) -> NumpyArray:
|
219
106
|
"""Draw the walls."""
|
220
107
|
if not self.drawing_config.is_enabled(DrawableElement.WALL):
|
221
|
-
return array
|
108
|
+
return array
|
222
109
|
|
223
110
|
# Get wall color from drawing config
|
224
111
|
wall_color = self.drawing_config.get_property(
|
@@ -240,17 +127,19 @@ class EnhancedDrawable(Drawable):
|
|
240
127
|
|
241
128
|
# Draw wall pixels with color blending
|
242
129
|
for x, y in wall_pixels:
|
243
|
-
# Use
|
244
|
-
|
130
|
+
# Use sample_and_blend_color from ColorsManagement
|
131
|
+
blended_color = ColorsManagement.sample_and_blend_color(
|
132
|
+
array, x, y, wall_color
|
133
|
+
)
|
134
|
+
if 0 <= y < array.shape[0] and 0 <= x < array.shape[1]:
|
135
|
+
array[y, x] = blended_color
|
245
136
|
|
246
|
-
return array
|
137
|
+
return array
|
247
138
|
|
248
|
-
async def _draw_robot(
|
249
|
-
self, map_data: dict, array: NumpyArray, element_map: NumpyArray
|
250
|
-
) -> Tuple[NumpyArray, NumpyArray]:
|
139
|
+
async def _draw_robot(self, map_data: dict, array: NumpyArray) -> NumpyArray:
|
251
140
|
"""Draw the robot."""
|
252
141
|
if not self.drawing_config.is_enabled(DrawableElement.ROBOT):
|
253
|
-
return array
|
142
|
+
return array
|
254
143
|
|
255
144
|
# Get robot color from drawing config
|
256
145
|
robot_color = self.drawing_config.get_property(
|
@@ -271,23 +160,18 @@ class EnhancedDrawable(Drawable):
|
|
271
160
|
for dx in range(-radius, radius + 1):
|
272
161
|
if dx * dx + dy * dy <= radius * radius:
|
273
162
|
map_x, map_y = int(x + dx), int(y + dy)
|
274
|
-
# Use
|
275
|
-
|
276
|
-
array,
|
277
|
-
map_x,
|
278
|
-
map_y,
|
279
|
-
robot_color,
|
280
|
-
DrawableElement.ROBOT,
|
281
|
-
element_map,
|
163
|
+
# Use sample_and_blend_color from ColorsManagement
|
164
|
+
blended_color = ColorsManagement.sample_and_blend_color(
|
165
|
+
array, map_x, map_y, robot_color
|
282
166
|
)
|
283
|
-
|
167
|
+
if 0 <= map_y < array.shape[0] and 0 <= map_x < array.shape[1]:
|
168
|
+
array[map_y, map_x] = blended_color
|
169
|
+
return array
|
284
170
|
|
285
|
-
async def _draw_charger(
|
286
|
-
self, map_data: dict, array: NumpyArray, element_map: NumpyArray
|
287
|
-
) -> Tuple[NumpyArray, NumpyArray]:
|
171
|
+
async def _draw_charger(self, map_data: dict, array: NumpyArray) -> NumpyArray:
|
288
172
|
"""Draw the charger."""
|
289
173
|
if not self.drawing_config.is_enabled(DrawableElement.CHARGER):
|
290
|
-
return array
|
174
|
+
return array
|
291
175
|
|
292
176
|
# Get charger color from drawing config
|
293
177
|
charger_color = self.drawing_config.get_property(
|
@@ -297,14 +181,14 @@ class EnhancedDrawable(Drawable):
|
|
297
181
|
# Implementation depends on the map data format
|
298
182
|
# This would extract charger data from map_data and draw it
|
299
183
|
|
300
|
-
return array
|
184
|
+
return array
|
301
185
|
|
302
186
|
async def _draw_virtual_walls(
|
303
|
-
self, map_data: dict, array: NumpyArray
|
304
|
-
) ->
|
187
|
+
self, map_data: dict, array: NumpyArray
|
188
|
+
) -> NumpyArray:
|
305
189
|
"""Draw virtual walls."""
|
306
190
|
if not self.drawing_config.is_enabled(DrawableElement.VIRTUAL_WALL):
|
307
|
-
return array
|
191
|
+
return array
|
308
192
|
|
309
193
|
# Get virtual wall color from drawing config
|
310
194
|
wall_color = self.drawing_config.get_property(
|
@@ -314,14 +198,14 @@ class EnhancedDrawable(Drawable):
|
|
314
198
|
# Implementation depends on the map data format
|
315
199
|
# This would extract virtual wall data from map_data and draw it
|
316
200
|
|
317
|
-
return array
|
201
|
+
return array
|
318
202
|
|
319
203
|
async def _draw_restricted_areas(
|
320
|
-
self, map_data: dict, array: NumpyArray
|
321
|
-
) ->
|
204
|
+
self, map_data: dict, array: NumpyArray
|
205
|
+
) -> NumpyArray:
|
322
206
|
"""Draw restricted areas."""
|
323
207
|
if not self.drawing_config.is_enabled(DrawableElement.RESTRICTED_AREA):
|
324
|
-
return array
|
208
|
+
return array
|
325
209
|
|
326
210
|
# Get restricted area color from drawing config
|
327
211
|
area_color = self.drawing_config.get_property(
|
@@ -331,14 +215,12 @@ class EnhancedDrawable(Drawable):
|
|
331
215
|
# Implementation depends on the map data format
|
332
216
|
# This would extract restricted area data from map_data and draw it
|
333
217
|
|
334
|
-
return array
|
218
|
+
return array
|
335
219
|
|
336
|
-
async def _draw_no_mop_areas(
|
337
|
-
self, map_data: dict, array: NumpyArray, element_map: NumpyArray
|
338
|
-
) -> Tuple[NumpyArray, NumpyArray]:
|
220
|
+
async def _draw_no_mop_areas(self, map_data: dict, array: NumpyArray) -> NumpyArray:
|
339
221
|
"""Draw no-mop areas."""
|
340
222
|
if not self.drawing_config.is_enabled(DrawableElement.NO_MOP_AREA):
|
341
|
-
return array
|
223
|
+
return array
|
342
224
|
|
343
225
|
# Get no-mop area color from drawing config
|
344
226
|
area_color = self.drawing_config.get_property(
|
@@ -348,14 +230,12 @@ class EnhancedDrawable(Drawable):
|
|
348
230
|
# Implementation depends on the map data format
|
349
231
|
# This would extract no-mop area data from map_data and draw it
|
350
232
|
|
351
|
-
return array
|
233
|
+
return array
|
352
234
|
|
353
|
-
async def _draw_path(
|
354
|
-
self, map_data: dict, array: NumpyArray, element_map: NumpyArray
|
355
|
-
) -> Tuple[NumpyArray, NumpyArray]:
|
235
|
+
async def _draw_path(self, map_data: dict, array: NumpyArray) -> NumpyArray:
|
356
236
|
"""Draw the robot's path."""
|
357
237
|
if not self.drawing_config.is_enabled(DrawableElement.PATH):
|
358
|
-
return array
|
238
|
+
return array
|
359
239
|
|
360
240
|
# Get path color from drawing config
|
361
241
|
path_color = self.drawing_config.get_property(
|
@@ -365,14 +245,14 @@ class EnhancedDrawable(Drawable):
|
|
365
245
|
# Implementation depends on the map data format
|
366
246
|
# This would extract path data from map_data and draw it
|
367
247
|
|
368
|
-
return array
|
248
|
+
return array
|
369
249
|
|
370
250
|
async def _draw_predicted_path(
|
371
|
-
self, map_data: dict, array: NumpyArray
|
372
|
-
) ->
|
251
|
+
self, map_data: dict, array: NumpyArray
|
252
|
+
) -> NumpyArray:
|
373
253
|
"""Draw the predicted path."""
|
374
254
|
if not self.drawing_config.is_enabled(DrawableElement.PREDICTED_PATH):
|
375
|
-
return array
|
255
|
+
return array
|
376
256
|
|
377
257
|
# Get predicted path color from drawing config
|
378
258
|
path_color = self.drawing_config.get_property(
|
@@ -382,14 +262,12 @@ class EnhancedDrawable(Drawable):
|
|
382
262
|
# Implementation depends on the map data format
|
383
263
|
# This would extract predicted path data from map_data and draw it
|
384
264
|
|
385
|
-
return array
|
265
|
+
return array
|
386
266
|
|
387
|
-
async def _draw_go_to_target(
|
388
|
-
self, map_data: dict, array: NumpyArray, element_map: NumpyArray
|
389
|
-
) -> Tuple[NumpyArray, NumpyArray]:
|
267
|
+
async def _draw_go_to_target(self, map_data: dict, array: NumpyArray) -> NumpyArray:
|
390
268
|
"""Draw the go-to target."""
|
391
269
|
if not self.drawing_config.is_enabled(DrawableElement.GO_TO_TARGET):
|
392
|
-
return array
|
270
|
+
return array
|
393
271
|
|
394
272
|
# Get go-to target color from drawing config
|
395
273
|
target_color = self.drawing_config.get_property(
|
@@ -399,15 +277,15 @@ class EnhancedDrawable(Drawable):
|
|
399
277
|
# Implementation depends on the map data format
|
400
278
|
# This would extract go-to target data from map_data and draw it
|
401
279
|
|
402
|
-
return array
|
280
|
+
return array
|
403
281
|
|
404
282
|
async def _draw_room(
|
405
|
-
self, map_data: dict, room_id: int, array: NumpyArray
|
406
|
-
) ->
|
283
|
+
self, map_data: dict, room_id: int, array: NumpyArray
|
284
|
+
) -> NumpyArray:
|
407
285
|
"""Draw a specific room."""
|
408
286
|
element = getattr(DrawableElement, f"ROOM_{room_id}")
|
409
287
|
if not self.drawing_config.is_enabled(element):
|
410
|
-
return array
|
288
|
+
return array
|
411
289
|
|
412
290
|
# Get room color from drawing config
|
413
291
|
room_color = self.drawing_config.get_property(
|
@@ -436,7 +314,11 @@ class EnhancedDrawable(Drawable):
|
|
436
314
|
|
437
315
|
# Draw room pixels with color blending
|
438
316
|
for x, y in room_pixels:
|
439
|
-
# Use
|
440
|
-
|
317
|
+
# Use sample_and_blend_color from ColorsManagement
|
318
|
+
blended_color = ColorsManagement.sample_and_blend_color(
|
319
|
+
array, x, y, room_color
|
320
|
+
)
|
321
|
+
if 0 <= y < array.shape[0] and 0 <= x < array.shape[1]:
|
322
|
+
array[y, x] = blended_color
|
441
323
|
|
442
|
-
return array
|
324
|
+
return array
|
@@ -107,7 +107,6 @@ class CameraShared:
|
|
107
107
|
self.trims = TrimsData.from_dict(DEFAULT_VALUES["trims_data"]) # Trims data
|
108
108
|
self.skip_room_ids: List[str] = []
|
109
109
|
self.device_info = None # Store the device_info
|
110
|
-
self.element_map = None # Map of element codes
|
111
110
|
|
112
111
|
def update_user_colors(self, user_colors):
|
113
112
|
"""Update the user colors."""
|
@@ -8,7 +8,7 @@ import json
|
|
8
8
|
import logging
|
9
9
|
import threading
|
10
10
|
from dataclasses import asdict, dataclass
|
11
|
-
from typing import Any, Dict, Optional, Tuple, Union
|
11
|
+
from typing import Any, Dict, Optional, Tuple, Union, TypedDict
|
12
12
|
|
13
13
|
import numpy as np
|
14
14
|
from PIL import Image
|
@@ -19,17 +19,12 @@ DEFAULT_ROOMS = 1
|
|
19
19
|
LOGGER = logging.getLogger(__package__)
|
20
20
|
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
ImageSize = dict[str, int | list[int]]
|
29
|
-
JsonType = Any # json.loads() return type is Any
|
30
|
-
PilPNG = Image.Image
|
31
|
-
NumpyArray = np.ndarray
|
32
|
-
Point = Tuple[int, int]
|
22
|
+
class RoomProperty(TypedDict):
|
23
|
+
number: int
|
24
|
+
outline: list[tuple[int, int]]
|
25
|
+
name: str
|
26
|
+
x: int
|
27
|
+
y: int
|
33
28
|
|
34
29
|
|
35
30
|
# pylint: disable=no-member
|
@@ -77,21 +72,9 @@ class TrimCropData:
|
|
77
72
|
|
78
73
|
|
79
74
|
class RoomStore:
|
80
|
-
"""
|
81
|
-
Singleton RoomStore per vacuum_id.
|
82
|
-
|
83
|
-
This class stores room data (a dictionary) for each vacuum.
|
84
|
-
Calling RoomStore(vacuum_id, rooms_data) creates a new instance for that vacuum_id if it doesn't exist,
|
85
|
-
or returns the existing one (optionally updating the data if rooms_data is provided).
|
86
|
-
"""
|
87
|
-
|
88
75
|
_instances: Dict[str, "RoomStore"] = {}
|
89
76
|
_lock = threading.Lock()
|
90
77
|
|
91
|
-
# Declare instance attributes for static analysis tools like Pylint
|
92
|
-
vacuum_id: str
|
93
|
-
vacuums_data: dict
|
94
|
-
|
95
78
|
def __new__(cls, vacuum_id: str, rooms_data: Optional[dict] = None) -> "RoomStore":
|
96
79
|
with cls._lock:
|
97
80
|
if vacuum_id not in cls._instances:
|
@@ -100,25 +83,17 @@ class RoomStore:
|
|
100
83
|
instance.vacuums_data = rooms_data or {}
|
101
84
|
cls._instances[vacuum_id] = instance
|
102
85
|
else:
|
103
|
-
# Update the instance's data if new rooms_data is provided.
|
104
86
|
if rooms_data is not None:
|
105
87
|
cls._instances[vacuum_id].vacuums_data = rooms_data
|
106
88
|
return cls._instances[vacuum_id]
|
107
89
|
|
108
90
|
def get_rooms(self) -> dict:
|
109
|
-
"""Return the stored rooms data."""
|
110
91
|
return self.vacuums_data
|
111
92
|
|
112
93
|
def set_rooms(self, rooms_data: dict) -> None:
|
113
|
-
"""Update the stored rooms data."""
|
114
94
|
self.vacuums_data = rooms_data
|
115
95
|
|
116
96
|
def get_rooms_count(self) -> int:
|
117
|
-
"""
|
118
|
-
Return the number of rooms stored for this vacuum.
|
119
|
-
This is simply the number of keys in the vacuums_data dictionary.
|
120
|
-
If no data is stored, return the default number of rooms.
|
121
|
-
"""
|
122
97
|
if isinstance(self.vacuums_data, dict):
|
123
98
|
count = len(self.vacuums_data)
|
124
99
|
return count if count > 0 else DEFAULT_ROOMS
|
@@ -126,7 +101,6 @@ class RoomStore:
|
|
126
101
|
|
127
102
|
@classmethod
|
128
103
|
def get_all_instances(cls) -> Dict[str, "RoomStore"]:
|
129
|
-
"""Return all active RoomStore instances (useful for debugging)."""
|
130
104
|
return cls._instances
|
131
105
|
|
132
106
|
|
@@ -220,6 +194,18 @@ class SnapshotStore:
|
|
220
194
|
self.vacuum_json_data[vacuum_id] = json_data
|
221
195
|
|
222
196
|
|
197
|
+
Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
|
198
|
+
Colors = Dict[str, Color]
|
199
|
+
CalibrationPoints = list[dict[str, Any]]
|
200
|
+
RobotPosition = dict[str, int | float]
|
201
|
+
ChargerPosition = dict[str, Any]
|
202
|
+
RoomsProperties = dict[str, RoomProperty]
|
203
|
+
ImageSize = dict[str, int | list[int]]
|
204
|
+
JsonType = Any # json.loads() return type is Any
|
205
|
+
PilPNG = Image.Image
|
206
|
+
NumpyArray = np.ndarray
|
207
|
+
Point = Tuple[int, int]
|
208
|
+
|
223
209
|
CAMERA_STORAGE = "valetudo_camera"
|
224
210
|
ATTR_ROTATE = "rotate_image"
|
225
211
|
ATTR_CROP = "crop_image"
|
@@ -696,73 +696,6 @@ def blend_pixel(array, x, y, color, element, element_map=None, drawing_config=No
|
|
696
696
|
array[y, x] = blended_color
|
697
697
|
|
698
698
|
|
699
|
-
def get_element_at_position(element_map, x, y):
|
700
|
-
"""
|
701
|
-
Get the element code at a specific position in the element map.
|
702
|
-
|
703
|
-
Args:
|
704
|
-
element_map: The element map array
|
705
|
-
x: X coordinate
|
706
|
-
y: Y coordinate
|
707
|
-
|
708
|
-
Returns:
|
709
|
-
Element code or None if out of bounds
|
710
|
-
"""
|
711
|
-
if element_map is not None:
|
712
|
-
if 0 <= y < element_map.shape[0] and 0 <= x < element_map.shape[1]:
|
713
|
-
return element_map[y, x]
|
714
|
-
return None
|
715
|
-
|
716
|
-
|
717
|
-
def get_room_at_position(element_map, x, y, room_base=101):
|
718
|
-
"""
|
719
|
-
Get the room ID at a specific position, or None if not a room.
|
720
|
-
|
721
|
-
Args:
|
722
|
-
element_map: The element map array
|
723
|
-
x: X coordinate
|
724
|
-
y: Y coordinate
|
725
|
-
room_base: Base value for room elements (default: 101 for DrawableElement.ROOM_1)
|
726
|
-
|
727
|
-
Returns:
|
728
|
-
Room ID (1-15) or None if not a room
|
729
|
-
"""
|
730
|
-
element = get_element_at_position(element_map, x, y)
|
731
|
-
if element is not None and room_base <= element <= room_base + 14: # 15 rooms max
|
732
|
-
return element - room_base + 1
|
733
|
-
return None
|
734
|
-
|
735
|
-
|
736
|
-
def update_element_map_with_robot(
|
737
|
-
element_map, robot_position, robot_element=3, robot_radius=25
|
738
|
-
):
|
739
|
-
"""
|
740
|
-
Update the element map with the robot position.
|
741
|
-
|
742
|
-
Args:
|
743
|
-
element_map: The element map to update
|
744
|
-
robot_position: Tuple of (x, y) coordinates for the robot
|
745
|
-
robot_element: Element code for the robot (default: 3 for DrawableElement.ROBOT)
|
746
|
-
robot_radius: Radius of the robot in pixels
|
747
|
-
|
748
|
-
Returns:
|
749
|
-
None
|
750
|
-
"""
|
751
|
-
if element_map is None or robot_position is None:
|
752
|
-
return
|
753
|
-
|
754
|
-
# Update element map for robot position
|
755
|
-
for dy in range(-robot_radius, robot_radius + 1):
|
756
|
-
for dx in range(-robot_radius, robot_radius + 1):
|
757
|
-
if dx * dx + dy * dy <= robot_radius * robot_radius:
|
758
|
-
rx, ry = (
|
759
|
-
int(robot_position[0] + dx),
|
760
|
-
int(robot_position[1] + dy),
|
761
|
-
)
|
762
|
-
if 0 <= ry < element_map.shape[0] and 0 <= rx < element_map.shape[1]:
|
763
|
-
element_map[ry, rx] = robot_element
|
764
|
-
|
765
|
-
|
766
699
|
def manage_drawable_elements(
|
767
700
|
handler,
|
768
701
|
action,
|