valetudo-map-parser 0.1.9b75__py3-none-any.whl → 0.1.10__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.
Files changed (33) hide show
  1. valetudo_map_parser/__init__.py +25 -7
  2. valetudo_map_parser/config/auto_crop.py +2 -27
  3. valetudo_map_parser/config/color_utils.py +3 -4
  4. valetudo_map_parser/config/colors.py +2 -2
  5. valetudo_map_parser/config/drawable.py +102 -153
  6. valetudo_map_parser/config/drawable_elements.py +0 -2
  7. valetudo_map_parser/config/fonts/FiraSans.ttf +0 -0
  8. valetudo_map_parser/config/fonts/Inter-VF.ttf +0 -0
  9. valetudo_map_parser/config/fonts/Lato-Regular.ttf +0 -0
  10. valetudo_map_parser/config/fonts/MPLUSRegular.ttf +0 -0
  11. valetudo_map_parser/config/fonts/NotoKufiArabic-VF.ttf +0 -0
  12. valetudo_map_parser/config/fonts/NotoSansCJKhk-VF.ttf +0 -0
  13. valetudo_map_parser/config/fonts/NotoSansKhojki.ttf +0 -0
  14. valetudo_map_parser/config/rand256_parser.py +169 -44
  15. valetudo_map_parser/config/shared.py +103 -101
  16. valetudo_map_parser/config/status_text/status_text.py +96 -0
  17. valetudo_map_parser/config/status_text/translations.py +280 -0
  18. valetudo_map_parser/config/types.py +42 -13
  19. valetudo_map_parser/config/utils.py +221 -181
  20. valetudo_map_parser/hypfer_draw.py +6 -169
  21. valetudo_map_parser/hypfer_handler.py +40 -130
  22. valetudo_map_parser/map_data.py +403 -84
  23. valetudo_map_parser/rand256_handler.py +53 -197
  24. valetudo_map_parser/reimg_draw.py +14 -24
  25. valetudo_map_parser/rooms_handler.py +3 -18
  26. {valetudo_map_parser-0.1.9b75.dist-info → valetudo_map_parser-0.1.10.dist-info}/METADATA +7 -4
  27. valetudo_map_parser-0.1.10.dist-info/RECORD +34 -0
  28. {valetudo_map_parser-0.1.9b75.dist-info → valetudo_map_parser-0.1.10.dist-info}/WHEEL +1 -1
  29. valetudo_map_parser/config/enhanced_drawable.py +0 -324
  30. valetudo_map_parser/hypfer_rooms_handler.py +0 -599
  31. valetudo_map_parser-0.1.9b75.dist-info/RECORD +0 -27
  32. {valetudo_map_parser-0.1.9b75.dist-info → valetudo_map_parser-0.1.10.dist-info/licenses}/LICENSE +0 -0
  33. {valetudo_map_parser-0.1.9b75.dist-info → valetudo_map_parser-0.1.10.dist-info/licenses}/NOTICE.txt +0 -0
@@ -0,0 +1,96 @@
1
+ """
2
+ Version: 0.1.10
3
+ Status text of the vacuum cleaners.
4
+ Class to handle the status text of the vacuum cleaners.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from ..types import LOGGER, PilPNG
10
+ from .translations import translations
11
+
12
+
13
+ LOGGER.propagate = True
14
+
15
+
16
+ class StatusText:
17
+ """
18
+ Status text of the vacuum cleaners.
19
+ """
20
+
21
+ def __init__(self, camera_shared):
22
+ self._shared = camera_shared
23
+ self.file_name = self._shared.file_name
24
+
25
+ @staticmethod
26
+ async def get_vacuum_status_translation(
27
+ language: str = "en",
28
+ ) -> dict[str, str] | None:
29
+ """
30
+ Get the vacuum status translation.
31
+ @param language: Language code, default 'en'.
32
+ @return: Mapping for the given language or None.
33
+ """
34
+ return translations.get((language or "en").lower())
35
+
36
+ async def translate_vacuum_status(self) -> str:
37
+ """Return the translated status with EN fallback and safe default."""
38
+ status = self._shared.vacuum_state or "unknown"
39
+ language = (self._shared.user_language or "en").lower()
40
+ translation = await self.get_vacuum_status_translation(language)
41
+ if not translation:
42
+ translation = translations.get("en", {})
43
+ return translation.get(status, str(status).capitalize())
44
+
45
+ async def get_status_text(self, text_img: PilPNG) -> tuple[list[str], int]:
46
+ """
47
+ Compose the image status text.
48
+ :param text_img: Image to draw the text on.
49
+ :return status_text, text_size: List of the status text and the text size.
50
+ """
51
+ status_text = ["If you read me, something really went wrong.."] # default text
52
+ text_size_coverage = 1.5 # resize factor for the text
53
+ text_size = self._shared.vacuum_status_size # default text size
54
+ charge_level = "\u03de" # unicode Koppa symbol
55
+ charging = "\u2211" # unicode Charging symbol
56
+ vacuum_state = await self.translate_vacuum_status()
57
+ if self._shared.show_vacuum_state:
58
+ status_text = [f"{self.file_name}: {vacuum_state}"]
59
+ language = (self._shared.user_language or "en").lower()
60
+ lang_map = translations.get(language) or translations.get("en", {})
61
+ if not self._shared.vacuum_connection:
62
+ mqtt_disc = lang_map.get(
63
+ "mqtt_disconnected",
64
+ translations.get("en", {}).get(
65
+ "mqtt_disconnected", "Disconnected from MQTT?"
66
+ ),
67
+ )
68
+ status_text = [f"{self.file_name}: {mqtt_disc}"]
69
+ else:
70
+ if self._shared.current_room:
71
+ in_room = self._shared.current_room.get("in_room")
72
+ if in_room:
73
+ status_text.append(f" ({in_room})")
74
+ if self._shared.vacuum_state == "docked":
75
+ if self._shared.vacuum_bat_charged():
76
+ status_text.append(" \u00b7 ")
77
+ status_text.append(f"{charging}{charge_level} ")
78
+ status_text.append(f"{self._shared.vacuum_battery}%")
79
+ else:
80
+ status_text.append(" \u00b7 ")
81
+ status_text.append(f"{charge_level} ")
82
+ ready_txt = lang_map.get(
83
+ "ready",
84
+ translations.get("en", {}).get("ready", "Ready."),
85
+ )
86
+ status_text.append(ready_txt)
87
+ else:
88
+ status_text.append(" \u00b7 ")
89
+ status_text.append(f"{charge_level}")
90
+ status_text.append(f" {self._shared.vacuum_battery}%")
91
+ if text_size >= 50 and getattr(text_img, "width", None):
92
+ text_pixels = max(1, sum(len(text) for text in status_text))
93
+ text_size = int(
94
+ (text_size_coverage * text_img.width) // text_pixels
95
+ )
96
+ return status_text, text_size
@@ -0,0 +1,280 @@
1
+ """Translations for vacuum status and status text snippets."""
2
+
3
+ translations = {
4
+ "en": {
5
+ "connected": "Connected",
6
+ "disconnected": "Disconnected",
7
+ "charging": "Charging",
8
+ "cleaning": "Cleaning",
9
+ "docked": "Docked",
10
+ "idle": "Idle",
11
+ "paused": "Paused",
12
+ "returning": "Returning",
13
+ "ready": "Ready.",
14
+ "mqtt_disconnected": "Disconnected from MQTT?",
15
+ },
16
+ "de": {
17
+ "connected": "Verbunden",
18
+ "disconnected": "Nicht verbunden",
19
+ "charging": "Laden",
20
+ "cleaning": "Reinigen",
21
+ "docked": "Angefahren",
22
+ "idle": "Inaktiv",
23
+ "paused": "Pausiert",
24
+ "returning": "Zurückkehren",
25
+ "ready": "Bereit.",
26
+ "mqtt_disconnected": "Von MQTT getrennt?",
27
+ },
28
+ "fr": {
29
+ "connected": "Connecté",
30
+ "disconnected": "Déconnecté",
31
+ "charging": "En charge",
32
+ "cleaning": "Nettoyage",
33
+ "docked": "Ancré",
34
+ "idle": "Inactif",
35
+ "paused": "En pause",
36
+ "returning": "Retour",
37
+ "ready": "Prêt.",
38
+ "mqtt_disconnected": "Déconnecté de MQTT ?",
39
+ },
40
+ "it": {
41
+ "connected": "Connesso",
42
+ "disconnected": "Disconnesso",
43
+ "charging": "Caricamento",
44
+ "cleaning": "Pulizia",
45
+ "docked": "Ancorato",
46
+ "idle": "Inattivo",
47
+ "paused": "In pausa",
48
+ "returning": "Ritorno",
49
+ "ready": "Pronto.",
50
+ "mqtt_disconnected": "Disconnesso da MQTT?",
51
+ },
52
+ "pl": {
53
+ "connected": "Połączony",
54
+ "disconnected": "Rozłączony",
55
+ "charging": "Ładowanie",
56
+ "cleaning": "Czyszczenie",
57
+ "docked": "Zaparkowany",
58
+ "idle": "Nieaktywny",
59
+ "paused": "Wstrzymany",
60
+ "returning": "Powrót",
61
+ "ready": "Gotowy.",
62
+ "mqtt_disconnected": "Odłączono od MQTT?",
63
+ },
64
+ "pt": {
65
+ "connected": "Conectado",
66
+ "disconnected": "Desconectado",
67
+ "charging": "Carregando",
68
+ "cleaning": "Limpando",
69
+ "docked": "Anclado",
70
+ "idle": "Inativo",
71
+ "paused": "Pausado",
72
+ "returning": "Retornando",
73
+ "ready": "Pronto.",
74
+ "mqtt_disconnected": "Desconectado do MQTT?",
75
+ },
76
+ "ru": {
77
+ "connected": "Подключен",
78
+ "disconnected": "Отключен",
79
+ "charging": "Заряжается",
80
+ "cleaning": "Очищается",
81
+ "docked": "Закреплен",
82
+ "idle": "Неактивен",
83
+ "paused": "Приостановлен",
84
+ "returning": "Возвращается",
85
+ "ready": "Готово.",
86
+ "mqtt_disconnected": "Отключено от MQTT?",
87
+ },
88
+ "tr": {
89
+ "connected": "Bağlandı",
90
+ "disconnected": "Bağlantı kesildi",
91
+ "charging": "Şarj ediliyor",
92
+ "cleaning": "Temizleniyor",
93
+ "docked": "Dokundu",
94
+ "idle": "Boşta",
95
+ "paused": "Duraklatıldı",
96
+ "returning": "Geri dönüyor",
97
+ "ready": "Hazır.",
98
+ "mqtt_disconnected": "MQTT bağlantısı kesildi mi?",
99
+ },
100
+ "zh": {
101
+ "connected": "已连接",
102
+ "disconnected": "未连接",
103
+ "charging": "充电中",
104
+ "cleaning": "清扫中",
105
+ "docked": "已停靠",
106
+ "idle": "空闲",
107
+ "paused": "已暂停",
108
+ "returning": "返回中",
109
+ "ready": "已就绪。",
110
+ "mqtt_disconnected": "MQTT 已断开?",
111
+ },
112
+ "ja": {
113
+ "connected": "接続済み",
114
+ "disconnected": "切断されました",
115
+ "charging": "充電中",
116
+ "cleaning": "掃除中",
117
+ "docked": "停泊中",
118
+ "idle": "アイドル",
119
+ "paused": "一時停止中",
120
+ "returning": "戻り中",
121
+ "ready": "準備完了。",
122
+ "mqtt_disconnected": "MQTT から切断されていますか?",
123
+ },
124
+ "ko": {
125
+ "connected": "연결됨",
126
+ "disconnected": "연결 해제됨",
127
+ "charging": "충전 중",
128
+ "cleaning": "청소 중",
129
+ "docked": "도킹됨",
130
+ "idle": "대기 중",
131
+ "paused": "일시 중지됨",
132
+ "returning": "돌아오는 중",
133
+ "ready": "준비 완료.",
134
+ "mqtt_disconnected": "MQTT에서 연결이 끊겼나요?",
135
+ },
136
+ "ar": {
137
+ "connected": "متصل",
138
+ "disconnected": "غير متصل",
139
+ "charging": "جار الت충ّد",
140
+ "cleaning": "washering",
141
+ "docked": "anchored",
142
+ "idle": "iddle",
143
+ "paused": "paused",
144
+ "returning": "returning",
145
+ "ready": "جاهز.",
146
+ "mqtt_disconnected": "هل تم قطع الاتصال بـ MQTT؟",
147
+ },
148
+ "hi": {
149
+ "connected": "संयुक्त",
150
+ "disconnected": "असंयुक्त",
151
+ "charging": "चार्जिंग",
152
+ "cleaning": "साफ़ कर रहा है",
153
+ "docked": "डॉक में",
154
+ "idle": "रूक गया है",
155
+ "paused": "रूक गया है",
156
+ "returning": "वापस आ रहा है",
157
+ "ready": "तैयार.",
158
+ "mqtt_disconnected": "MQTT से डिसकनेक्ट?",
159
+ },
160
+ "bn": {
161
+ "connected": "যোগাযোগ করা হয়েছে",
162
+ "disconnected": "বিরাজিত",
163
+ "charging": "চার্জিং",
164
+ "cleaning": "সাফ করা হচ্ছে",
165
+ "docked": "ডকেড",
166
+ "idle": "বিনা কর্মে",
167
+ "paused": "বিরত",
168
+ "returning": "পুনরায় আসছে",
169
+ "ready": "প্রস্তুত।",
170
+ "mqtt_disconnected": "MQTT থেকে সংযোগ বিচ্ছিন্ন?",
171
+ },
172
+ "sv": {
173
+ "connected": "Ansluten",
174
+ "disconnected": "Frånkopplad",
175
+ "charging": "Laddar",
176
+ "cleaning": "Rensar",
177
+ "docked": "Anknyttad",
178
+ "idle": "Väntande",
179
+ "paused": "Paus",
180
+ "returning": "Återvänder",
181
+ "ready": "Klar.",
182
+ "mqtt_disconnected": "Frånkopplad från MQTT?",
183
+ },
184
+ "fn": {
185
+ "connected": "Konektado",
186
+ "disconnected": "Hindi konektado",
187
+ "charging": "Kinakarga",
188
+ "cleaning": "Binabagay",
189
+ "docked": "Ankore",
190
+ "idle": "Idle",
191
+ "paused": "Napupaua",
192
+ "returning": "Nagbabalik",
193
+ "ready": "Handa.",
194
+ "mqtt_disconnected": "Na-disconnect sa MQTT?",
195
+ },
196
+ "no": {
197
+ "connected": "Tilkoblet",
198
+ "disconnected": "Frakoblet",
199
+ "charging": "Lader",
200
+ "cleaning": "Renser",
201
+ "docked": "Ankoblet",
202
+ "idle": "Inaktiv",
203
+ "paused": "Pause",
204
+ "returning": "Gir tilbake",
205
+ "ready": "Klar.",
206
+ "mqtt_disconnected": "Frakoblet fra MQTT?",
207
+ },
208
+ "cz": {
209
+ "connected": "Připojeno",
210
+ "disconnected": "Odpojeno",
211
+ "charging": "Nabíjení",
212
+ "cleaning": "Čištění",
213
+ "docked": "Zaparkováno",
214
+ "idle": "Nečinný",
215
+ "paused": "Pozastaveno",
216
+ "returning": "Vrací se",
217
+ "ready": "Připraven.",
218
+ "mqtt_disconnected": "Odpojeno od MQTT?",
219
+ },
220
+ "da": {
221
+ "connected": "Tilsluttet",
222
+ "disconnected": "Afvist",
223
+ "charging": "Oplader",
224
+ "cleaning": "Renser",
225
+ "docked": "Ankeret",
226
+ "idle": "Inaktiv",
227
+ "paused": "Pause",
228
+ "returning": "Returnerer",
229
+ "ready": "Klar.",
230
+ "mqtt_disconnected": "Afbrudt fra MQTT?",
231
+ },
232
+ "fi": {
233
+ "connected": "Yhdistetty",
234
+ "disconnected": "Yhteys katkaistu",
235
+ "charging": "Lataa",
236
+ "cleaning": "Siivous",
237
+ "docked": "Ankeroitu",
238
+ "idle": "Lähes",
239
+ "paused": "Tauko",
240
+ "returning": "Palauttaa",
241
+ "ready": "Valmis.",
242
+ "mqtt_disconnected": "Yhteys katkennut MQTT:stä?",
243
+ },
244
+ "el": {
245
+ "connected": "Συνδεδεμένος",
246
+ "disconnected": "Αποσυνδεδεμένος",
247
+ "charging": "Φορτώνει",
248
+ "cleaning": "Καθαρισμός",
249
+ "docked": "Ανκερώθηκε",
250
+ "idle": "Αδρανής",
251
+ "paused": "Παύση",
252
+ "returning": "Επιστρέφει",
253
+ "ready": "Έτοιμο.",
254
+ "mqtt_disconnected": "Αποσυνδεδεμένο από MQTT;",
255
+ },
256
+ "es": {
257
+ "connected": "Conectado",
258
+ "disconnected": "Desconectado",
259
+ "charging": "Cargando",
260
+ "cleaning": "Limpiando",
261
+ "docked": "Anclado",
262
+ "idle": "Inactivo",
263
+ "paused": "Pausado",
264
+ "returning": "Regresando",
265
+ "ready": "Listo.",
266
+ "mqtt_disconnected": "¿Desconectado de MQTT?",
267
+ },
268
+ "nl": {
269
+ "connected": "Verbonden",
270
+ "disconnected": "Niet verbonden",
271
+ "charging": "Laden",
272
+ "cleaning": "Schoonmaken",
273
+ "docked": "Aangeschoten",
274
+ "idle": "Inactief",
275
+ "paused": "Gepauzeerd",
276
+ "returning": "Terugkeren",
277
+ "ready": "Klaar.",
278
+ "mqtt_disconnected": "Verbinding met MQTT verbroken?",
279
+ },
280
+ }
@@ -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, TypedDict, Union
11
+ from typing import Any, Dict, List, NotRequired, Optional, Tuple, TypedDict, Union
12
12
 
13
13
  import numpy as np
14
14
  from PIL import Image
@@ -19,6 +19,29 @@ DEFAULT_ROOMS = 1
19
19
  LOGGER = logging.getLogger(__package__)
20
20
 
21
21
 
22
+ class Spot(TypedDict):
23
+ name: str
24
+ coordinates: List[int] # [x, y]
25
+
26
+
27
+ class Zone(TypedDict):
28
+ name: str
29
+ coordinates: List[List[int]] # [[x1, y1, x2, y2, repeats], ...]
30
+
31
+
32
+ class Room(TypedDict):
33
+ name: str
34
+ id: int
35
+
36
+
37
+ # list[dict[str, str | list[int]]] | list[dict[str, str | list[list[int]]]] | list[dict[str, str | int]] | int]'
38
+ class Destinations(TypedDict, total=False):
39
+ spots: NotRequired[Optional[List[Spot]]]
40
+ zones: NotRequired[Optional[List[Zone]]]
41
+ rooms: NotRequired[Optional[List[Room]]]
42
+ updated: NotRequired[Optional[float]]
43
+
44
+
22
45
  class RoomProperty(TypedDict):
23
46
  number: int
24
47
  outline: list[tuple[int, int]]
@@ -81,6 +104,8 @@ class RoomStore:
81
104
  instance = super(RoomStore, cls).__new__(cls)
82
105
  instance.vacuum_id = vacuum_id
83
106
  instance.vacuums_data = rooms_data or {}
107
+ instance.rooms_count = instance.get_rooms_count()
108
+ instance.floor = None
84
109
  cls._instances[vacuum_id] = instance
85
110
  else:
86
111
  if rooms_data is not None:
@@ -126,10 +151,10 @@ class UserLanguageStore:
126
151
  async with self._lock:
127
152
  self.user_languages[user_id] = language
128
153
 
129
- async def get_user_language(self, user_id: str) -> str or None:
154
+ async def get_user_language(self, user_id: str) -> str:
130
155
  """Get the user language."""
131
156
  async with self._lock:
132
- return self.user_languages.get(user_id, None)
157
+ return self.user_languages.get(user_id, "")
133
158
 
134
159
  async def get_all_languages(self):
135
160
  """Get all the user languages."""
@@ -197,20 +222,24 @@ class SnapshotStore:
197
222
  Color = Union[Tuple[int, int, int], Tuple[int, int, int, int]]
198
223
  Colors = Dict[str, Color]
199
224
  CalibrationPoints = list[dict[str, Any]]
200
- RobotPosition = dict[str, int | float]
225
+ RobotPosition: type[tuple[Any, Any, dict[str, int | float] | None]] = tuple[
226
+ Any, Any, dict[str, int | float] | None
227
+ ]
201
228
  ChargerPosition = dict[str, Any]
202
229
  RoomsProperties = dict[str, RoomProperty]
203
230
  ImageSize = dict[str, int | list[int]]
231
+ Size = dict[str, int]
204
232
  JsonType = Any # json.loads() return type is Any
205
233
  PilPNG = Image.Image # Keep for backward compatibility
206
- WebPBytes = bytes # WebP image as bytes
207
234
  NumpyArray = np.ndarray
208
235
  Point = Tuple[int, int]
209
236
 
210
237
  CAMERA_STORAGE = "valetudo_camera"
238
+ ATTR_IMAGE_LAST_UPDATED = "image_last_updated"
211
239
  ATTR_ROTATE = "rotate_image"
212
240
  ATTR_CROP = "crop_image"
213
241
  ATTR_MARGINS = "margins"
242
+ ATTR_CONTENT_TYPE = "content_type"
214
243
  CONF_OFFSET_TOP = "offset_top"
215
244
  CONF_OFFSET_BOTTOM = "offset_bottom"
216
245
  CONF_OFFSET_LEFT = "offset_left"
@@ -283,7 +312,7 @@ DEFAULT_VALUES = {
283
312
  "auto_zoom": False,
284
313
  "zoom_lock_ratio": True,
285
314
  "show_vac_status": False,
286
- "vac_status_font": "custom_components/mqtt_vacuum_camera/utils/fonts/FiraSans.ttf",
315
+ "vac_status_font": "SCR/valetudo_map_parser/config/fonts/FiraSans.ttf",
287
316
  "vac_status_size": 50,
288
317
  "vac_status_position": True,
289
318
  "get_svg_file": False,
@@ -443,31 +472,31 @@ RATIO_VALUES = [
443
472
  FONTS_AVAILABLE = [
444
473
  {
445
474
  "label": "Fira Sans",
446
- "value": "custom_components/mqtt_vacuum_camera/utils/fonts/FiraSans.ttf",
475
+ "value": "config/fonts/FiraSans.ttf",
447
476
  },
448
477
  {
449
478
  "label": "Inter",
450
- "value": "custom_components/mqtt_vacuum_camera/utils/fonts/Inter-VF.ttf",
479
+ "value": "config/fonts/Inter-VF.ttf",
451
480
  },
452
481
  {
453
482
  "label": "M Plus Regular",
454
- "value": "custom_components/mqtt_vacuum_camera/utils/fonts/MPLUSRegular.ttf",
483
+ "value": "config/fonts/MPLUSRegular.ttf",
455
484
  },
456
485
  {
457
486
  "label": "Noto Sans CJKhk",
458
- "value": "custom_components/mqtt_vacuum_camera/utils/fonts/NotoSansCJKhk-VF.ttf",
487
+ "value": "config/fonts/NotoSansCJKhk-VF.ttf",
459
488
  },
460
489
  {
461
490
  "label": "Noto Kufi Arabic",
462
- "value": "custom_components/mqtt_vacuum_camera/utils/fonts/NotoKufiArabic-VF.ttf",
491
+ "value": "config/fonts/NotoKufiArabic-VF.ttf",
463
492
  },
464
493
  {
465
494
  "label": "Noto Sans Khojki",
466
- "value": "custom_components/mqtt_vacuum_camera/utils/fonts/NotoSansKhojki.ttf",
495
+ "value": "config/fonts/NotoSansKhojki.ttf",
467
496
  },
468
497
  {
469
498
  "label": "Lato Regular",
470
- "value": "custom_components/mqtt_vacuum_camera/utils/fonts/Lato-Regular.ttf",
499
+ "value": "config/fonts/Lato-Regular.ttf",
471
500
  },
472
501
  ]
473
502