valetudo-map-parser 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -0,0 +1,590 @@
1
+ """
2
+ This module contains type aliases for the project.
3
+ Version 0.0.1
4
+ """
5
+
6
+ import asyncio
7
+ from dataclasses import dataclass
8
+ import json
9
+ import logging
10
+ from typing import Any, Dict, Tuple, Union
11
+
12
+ from PIL import Image
13
+ import numpy as np
14
+
15
+ DEFAULT_ROOMS = 1
16
+
17
+ MY_LOGGER = logging.getLogger(__name__)
18
+
19
+ Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
20
+ Colors = Dict[str, Color]
21
+ CalibrationPoints = list[dict[str, Any]]
22
+ RobotPosition = dict[str, int | float]
23
+ ChargerPosition = dict[str, Any]
24
+ RoomsProperties = dict[str, dict[str, int | list[tuple[Any, Any]]]]
25
+ ImageSize = dict[str, int | list[int]]
26
+ JsonType = Any # json.loads() return type is Any
27
+ PilPNG = Image.Image
28
+ NumpyArray = np.ndarray
29
+ Point = Tuple[int, int]
30
+
31
+
32
+ # pylint: disable=no-member
33
+ @dataclass
34
+ class TrimCropData:
35
+ """Dataclass for trim and crop data."""
36
+
37
+ trim_left: int
38
+ trim_up: int
39
+ trim_right: int
40
+ trim_down: int
41
+
42
+ def to_dict(self) -> dict:
43
+ """Convert dataclass to dictionary."""
44
+ return {
45
+ "trim_left": self.trim_left,
46
+ "trim_up": self.trim_up,
47
+ "trim_right": self.trim_right,
48
+ "trim_down": self.trim_down,
49
+ }
50
+
51
+ @staticmethod
52
+ def from_dict(data: dict):
53
+ """Create dataclass from dictionary."""
54
+ return TrimCropData(
55
+ trim_left=data["trim_left"],
56
+ trim_up=data["trim_up"],
57
+ trim_right=data["trim_right"],
58
+ trim_down=data["trim_down"],
59
+ )
60
+
61
+ def to_list(self) -> list:
62
+ """Convert dataclass to list."""
63
+ return [self.trim_left, self.trim_up, self.trim_right, self.trim_down]
64
+
65
+ @staticmethod
66
+ def from_list(data: list):
67
+ """Create dataclass from list."""
68
+ return TrimCropData(
69
+ trim_left=data[0],
70
+ trim_up=data[1],
71
+ trim_right=data[2],
72
+ trim_down=data[3],
73
+ )
74
+
75
+
76
+ # pylint: disable=no-member
77
+ class RoomStore:
78
+ """Store the room data for the vacuum."""
79
+
80
+ _instance = None
81
+ _lock = asyncio.Lock()
82
+
83
+ def __init__(self):
84
+ self.vacuums_data = {}
85
+
86
+ def __new__(cls):
87
+ if cls._instance is None:
88
+ cls._instance = super(RoomStore, cls).__new__(cls)
89
+ cls._instance.vacuums_data = {}
90
+ return cls._instance
91
+
92
+ async def async_set_rooms_data(self, vacuum_id: str, rooms_data: dict) -> None:
93
+ """Set the room data for the vacuum."""
94
+ async with self._lock:
95
+ self.vacuums_data[vacuum_id] = rooms_data
96
+
97
+ async def async_get_rooms_data(self, vacuum_id: str) -> dict:
98
+ """Get the room data for a vacuum."""
99
+ async with self._lock:
100
+ data = self.vacuums_data.get(vacuum_id, {})
101
+ if isinstance(data, str):
102
+ json_data = json.loads(data)
103
+ return json_data
104
+ return data
105
+
106
+ async def async_get_rooms_count(self, vacuum_id: str) -> int:
107
+ """Count the number of rooms for a vacuum."""
108
+ async with self._lock:
109
+ count = len(self.vacuums_data.get(vacuum_id, {}))
110
+ if count == 0:
111
+ return DEFAULT_ROOMS
112
+ return count
113
+
114
+
115
+ # pylint: disable=no-member
116
+ class UserLanguageStore:
117
+ """Store the user language data."""
118
+
119
+ _instance = None
120
+ _lock = asyncio.Lock()
121
+ _initialized = False
122
+
123
+ def __init__(self):
124
+ self.user_languages = {}
125
+
126
+ def __new__(cls):
127
+ if cls._instance is None:
128
+ cls._instance = super(UserLanguageStore, cls).__new__(cls)
129
+ cls._instance.user_languages = {}
130
+ return cls._instance
131
+
132
+ async def set_user_language(self, user_id: str, language: str) -> None:
133
+ """Set the user language."""
134
+ async with self._lock:
135
+ self.user_languages[user_id] = language
136
+
137
+ async def get_user_language(self, user_id: str) -> str or None:
138
+ """Get the user language."""
139
+ async with self._lock:
140
+ return self.user_languages.get(user_id, None)
141
+
142
+ async def get_all_languages(self):
143
+ """Get all the user languages."""
144
+ async with self._lock:
145
+ if not self.user_languages:
146
+ return ["en"]
147
+ return list(self.user_languages.values())
148
+
149
+ @classmethod
150
+ async def is_initialized(cls):
151
+ """Return if the instance is initialized."""
152
+ async with cls._lock:
153
+ return bool(cls._initialized)
154
+
155
+ @classmethod
156
+ async def initialize_if_needed(cls, other_instance=None):
157
+ """Initialize the instance if needed by copying from another instance if available."""
158
+ async with cls._lock:
159
+ if not cls._initialized and other_instance is not None:
160
+ cls._instance.user_languages = other_instance.user_languages
161
+ cls._initialized = True
162
+
163
+
164
+ # pylint: disable=no-member
165
+ class SnapshotStore:
166
+ """Store the snapshot data."""
167
+
168
+ _instance = None
169
+ _lock = asyncio.Lock()
170
+
171
+ def __init__(self):
172
+ self.snapshot_save_data = {}
173
+ self.vacuum_json_data = {}
174
+
175
+ def __new__(cls):
176
+ if cls._instance is None:
177
+ cls._instance = super(SnapshotStore, cls).__new__(cls)
178
+ cls._instance.snapshot_save_data = {}
179
+ cls._instance.vacuum_json_data = {}
180
+ return cls._instance
181
+
182
+ async def async_set_snapshot_save_data(
183
+ self, vacuum_id: str, snapshot_data: bool = False
184
+ ) -> None:
185
+ """Set the snapshot save data for the vacuum."""
186
+ async with self._lock:
187
+ self.snapshot_save_data[vacuum_id] = snapshot_data
188
+
189
+ async def async_get_snapshot_save_data(self, vacuum_id: str) -> bool:
190
+ """Get the snapshot save data for a vacuum."""
191
+ async with self._lock:
192
+ return self.snapshot_save_data.get(vacuum_id, False)
193
+
194
+ async def async_get_vacuum_json(self, vacuum_id: str) -> Any:
195
+ """Get the JSON data for a vacuum."""
196
+ async with self._lock:
197
+ return self.vacuum_json_data.get(vacuum_id, {})
198
+
199
+ async def async_set_vacuum_json(self, vacuum_id: str, json_data: Any) -> None:
200
+ """Set the JSON data for the vacuum."""
201
+ async with self._lock:
202
+ self.vacuum_json_data[vacuum_id] = json_data
203
+
204
+
205
+ CAMERA_STORAGE = "valetudo_camera"
206
+ DEFAULT_ROOMS = 1 # 15 is the maximum number of rooms.
207
+ ATTR_ROTATE = "rotate_image"
208
+ ATTR_CROP = "crop_image"
209
+ ATTR_MARGINS = "margins"
210
+ CONF_OFFSET_TOP = "offset_top"
211
+ CONF_OFFSET_BOTTOM = "offset_bottom"
212
+ CONF_OFFSET_LEFT = "offset_left"
213
+ CONF_OFFSET_RIGHT = "offset_right"
214
+ CONF_ASPECT_RATIO = "aspect_ratio"
215
+ CONF_VAC_STAT = "show_vac_status"
216
+ CONF_VAC_STAT_SIZE = "vac_status_size"
217
+ CONF_VAC_STAT_POS = "vac_status_position"
218
+ CONF_VAC_STAT_FONT = "vac_status_font"
219
+ CONF_VACUUM_CONNECTION_STRING = "vacuum_map"
220
+ CONF_VACUUM_ENTITY_ID = "vacuum_entity"
221
+ CONF_VACUUM_CONFIG_ENTRY_ID = "vacuum_config_entry"
222
+ CONF_VACUUM_IDENTIFIERS = "vacuum_identifiers"
223
+ CONF_SNAPSHOTS_ENABLE = "enable_www_snapshots"
224
+ CONF_EXPORT_SVG = "get_svg_file"
225
+ CONF_AUTO_ZOOM = "auto_zoom"
226
+ CONF_ZOOM_LOCK_RATIO = "zoom_lock_ratio"
227
+ CONF_TRIMS_SAVE = "save_trims"
228
+ ICON = "mdi:camera"
229
+ NAME = "MQTT Vacuum Camera"
230
+
231
+ DEFAULT_IMAGE_SIZE = {
232
+ "x": 5120,
233
+ "y": 5120,
234
+ "centre": [(5120 // 2), (5120 // 2)],
235
+ }
236
+
237
+ COLORS = [
238
+ "wall",
239
+ "zone_clean",
240
+ "robot",
241
+ "background",
242
+ "move",
243
+ "charger",
244
+ "no_go",
245
+ "go_to",
246
+ ]
247
+
248
+ SENSOR_NO_DATA = {
249
+ "mainBrush": 0,
250
+ "sideBrush": 0,
251
+ "filter": 0,
252
+ "currentCleanTime": 0,
253
+ "currentCleanArea": 0,
254
+ "cleanTime": 0,
255
+ "cleanArea": 0,
256
+ "cleanCount": 0,
257
+ "battery": 0,
258
+ "state": 0,
259
+ "last_run_start": 0,
260
+ "last_run_end": 0,
261
+ "last_run_duration": 0,
262
+ "last_run_area": 0,
263
+ "last_bin_out": 0,
264
+ "last_bin_full": 0,
265
+ "last_loaded_map": "NoMap",
266
+ "robot_in_room": "Unsupported",
267
+ }
268
+
269
+ DEFAULT_PIXEL_SIZE = 5
270
+
271
+ DEFAULT_VALUES = {
272
+ "rotate_image": "0",
273
+ "margins": "100",
274
+ "aspect_ratio": "None",
275
+ "offset_top": 0,
276
+ "offset_bottom": 0,
277
+ "offset_left": 0,
278
+ "offset_right": 0,
279
+ "auto_zoom": False,
280
+ "zoom_lock_ratio": True,
281
+ "show_vac_status": False,
282
+ "vac_status_font": "custom_components/mqtt_vacuum_camera/utils/fonts/FiraSans.ttf",
283
+ "vac_status_size": 50,
284
+ "vac_status_position": True,
285
+ "get_svg_file": False,
286
+ "save_trims": True,
287
+ "enable_www_snapshots": False,
288
+ "color_charger": [255, 128, 0],
289
+ "color_move": [238, 247, 255],
290
+ "color_wall": [255, 255, 0],
291
+ "color_robot": [255, 255, 204],
292
+ "color_go_to": [0, 255, 0],
293
+ "color_no_go": [255, 0, 0],
294
+ "color_zone_clean": [255, 255, 255],
295
+ "color_background": [0, 125, 255],
296
+ "color_text": [255, 255, 255],
297
+ "alpha_charger": 255.0,
298
+ "alpha_move": 255.0,
299
+ "alpha_wall": 255.0,
300
+ "alpha_robot": 255.0,
301
+ "alpha_go_to": 255.0,
302
+ "alpha_no_go": 125.0,
303
+ "alpha_zone_clean": 125.0,
304
+ "alpha_background": 255.0,
305
+ "alpha_text": 255.0,
306
+ "color_room_0": [135, 206, 250],
307
+ "color_room_1": [176, 226, 255],
308
+ "color_room_2": [165, 105, 18],
309
+ "color_room_3": [164, 211, 238],
310
+ "color_room_4": [141, 182, 205],
311
+ "color_room_5": [96, 123, 139],
312
+ "color_room_6": [224, 255, 255],
313
+ "color_room_7": [209, 238, 238],
314
+ "color_room_8": [180, 205, 205],
315
+ "color_room_9": [122, 139, 139],
316
+ "color_room_10": [175, 238, 238],
317
+ "color_room_11": [84, 153, 199],
318
+ "color_room_12": [133, 193, 233],
319
+ "color_room_13": [245, 176, 65],
320
+ "color_room_14": [82, 190, 128],
321
+ "color_room_15": [72, 201, 176],
322
+ "alpha_room_0": 255.0,
323
+ "alpha_room_1": 255.0,
324
+ "alpha_room_2": 255.0,
325
+ "alpha_room_3": 255.0,
326
+ "alpha_room_4": 255.0,
327
+ "alpha_room_5": 255.0,
328
+ "alpha_room_6": 255.0,
329
+ "alpha_room_7": 255.0,
330
+ "alpha_room_8": 255.0,
331
+ "alpha_room_9": 255.0,
332
+ "alpha_room_10": 255.0,
333
+ "alpha_room_11": 255.0,
334
+ "alpha_room_12": 255.0,
335
+ "alpha_room_13": 255.0,
336
+ "alpha_room_14": 255.0,
337
+ "alpha_room_15": 255.0,
338
+ }
339
+
340
+ KEYS_TO_UPDATE = [
341
+ "rotate_image",
342
+ "margins",
343
+ "aspect_ratio",
344
+ "offset_top",
345
+ "offset_bottom",
346
+ "offset_left",
347
+ "offset_right",
348
+ "auto_zoom",
349
+ "zoom_lock_ratio",
350
+ "show_vac_status",
351
+ "vac_status_size",
352
+ "vac_status_position",
353
+ "vac_status_font",
354
+ "get_svg_file",
355
+ "enable_www_snapshots",
356
+ "color_charger",
357
+ "color_move",
358
+ "color_wall",
359
+ "color_robot",
360
+ "color_go_to",
361
+ "color_no_go",
362
+ "color_zone_clean",
363
+ "color_background",
364
+ "color_text",
365
+ "alpha_charger",
366
+ "alpha_move",
367
+ "alpha_wall",
368
+ "alpha_robot",
369
+ "alpha_go_to",
370
+ "alpha_no_go",
371
+ "alpha_zone_clean",
372
+ "alpha_background",
373
+ "alpha_text",
374
+ "color_room_0",
375
+ "color_room_1",
376
+ "color_room_2",
377
+ "color_room_3",
378
+ "color_room_4",
379
+ "color_room_5",
380
+ "color_room_6",
381
+ "color_room_7",
382
+ "color_room_8",
383
+ "color_room_9",
384
+ "color_room_10",
385
+ "color_room_11",
386
+ "color_room_12",
387
+ "color_room_13",
388
+ "color_room_14",
389
+ "color_room_15",
390
+ "alpha_room_0",
391
+ "alpha_room_1",
392
+ "alpha_room_2",
393
+ "alpha_room_3",
394
+ "alpha_room_4",
395
+ "alpha_room_5",
396
+ "alpha_room_6",
397
+ "alpha_room_7",
398
+ "alpha_room_8",
399
+ "alpha_room_9",
400
+ "alpha_room_10",
401
+ "alpha_room_11",
402
+ "alpha_room_12",
403
+ "alpha_room_13",
404
+ "alpha_room_14",
405
+ "alpha_room_15",
406
+ ]
407
+
408
+ ALPHA_VALUES = {
409
+ "min": 0.0, # Minimum value
410
+ "max": 255.0, # Maximum value
411
+ "step": 1.0, # Step value
412
+ }
413
+
414
+ TEXT_SIZE_VALUES = {
415
+ "min": 5, # Minimum value
416
+ "max": 51, # Maximum value
417
+ "step": 1, # Step value
418
+ }
419
+
420
+ ROTATION_VALUES = [
421
+ {"label": "0", "value": "0"},
422
+ {"label": "90", "value": "90"},
423
+ {"label": "180", "value": "180"},
424
+ {"label": "270", "value": "270"},
425
+ ]
426
+
427
+ RATIO_VALUES = [
428
+ {"label": "Original Ratio.", "value": "None"},
429
+ {"label": "1:1", "value": "1, 1"},
430
+ {"label": "2:1", "value": "2, 1"},
431
+ {"label": "3:2", "value": "3, 2"},
432
+ {"label": "5:4", "value": "5, 4"},
433
+ {"label": "9:16", "value": "9, 16"},
434
+ {"label": "16:9", "value": "16, 9"},
435
+ ]
436
+
437
+ FONTS_AVAILABLE = [
438
+ {
439
+ "label": "Fira Sans",
440
+ "value": "custom_components/mqtt_vacuum_camera/utils/fonts/FiraSans.ttf",
441
+ },
442
+ {
443
+ "label": "Inter",
444
+ "value": "custom_components/mqtt_vacuum_camera/utils/fonts/Inter-VF.ttf",
445
+ },
446
+ {
447
+ "label": "M Plus Regular",
448
+ "value": "custom_components/mqtt_vacuum_camera/utils/fonts/MPLUSRegular.ttf",
449
+ },
450
+ {
451
+ "label": "Noto Sans CJKhk",
452
+ "value": "custom_components/mqtt_vacuum_camera/utils/fonts/NotoSansCJKhk-VF.ttf",
453
+ },
454
+ {
455
+ "label": "Noto Kufi Arabic",
456
+ "value": "custom_components/mqtt_vacuum_camera/utils/fonts/NotoKufiArabic-VF.ttf",
457
+ },
458
+ {
459
+ "label": "Noto Sans Khojki",
460
+ "value": "custom_components/mqtt_vacuum_camera/utils/fonts/NotoSansKhojki.ttf",
461
+ },
462
+ {
463
+ "label": "Lato Regular",
464
+ "value": "custom_components/mqtt_vacuum_camera/utils/fonts/Lato-Regular.ttf",
465
+ },
466
+ ]
467
+
468
+ NOT_STREAMING_STATES = {
469
+ "idle",
470
+ "paused",
471
+ "charging",
472
+ "error",
473
+ "docked",
474
+ }
475
+
476
+ DECODED_TOPICS = {
477
+ "/MapData/segments",
478
+ "/maploader/map",
479
+ "/maploader/status",
480
+ "/StatusStateAttribute/status",
481
+ "/StatusStateAttribute/error_description",
482
+ "/$state",
483
+ "/BatteryStateAttribute/level",
484
+ "/WifiConfigurationCapability/ips",
485
+ "/state", # Rand256
486
+ "/destinations", # Rand256
487
+ "/command", # Rand256
488
+ "/custom_command", # Rand256
489
+ "/attributes", # Rand256
490
+ }
491
+
492
+
493
+ # self.command_topic need to be added to this dictionary after init.
494
+ NON_DECODED_TOPICS = {
495
+ "/MapData/map-data",
496
+ "/map_data",
497
+ }
498
+
499
+ """App Constants. Not in use, and dummy values"""
500
+ IDLE_SCAN_INTERVAL = 120
501
+ CLEANING_SCAN_INTERVAL = 5
502
+ IS_ALPHA = "add_base_alpha"
503
+ IS_ALPHA_R1 = "add_room_1_alpha"
504
+ IS_ALPHA_R2 = "add_room_2_alpha"
505
+ IS_OFFSET = "add_offset"
506
+
507
+ """Base Colours RGB"""
508
+ COLOR_CHARGER = "color_charger"
509
+ COLOR_MOVE = "color_move"
510
+ COLOR_ROBOT = "color_robot"
511
+ COLOR_NO_GO = "color_no_go"
512
+ COLOR_GO_TO = "color_go_to"
513
+ COLOR_BACKGROUND = "color_background"
514
+ COLOR_ZONE_CLEAN = "color_zone_clean"
515
+ COLOR_WALL = "color_wall"
516
+ COLOR_TEXT = "color_text"
517
+
518
+ "Rooms Colours RGB"
519
+ COLOR_ROOM_0 = "color_room_0"
520
+ COLOR_ROOM_1 = "color_room_1"
521
+ COLOR_ROOM_2 = "color_room_2"
522
+ COLOR_ROOM_3 = "color_room_3"
523
+ COLOR_ROOM_4 = "color_room_4"
524
+ COLOR_ROOM_5 = "color_room_5"
525
+ COLOR_ROOM_6 = "color_room_6"
526
+ COLOR_ROOM_7 = "color_room_7"
527
+ COLOR_ROOM_8 = "color_room_8"
528
+ COLOR_ROOM_9 = "color_room_9"
529
+ COLOR_ROOM_10 = "color_room_10"
530
+ COLOR_ROOM_11 = "color_room_11"
531
+ COLOR_ROOM_12 = "color_room_12"
532
+ COLOR_ROOM_13 = "color_room_13"
533
+ COLOR_ROOM_14 = "color_room_14"
534
+ COLOR_ROOM_15 = "color_room_15"
535
+
536
+ """Alpha for RGBA Colours"""
537
+ ALPHA_CHARGER = "alpha_charger"
538
+ ALPHA_MOVE = "alpha_move"
539
+ ALPHA_ROBOT = "alpha_robot"
540
+ ALPHA_NO_GO = "alpha_no_go"
541
+ ALPHA_GO_TO = "alpha_go_to"
542
+ ALPHA_BACKGROUND = "alpha_background"
543
+ ALPHA_ZONE_CLEAN = "alpha_zone_clean"
544
+ ALPHA_WALL = "alpha_wall"
545
+ ALPHA_TEXT = "alpha_text"
546
+ ALPHA_ROOM_0 = "alpha_room_0"
547
+ ALPHA_ROOM_1 = "alpha_room_1"
548
+ ALPHA_ROOM_2 = "alpha_room_2"
549
+ ALPHA_ROOM_3 = "alpha_room_3"
550
+ ALPHA_ROOM_4 = "alpha_room_4"
551
+ ALPHA_ROOM_5 = "alpha_room_5"
552
+ ALPHA_ROOM_6 = "alpha_room_6"
553
+ ALPHA_ROOM_7 = "alpha_room_7"
554
+ ALPHA_ROOM_8 = "alpha_room_8"
555
+ ALPHA_ROOM_9 = "alpha_room_9"
556
+ ALPHA_ROOM_10 = "alpha_room_10"
557
+ ALPHA_ROOM_11 = "alpha_room_11"
558
+ ALPHA_ROOM_12 = "alpha_room_12"
559
+ ALPHA_ROOM_13 = "alpha_room_13"
560
+ ALPHA_ROOM_14 = "alpha_room_14"
561
+ ALPHA_ROOM_15 = "alpha_room_15"
562
+
563
+ """ Constants for the attribute keys """
564
+ ATTR_FRIENDLY_NAME = "friendly_name"
565
+ ATTR_VACUUM_BATTERY = "vacuum_battery"
566
+ ATTR_VACUUM_POSITION = "vacuum_position"
567
+ ATTR_VACUUM_TOPIC = "vacuum_topic"
568
+ ATTR_VACUUM_STATUS = "vacuum_status"
569
+ ATTR_JSON_DATA = "json_data"
570
+ ATTR_VACUUM_JSON_ID = "vacuum_json_id"
571
+ ATTR_CALIBRATION_POINTS = "calibration_points"
572
+ ATTR_SNAPSHOT = "snapshot"
573
+ ATTR_SNAPSHOT_PATH = "snapshot_path"
574
+ ATTR_ROOMS = "rooms"
575
+ ATTR_ZONES = "zones"
576
+ ATTR_POINTS = "points"
577
+ ATTR_OBSTACLES = "obstacles"
578
+ ATTR_CAMERA_MODE = "camera_mode"
579
+
580
+
581
+ class CameraModes:
582
+ """Constants for the camera modes"""
583
+
584
+ MAP_VIEW = "map_view"
585
+ OBSTACLE_VIEW = "obstacle_view"
586
+ OBSTACLE_DOWNLOAD = "load_view"
587
+ OBSTACLE_SEARCH = "search_view"
588
+ CAMERA_STANDBY = "camera_standby"
589
+ CAMERA_OFF = False
590
+ CAMERA_ON = True