valetudo-map-parser 0.1.2__tar.gz → 0.1.4__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.2 → valetudo_map_parser-0.1.4}/PKG-INFO +2 -2
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/__init__.py +32 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/config/__init__.py +1 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/config/auto_crop.py +288 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/config/colors.py +178 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/config/drawable.py +561 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/config/shared.py +249 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/config/types.py +590 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/hypfer_draw.py +422 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/hypfer_handler.py +418 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/images_utils.py +398 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/map_data.py +510 -0
- valetudo_map_parser-0.1.4/SCR/valetudo_map_parser/py.typed +0 -0
- {valetudo_map_parser-0.1.2 → valetudo_map_parser-0.1.4}/pyproject.toml +4 -4
- valetudo_map_parser-0.1.2/SCR/__init__.py +0 -18
- {valetudo_map_parser-0.1.2 → valetudo_map_parser-0.1.4}/LICENSE +0 -0
- {valetudo_map_parser-0.1.2 → valetudo_map_parser-0.1.4}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.2 → valetudo_map_parser-0.1.4}/README.md +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: valetudo-map-parser
|
3
|
-
Version: 0.1.
|
4
|
-
Summary: A Python library to parse Valetudo map data returning a PIL Image object
|
3
|
+
Version: 0.1.4
|
4
|
+
Summary: A Python library to parse Valetudo map data returning a PIL Image object.
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: Sandro Cantarella
|
7
7
|
Author-email: gsca075@gmail.com
|
@@ -0,0 +1,32 @@
|
|
1
|
+
"""Valetudo map parser.
|
2
|
+
Version: 0.1.4"""
|
3
|
+
|
4
|
+
|
5
|
+
from .hypfer_handler import HypferMapImageHandler
|
6
|
+
from .config.shared import CameraShared, CameraSharedManager
|
7
|
+
from .config.colors import ColorsManagment
|
8
|
+
from .config.drawable import Drawable
|
9
|
+
from .config.types import (
|
10
|
+
SnapshotStore,
|
11
|
+
UserLanguageStore,
|
12
|
+
RoomStore,
|
13
|
+
RoomsProperties,
|
14
|
+
TrimCropData,
|
15
|
+
CameraModes,
|
16
|
+
)
|
17
|
+
|
18
|
+
__all__ = [
|
19
|
+
"HypferMapImageHandler",
|
20
|
+
"CameraShared",
|
21
|
+
"CameraSharedManager",
|
22
|
+
"ColorsManagment",
|
23
|
+
"Drawable",
|
24
|
+
"SnapshotStore",
|
25
|
+
"UserLanguageStore",
|
26
|
+
"UserLanguageStore",
|
27
|
+
"SnapshotStore",
|
28
|
+
"RoomStore",
|
29
|
+
"RoomsProperties",
|
30
|
+
"TrimCropData",
|
31
|
+
"CameraModes",
|
32
|
+
]
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Configuration module for the SCR package."""
|
@@ -0,0 +1,288 @@
|
|
1
|
+
"""Auto Crop Class for trimming and zooming images.
|
2
|
+
Version: 2024.10.0"""
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import logging
|
7
|
+
|
8
|
+
import numpy as np
|
9
|
+
from numpy import rot90
|
10
|
+
|
11
|
+
from .types import Color, NumpyArray, TrimCropData
|
12
|
+
|
13
|
+
_LOGGER = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class TrimError(Exception):
|
17
|
+
"""Exception raised for errors in the trim process."""
|
18
|
+
|
19
|
+
def __init__(self, message, image):
|
20
|
+
super().__init__(message)
|
21
|
+
self.image = image
|
22
|
+
|
23
|
+
|
24
|
+
class AutoCrop:
|
25
|
+
"""Auto Crop Class for trimming and zooming images."""
|
26
|
+
|
27
|
+
def __init__(self, image_handler):
|
28
|
+
self.imh = image_handler
|
29
|
+
self.file_name = self.imh.file_name
|
30
|
+
# self.path_to_data = self.hass.config.path(
|
31
|
+
# STORAGE_DIR, CAMERA_STORAGE, f"auto_crop_{self.file_name}.json"
|
32
|
+
# )
|
33
|
+
|
34
|
+
def check_trim(
|
35
|
+
self, trimmed_height, trimmed_width, margin_size, image_array, file_name, rotate
|
36
|
+
):
|
37
|
+
"""Check if the trim is okay."""
|
38
|
+
if trimmed_height <= margin_size or trimmed_width <= margin_size:
|
39
|
+
self.imh.crop_area = [0, 0, image_array.shape[1], image_array.shape[0]]
|
40
|
+
self.imh.img_size = (image_array.shape[1], image_array.shape[0])
|
41
|
+
raise TrimError(
|
42
|
+
f"{file_name}: Trimming failed at rotation {rotate}.", image_array
|
43
|
+
)
|
44
|
+
|
45
|
+
def _calculate_trimmed_dimensions(self):
|
46
|
+
"""Calculate and update the dimensions after trimming."""
|
47
|
+
trimmed_width = max(
|
48
|
+
0,
|
49
|
+
(
|
50
|
+
(self.imh.trim_right - self.imh.offset_right)
|
51
|
+
- (self.imh.trim_left + self.imh.offset_left)
|
52
|
+
),
|
53
|
+
)
|
54
|
+
trimmed_height = max(
|
55
|
+
0,
|
56
|
+
(
|
57
|
+
(self.imh.trim_down - self.imh.offset_bottom)
|
58
|
+
- (self.imh.trim_up + self.imh.offset_top)
|
59
|
+
),
|
60
|
+
)
|
61
|
+
# Ensure shared reference dimensions are updated
|
62
|
+
if hasattr(self.imh.shared, "image_ref_height") and hasattr(
|
63
|
+
self.imh.shared, "image_ref_width"
|
64
|
+
):
|
65
|
+
self.imh.shared.image_ref_height = trimmed_height
|
66
|
+
self.imh.shared.image_ref_width = trimmed_width
|
67
|
+
else:
|
68
|
+
_LOGGER.warning(
|
69
|
+
"Shared attributes for image dimensions are not initialized."
|
70
|
+
)
|
71
|
+
return trimmed_width, trimmed_height
|
72
|
+
|
73
|
+
async def _async_auto_crop_data(self, tdata=None):
|
74
|
+
"""Load the auto crop data from the disk."""
|
75
|
+
|
76
|
+
if not self.imh.auto_crop:
|
77
|
+
trims_data = TrimCropData.from_dict(dict(tdata)).to_list()
|
78
|
+
(
|
79
|
+
self.imh.trim_left,
|
80
|
+
self.imh.trim_up,
|
81
|
+
self.imh.trim_right,
|
82
|
+
self.imh.trim_down,
|
83
|
+
) = trims_data
|
84
|
+
self._calculate_trimmed_dimensions()
|
85
|
+
return trims_data
|
86
|
+
return None
|
87
|
+
|
88
|
+
def auto_crop_offset(self):
|
89
|
+
"""Calculate the offset for the auto crop."""
|
90
|
+
if self.imh.auto_crop:
|
91
|
+
self.imh.auto_crop[0] += self.imh.offset_left
|
92
|
+
self.imh.auto_crop[1] += self.imh.offset_top
|
93
|
+
self.imh.auto_crop[2] -= self.imh.offset_right
|
94
|
+
self.imh.auto_crop[3] -= self.imh.offset_bottom
|
95
|
+
|
96
|
+
async def _init_auto_crop(self):
|
97
|
+
"""Initialize the auto crop data."""
|
98
|
+
if not self.imh.auto_crop and self.imh.shared.vacuum_state == "docked":
|
99
|
+
self.imh.auto_crop = await self._async_auto_crop_data()
|
100
|
+
if self.imh.auto_crop:
|
101
|
+
self.auto_crop_offset()
|
102
|
+
else:
|
103
|
+
self.imh.max_frames = 5
|
104
|
+
return self.imh.auto_crop
|
105
|
+
|
106
|
+
# async def _async_save_auto_crop_data(self):
|
107
|
+
# """Save the auto crop data to the disk."""
|
108
|
+
# try:
|
109
|
+
# if not os.path.exists(self.path_to_data):
|
110
|
+
# data = TrimCropData(
|
111
|
+
# self.imh.trim_left,
|
112
|
+
# self.imh.trim_up,
|
113
|
+
# self.imh.trim_right,
|
114
|
+
# self.imh.trim_down,
|
115
|
+
# ).to_dict()
|
116
|
+
# except Exception as e:
|
117
|
+
# _LOGGER.error(f"Failed to save trim data due to an error: {e}")
|
118
|
+
|
119
|
+
async def async_image_margins(
|
120
|
+
self, image_array: NumpyArray, detect_colour: Color
|
121
|
+
) -> tuple[int, int, int, int]:
|
122
|
+
"""Crop the image based on the auto crop area."""
|
123
|
+
nonzero_coords = np.column_stack(np.where(image_array != list(detect_colour)))
|
124
|
+
# Calculate the trim box based on the first and last occurrences
|
125
|
+
min_y, min_x, _ = NumpyArray.min(nonzero_coords, axis=0)
|
126
|
+
max_y, max_x, _ = NumpyArray.max(nonzero_coords, axis=0)
|
127
|
+
del nonzero_coords
|
128
|
+
_LOGGER.debug(
|
129
|
+
"%s: Found trims max and min values (y,x) (%s, %s) (%s, %s)...",
|
130
|
+
self.file_name,
|
131
|
+
int(max_y),
|
132
|
+
int(max_x),
|
133
|
+
int(min_y),
|
134
|
+
int(min_x),
|
135
|
+
)
|
136
|
+
return min_y, min_x, max_x, max_y
|
137
|
+
|
138
|
+
async def async_check_if_zoom_is_on(
|
139
|
+
self,
|
140
|
+
image_array: NumpyArray,
|
141
|
+
margin_size: int = 100,
|
142
|
+
zoom: bool = False,
|
143
|
+
rand256: bool = False,
|
144
|
+
) -> NumpyArray:
|
145
|
+
"""Check if the image need to be zoom."""
|
146
|
+
|
147
|
+
if (
|
148
|
+
zoom
|
149
|
+
and self.imh.shared.vacuum_state == "cleaning"
|
150
|
+
and self.imh.shared.image_auto_zoom
|
151
|
+
):
|
152
|
+
# Zoom the image based on the robot's position.
|
153
|
+
_LOGGER.debug(
|
154
|
+
"%s: Zooming the image on room %s.",
|
155
|
+
self.file_name,
|
156
|
+
self.imh.robot_in_room["room"],
|
157
|
+
)
|
158
|
+
if rand256:
|
159
|
+
trim_left = round(self.imh.robot_in_room["right"] / 10) - margin_size
|
160
|
+
trim_right = round(self.imh.robot_in_room["left"] / 10) + margin_size
|
161
|
+
trim_up = round(self.imh.robot_in_room["down"] / 10) - margin_size
|
162
|
+
trim_down = round(self.imh.robot_in_room["up"] / 10) + margin_size
|
163
|
+
else:
|
164
|
+
trim_left = self.imh.robot_in_room["left"] - margin_size
|
165
|
+
trim_right = self.imh.robot_in_room["right"] + margin_size
|
166
|
+
trim_up = self.imh.robot_in_room["up"] - margin_size
|
167
|
+
trim_down = self.imh.robot_in_room["down"] + margin_size
|
168
|
+
trim_left, trim_right = sorted([trim_left, trim_right])
|
169
|
+
trim_up, trim_down = sorted([trim_up, trim_down])
|
170
|
+
trimmed = image_array[trim_up:trim_down, trim_left:trim_right]
|
171
|
+
else:
|
172
|
+
# Apply the auto-calculated trims to the rotated image
|
173
|
+
trimmed = image_array[
|
174
|
+
self.imh.auto_crop[1] : self.imh.auto_crop[3],
|
175
|
+
self.imh.auto_crop[0] : self.imh.auto_crop[2],
|
176
|
+
]
|
177
|
+
return trimmed
|
178
|
+
|
179
|
+
async def async_rotate_the_image(
|
180
|
+
self, trimmed: NumpyArray, rotate: int
|
181
|
+
) -> NumpyArray:
|
182
|
+
"""Rotate the image and return the new array."""
|
183
|
+
if rotate == 90:
|
184
|
+
rotated = rot90(trimmed)
|
185
|
+
self.imh.crop_area = [
|
186
|
+
self.imh.trim_left,
|
187
|
+
self.imh.trim_up,
|
188
|
+
self.imh.trim_right,
|
189
|
+
self.imh.trim_down,
|
190
|
+
]
|
191
|
+
elif rotate == 180:
|
192
|
+
rotated = rot90(trimmed, 2)
|
193
|
+
self.imh.crop_area = self.imh.auto_crop
|
194
|
+
elif rotate == 270:
|
195
|
+
rotated = rot90(trimmed, 3)
|
196
|
+
self.imh.crop_area = [
|
197
|
+
self.imh.trim_left,
|
198
|
+
self.imh.trim_up,
|
199
|
+
self.imh.trim_right,
|
200
|
+
self.imh.trim_down,
|
201
|
+
]
|
202
|
+
else:
|
203
|
+
rotated = trimmed
|
204
|
+
self.imh.crop_area = self.imh.auto_crop
|
205
|
+
return rotated
|
206
|
+
|
207
|
+
async def async_auto_trim_and_zoom_image(
|
208
|
+
self,
|
209
|
+
image_array: NumpyArray,
|
210
|
+
detect_colour: Color = (93, 109, 126, 255),
|
211
|
+
margin_size: int = 0,
|
212
|
+
rotate: int = 0,
|
213
|
+
zoom: bool = False,
|
214
|
+
rand256: bool = False,
|
215
|
+
):
|
216
|
+
"""
|
217
|
+
Automatically crops and trims a numpy array and returns the processed image.
|
218
|
+
"""
|
219
|
+
try:
|
220
|
+
await self._init_auto_crop()
|
221
|
+
if self.imh.auto_crop is None:
|
222
|
+
_LOGGER.debug("%s: Calculating auto trim box", self.file_name)
|
223
|
+
# Find the coordinates of the first occurrence of a non-background color
|
224
|
+
min_y, min_x, max_x, max_y = await self.async_image_margins(
|
225
|
+
image_array, detect_colour
|
226
|
+
)
|
227
|
+
# Calculate and store the trims coordinates with margins
|
228
|
+
self.imh.trim_left = int(min_x) - margin_size
|
229
|
+
self.imh.trim_up = int(min_y) - margin_size
|
230
|
+
self.imh.trim_right = int(max_x) + margin_size
|
231
|
+
self.imh.trim_down = int(max_y) + margin_size
|
232
|
+
del min_y, min_x, max_x, max_y
|
233
|
+
|
234
|
+
# Calculate the dimensions after trimming using min/max values
|
235
|
+
trimmed_width, trimmed_height = self._calculate_trimmed_dimensions()
|
236
|
+
|
237
|
+
# Test if the trims are okay or not
|
238
|
+
try:
|
239
|
+
self.check_trim(
|
240
|
+
trimmed_height,
|
241
|
+
trimmed_width,
|
242
|
+
margin_size,
|
243
|
+
image_array,
|
244
|
+
self.file_name,
|
245
|
+
rotate,
|
246
|
+
)
|
247
|
+
except TrimError as e:
|
248
|
+
return e.image
|
249
|
+
|
250
|
+
# Store Crop area of the original image_array we will use from the next frame.
|
251
|
+
self.imh.auto_crop = TrimCropData(
|
252
|
+
self.imh.trim_left,
|
253
|
+
self.imh.trim_up,
|
254
|
+
self.imh.trim_right,
|
255
|
+
self.imh.trim_down,
|
256
|
+
).to_list()
|
257
|
+
# if self.imh.shared.vacuum_state == "docked":
|
258
|
+
# await (
|
259
|
+
# self._async_save_auto_crop_data()
|
260
|
+
# ) # Save the crop data to the disk
|
261
|
+
self.auto_crop_offset()
|
262
|
+
# If it is needed to zoom the image.
|
263
|
+
trimmed = await self.async_check_if_zoom_is_on(
|
264
|
+
image_array, margin_size, zoom, rand256
|
265
|
+
)
|
266
|
+
del image_array # Free memory.
|
267
|
+
# Rotate the cropped image based on the given angle
|
268
|
+
rotated = await self.async_rotate_the_image(trimmed, rotate)
|
269
|
+
del trimmed # Free memory.
|
270
|
+
_LOGGER.debug(
|
271
|
+
"%s: Auto Trim Box data: %s", self.file_name, self.imh.crop_area
|
272
|
+
)
|
273
|
+
self.imh.crop_img_size = [rotated.shape[1], rotated.shape[0]]
|
274
|
+
_LOGGER.debug(
|
275
|
+
"%s: Auto Trimmed image size: %s",
|
276
|
+
self.file_name,
|
277
|
+
self.imh.crop_img_size,
|
278
|
+
)
|
279
|
+
|
280
|
+
except RuntimeError as e:
|
281
|
+
_LOGGER.warning(
|
282
|
+
"%s: Error %s during auto trim and zoom.",
|
283
|
+
self.file_name,
|
284
|
+
e,
|
285
|
+
exc_info=True,
|
286
|
+
)
|
287
|
+
return None
|
288
|
+
return rotated
|
@@ -0,0 +1,178 @@
|
|
1
|
+
"""Colors for the maps Elements."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from enum import StrEnum
|
6
|
+
from typing import List, Dict, Tuple
|
7
|
+
import logging
|
8
|
+
|
9
|
+
_LOGGER = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
Color = Tuple[int, int, int, int] # RGBA color definition
|
12
|
+
|
13
|
+
|
14
|
+
class SupportedColor(StrEnum):
|
15
|
+
"""Color of a supported map element."""
|
16
|
+
|
17
|
+
CHARGER = "color_charger"
|
18
|
+
PATH = "color_move"
|
19
|
+
PREDICTED_PATH = "color_predicted_move"
|
20
|
+
WALLS = "color_wall"
|
21
|
+
ROBOT = "color_robot"
|
22
|
+
GO_TO = "color_go_to"
|
23
|
+
NO_GO = "color_no_go"
|
24
|
+
ZONE_CLEAN = "color_zone_clean"
|
25
|
+
MAP_BACKGROUND = "color_background"
|
26
|
+
TEXT = "color_text"
|
27
|
+
TRANSPARENT = "color_transparent"
|
28
|
+
COLOR_ROOM_PREFIX = "color_room_"
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
def room_key(index: int) -> str:
|
32
|
+
return f"{SupportedColor.COLOR_ROOM_PREFIX}{index}"
|
33
|
+
|
34
|
+
|
35
|
+
class DefaultColors:
|
36
|
+
"""Container that simplifies retrieving default RGB and RGBA colors."""
|
37
|
+
|
38
|
+
COLORS_RGB: Dict[str, Tuple[int, int, int]] = {
|
39
|
+
SupportedColor.CHARGER: (255, 128, 0),
|
40
|
+
SupportedColor.PATH: (238, 247, 255),
|
41
|
+
SupportedColor.PREDICTED_PATH: (93, 109, 126),
|
42
|
+
SupportedColor.WALLS: (255, 255, 0),
|
43
|
+
SupportedColor.ROBOT: (255, 255, 204),
|
44
|
+
SupportedColor.GO_TO: (0, 255, 0),
|
45
|
+
SupportedColor.NO_GO: (255, 0, 0),
|
46
|
+
SupportedColor.ZONE_CLEAN: (255, 255, 255),
|
47
|
+
SupportedColor.MAP_BACKGROUND: (0, 125, 255),
|
48
|
+
SupportedColor.TEXT: (0, 0, 0),
|
49
|
+
SupportedColor.TRANSPARENT: (0, 0, 0),
|
50
|
+
}
|
51
|
+
|
52
|
+
DEFAULT_ROOM_COLORS: Dict[str, Tuple[int, int, int]] = {
|
53
|
+
SupportedColor.room_key(i): color
|
54
|
+
for i, color in enumerate(
|
55
|
+
[
|
56
|
+
(135, 206, 250),
|
57
|
+
(176, 226, 255),
|
58
|
+
(165, 105, 18),
|
59
|
+
(164, 211, 238),
|
60
|
+
(141, 182, 205),
|
61
|
+
(96, 123, 139),
|
62
|
+
(224, 255, 255),
|
63
|
+
(209, 238, 238),
|
64
|
+
(180, 205, 205),
|
65
|
+
(122, 139, 139),
|
66
|
+
(175, 238, 238),
|
67
|
+
(84, 153, 199),
|
68
|
+
(133, 193, 233),
|
69
|
+
(245, 176, 65),
|
70
|
+
(82, 190, 128),
|
71
|
+
(72, 201, 176),
|
72
|
+
]
|
73
|
+
)
|
74
|
+
}
|
75
|
+
|
76
|
+
DEFAULT_ALPHA: Dict[str, float] = {
|
77
|
+
f"alpha_{key}": 255.0 for key in COLORS_RGB.keys()
|
78
|
+
}
|
79
|
+
DEFAULT_ALPHA.update({f"alpha_room_{i}": 255.0 for i in range(16)})
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
def get_rgba(cls, key: str, alpha: float) -> Color:
|
83
|
+
rgb = cls.COLORS_RGB.get(key, (0, 0, 0))
|
84
|
+
r, g, b = rgb # Explicitly unpack the RGB values
|
85
|
+
return r, g, b, int(alpha)
|
86
|
+
|
87
|
+
|
88
|
+
class ColorsManagment:
|
89
|
+
"""Manages user-defined and default colors for map elements."""
|
90
|
+
|
91
|
+
def __init__(self, device_info: dict) -> None:
|
92
|
+
"""
|
93
|
+
Initialize ColorsManagment with optional device_info from Home Assistant.
|
94
|
+
:param device_info: Dictionary containing user-defined RGB colors and alpha values.
|
95
|
+
"""
|
96
|
+
self.user_colors = self.initialize_user_colors(device_info)
|
97
|
+
self.rooms_colors = self.initialize_rooms_colors(device_info)
|
98
|
+
|
99
|
+
def initialize_user_colors(self, device_info: dict) -> List[Color]:
|
100
|
+
"""
|
101
|
+
Initialize user-defined colors with defaults as fallback.
|
102
|
+
:param device_info: Dictionary containing user-defined colors.
|
103
|
+
:return: List of RGBA colors for map elements.
|
104
|
+
"""
|
105
|
+
colors = []
|
106
|
+
for key in SupportedColor:
|
107
|
+
if key.startswith(SupportedColor.COLOR_ROOM_PREFIX):
|
108
|
+
continue # Skip room colors for user_colors
|
109
|
+
rgb = device_info.get(key, DefaultColors.COLORS_RGB.get(key))
|
110
|
+
alpha = device_info.get(
|
111
|
+
f"alpha_{key}", DefaultColors.DEFAULT_ALPHA.get(f"alpha_{key}")
|
112
|
+
)
|
113
|
+
colors.append(self.add_alpha_to_color(rgb, alpha))
|
114
|
+
return colors
|
115
|
+
|
116
|
+
def initialize_rooms_colors(self, device_info: dict) -> List[Color]:
|
117
|
+
"""
|
118
|
+
Initialize room colors with defaults as fallback.
|
119
|
+
:param device_info: Dictionary containing user-defined room colors.
|
120
|
+
:return: List of RGBA colors for rooms.
|
121
|
+
"""
|
122
|
+
colors = []
|
123
|
+
for i in range(16):
|
124
|
+
rgb = device_info.get(
|
125
|
+
SupportedColor.room_key(i),
|
126
|
+
DefaultColors.DEFAULT_ROOM_COLORS.get(SupportedColor.room_key(i)),
|
127
|
+
)
|
128
|
+
alpha = device_info.get(
|
129
|
+
f"alpha_room_{i}", DefaultColors.DEFAULT_ALPHA.get(f"alpha_room_{i}")
|
130
|
+
)
|
131
|
+
colors.append(self.add_alpha_to_color(rgb, alpha))
|
132
|
+
return colors
|
133
|
+
|
134
|
+
@staticmethod
|
135
|
+
def add_alpha_to_color(rgb: Tuple[int, int, int], alpha: float) -> Color:
|
136
|
+
"""
|
137
|
+
Convert RGB to RGBA by appending the alpha value.
|
138
|
+
:param rgb: RGB values.
|
139
|
+
:param alpha: Alpha value (0.0 to 255.0).
|
140
|
+
:return: RGBA color.
|
141
|
+
"""
|
142
|
+
return (*rgb, int(alpha)) if rgb else (0, 0, 0, int(alpha))
|
143
|
+
|
144
|
+
def get_user_colors(self) -> List[Color]:
|
145
|
+
"""Return the list of RGBA colors for user-defined map elements."""
|
146
|
+
return self.user_colors
|
147
|
+
|
148
|
+
def get_rooms_colors(self) -> List[Color]:
|
149
|
+
"""Return the list of RGBA colors for rooms."""
|
150
|
+
return self.rooms_colors
|
151
|
+
|
152
|
+
def get_colour(self, supported_color: SupportedColor) -> Color:
|
153
|
+
"""
|
154
|
+
Retrieve the color for a specific map element, prioritizing user-defined values.
|
155
|
+
|
156
|
+
:param supported_color: The SupportedColor key for the desired color.
|
157
|
+
:return: The RGBA color for the given map element.
|
158
|
+
"""
|
159
|
+
# Handle room-specific colors
|
160
|
+
if supported_color.startswith("color_room_"):
|
161
|
+
room_index = int(supported_color.split("_")[-1])
|
162
|
+
try:
|
163
|
+
return self.rooms_colors[room_index]
|
164
|
+
except (IndexError, KeyError):
|
165
|
+
_LOGGER.warning("Room index %s not found, using default.", room_index)
|
166
|
+
r, g, b = DefaultColors.DEFAULT_ROOM_COLORS[f"color_room_{room_index}"]
|
167
|
+
a = DefaultColors.DEFAULT_ALPHA[f"alpha_room_{room_index}"]
|
168
|
+
return r, g, b, int(a)
|
169
|
+
|
170
|
+
# Handle general map element colors
|
171
|
+
try:
|
172
|
+
index = list(SupportedColor).index(supported_color)
|
173
|
+
return self.user_colors[index]
|
174
|
+
except (IndexError, KeyError, ValueError):
|
175
|
+
_LOGGER.warning(
|
176
|
+
"Color for %s not found. Returning default.", supported_color
|
177
|
+
)
|
178
|
+
return DefaultColors.get_rgba(supported_color, 255) # Transparent fallback
|