valetudo-map-parser 0.1.10rc5__py3-none-any.whl → 0.1.10rc7__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/rand256_parser.py +129 -47
- valetudo_map_parser/config/shared.py +83 -93
- valetudo_map_parser/config/types.py +26 -1
- valetudo_map_parser/config/utils.py +60 -10
- valetudo_map_parser/hypfer_handler.py +2 -5
- valetudo_map_parser/map_data.py +4 -3
- valetudo_map_parser/rand256_handler.py +13 -17
- valetudo_map_parser/reimg_draw.py +13 -18
- {valetudo_map_parser-0.1.10rc5.dist-info → valetudo_map_parser-0.1.10rc7.dist-info}/METADATA +2 -2
- {valetudo_map_parser-0.1.10rc5.dist-info → valetudo_map_parser-0.1.10rc7.dist-info}/RECORD +13 -14
- valetudo_map_parser/hypfer_rooms_handler.py +0 -599
- {valetudo_map_parser-0.1.10rc5.dist-info → valetudo_map_parser-0.1.10rc7.dist-info}/WHEEL +0 -0
- {valetudo_map_parser-0.1.10rc5.dist-info → valetudo_map_parser-0.1.10rc7.dist-info}/licenses/LICENSE +0 -0
- {valetudo_map_parser-0.1.10rc5.dist-info → valetudo_map_parser-0.1.10rc7.dist-info}/licenses/NOTICE.txt +0 -0
@@ -24,6 +24,14 @@ class RRMapParser:
|
|
24
24
|
VIRTUAL_WALLS = 10
|
25
25
|
CURRENTLY_CLEANED_BLOCKS = 11
|
26
26
|
FORBIDDEN_MOP_ZONES = 12
|
27
|
+
OBSTACLES = 13
|
28
|
+
IGNORED_OBSTACLES = 14
|
29
|
+
OBSTACLES_WITH_PHOTO = 15
|
30
|
+
IGNORED_OBSTACLES_WITH_PHOTO = 16
|
31
|
+
CARPET_MAP = 17
|
32
|
+
MOP_PATH = 18
|
33
|
+
NO_CARPET_AREAS = 19
|
34
|
+
DIGEST = 1024
|
27
35
|
|
28
36
|
class Tools:
|
29
37
|
"""Tools for coordinate transformations."""
|
@@ -33,6 +41,7 @@ class RRMapParser:
|
|
33
41
|
|
34
42
|
def __init__(self):
|
35
43
|
"""Initialize the parser."""
|
44
|
+
self.is_valid = False
|
36
45
|
self.map_data: Dict[str, Any] = {}
|
37
46
|
|
38
47
|
# Xiaomi/Roborock style byte extraction methods
|
@@ -67,6 +76,61 @@ class RRMapParser:
|
|
67
76
|
value = RRMapParser._get_int32(data, address)
|
68
77
|
return value if value < 0x80000000 else value - 0x100000000
|
69
78
|
|
79
|
+
@staticmethod
|
80
|
+
def _parse_carpet_map(data: bytes) -> set[int]:
|
81
|
+
carpet_map = set()
|
82
|
+
|
83
|
+
for i, v in enumerate(data):
|
84
|
+
if v:
|
85
|
+
carpet_map.add(i)
|
86
|
+
return carpet_map
|
87
|
+
|
88
|
+
@staticmethod
|
89
|
+
def _parse_area(header: bytes, data: bytes) -> list:
|
90
|
+
area_pairs = RRMapParser._get_int16(header, 0x08)
|
91
|
+
areas = []
|
92
|
+
for area_start in range(0, area_pairs * 16, 16):
|
93
|
+
x0 = RRMapParser._get_int16(data, area_start + 0)
|
94
|
+
y0 = RRMapParser._get_int16(data, area_start + 2)
|
95
|
+
x1 = RRMapParser._get_int16(data, area_start + 4)
|
96
|
+
y1 = RRMapParser._get_int16(data, area_start + 6)
|
97
|
+
x2 = RRMapParser._get_int16(data, area_start + 8)
|
98
|
+
y2 = RRMapParser._get_int16(data, area_start + 10)
|
99
|
+
x3 = RRMapParser._get_int16(data, area_start + 12)
|
100
|
+
y3 = RRMapParser._get_int16(data, area_start + 14)
|
101
|
+
areas.append(
|
102
|
+
[
|
103
|
+
x0,
|
104
|
+
RRMapParser.Tools.DIMENSION_MM - y0,
|
105
|
+
x1,
|
106
|
+
RRMapParser.Tools.DIMENSION_MM - y1,
|
107
|
+
x2,
|
108
|
+
RRMapParser.Tools.DIMENSION_MM - y2,
|
109
|
+
x3,
|
110
|
+
RRMapParser.Tools.DIMENSION_MM - y3,
|
111
|
+
]
|
112
|
+
)
|
113
|
+
return areas
|
114
|
+
|
115
|
+
@staticmethod
|
116
|
+
def _parse_zones(data: bytes, header: bytes) -> list:
|
117
|
+
zone_pairs = RRMapParser._get_int16(header, 0x08)
|
118
|
+
zones = []
|
119
|
+
for zone_start in range(0, zone_pairs * 8, 8):
|
120
|
+
x0 = RRMapParser._get_int16(data, zone_start + 0)
|
121
|
+
y0 = RRMapParser._get_int16(data, zone_start + 2)
|
122
|
+
x1 = RRMapParser._get_int16(data, zone_start + 4)
|
123
|
+
y1 = RRMapParser._get_int16(data, zone_start + 6)
|
124
|
+
zones.append(
|
125
|
+
[
|
126
|
+
x0,
|
127
|
+
RRMapParser.Tools.DIMENSION_MM - y0,
|
128
|
+
x1,
|
129
|
+
RRMapParser.Tools.DIMENSION_MM - y1,
|
130
|
+
]
|
131
|
+
)
|
132
|
+
return zones
|
133
|
+
|
70
134
|
@staticmethod
|
71
135
|
def _parse_object_position(block_data_length: int, data: bytes) -> Dict[str, Any]:
|
72
136
|
"""Parse object position using Xiaomi method."""
|
@@ -82,6 +146,19 @@ class RRMapParser:
|
|
82
146
|
angle = raw_angle
|
83
147
|
return {"position": [x, y], "angle": angle}
|
84
148
|
|
149
|
+
|
150
|
+
@staticmethod
|
151
|
+
def _parse_walls(data: bytes, header: bytes) -> list:
|
152
|
+
wall_pairs = RRMapParser._get_int16(header, 0x08)
|
153
|
+
walls = []
|
154
|
+
for wall_start in range(0, wall_pairs * 8, 8):
|
155
|
+
x0 = RRMapParser._get_int16(data, wall_start + 0)
|
156
|
+
y0 = RRMapParser._get_int16(data, wall_start + 2)
|
157
|
+
x1 = RRMapParser._get_int16(data, wall_start + 4)
|
158
|
+
y1 = RRMapParser._get_int16(data, wall_start + 6)
|
159
|
+
walls.append([x0, RRMapParser.Tools.DIMENSION_MM - y0, x1, RRMapParser.Tools.DIMENSION_MM - y1])
|
160
|
+
return walls
|
161
|
+
|
85
162
|
@staticmethod
|
86
163
|
def _parse_path_block(buf: bytes, offset: int, length: int) -> Dict[str, Any]:
|
87
164
|
"""Parse path block using EXACT same method as working parser."""
|
@@ -127,59 +204,45 @@ class RRMapParser:
|
|
127
204
|
return {}
|
128
205
|
|
129
206
|
def parse_blocks(self, raw: bytes, pixels: bool = True) -> Dict[int, Any]:
|
130
|
-
"""Parse all blocks using Xiaomi method."""
|
131
207
|
blocks = {}
|
132
208
|
map_header_length = self._get_int16(raw, 0x02)
|
133
209
|
block_start_position = map_header_length
|
134
|
-
|
135
210
|
while block_start_position < len(raw):
|
136
211
|
try:
|
137
|
-
# Parse block header using Xiaomi method
|
138
212
|
block_header_length = self._get_int16(raw, block_start_position + 0x02)
|
139
213
|
header = self._get_bytes(raw, block_start_position, block_header_length)
|
140
214
|
block_type = self._get_int16(header, 0x00)
|
141
215
|
block_data_length = self._get_int32(header, 0x04)
|
142
216
|
block_data_start = block_start_position + block_header_length
|
143
217
|
data = self._get_bytes(raw, block_data_start, block_data_length)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
block_data_length, data
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
block_data_length,
|
171
|
-
header_length,
|
172
|
-
pixels,
|
173
|
-
)
|
174
|
-
|
175
|
-
# Move to next block using Xiaomi method
|
176
|
-
block_start_position = (
|
177
|
-
block_start_position + block_data_length + self._get_int8(header, 2)
|
178
|
-
)
|
179
|
-
|
218
|
+
match block_type:
|
219
|
+
case self.Types.DIGEST.value:
|
220
|
+
self.is_valid = True
|
221
|
+
case self.Types.ROBOT_POSITION.value | self.Types.CHARGER_LOCATION.value:
|
222
|
+
blocks[block_type] = self._parse_object_position(block_data_length, data)
|
223
|
+
case self.Types.PATH.value | self.Types.GOTO_PREDICTED_PATH.value:
|
224
|
+
blocks[block_type] = self._parse_path_block(raw, block_start_position, block_data_length)
|
225
|
+
case self.Types.CURRENTLY_CLEANED_ZONES.value:
|
226
|
+
blocks[block_type] = {"zones": self._parse_zones(data, header)}
|
227
|
+
case self.Types.FORBIDDEN_ZONES.value:
|
228
|
+
blocks[block_type] = {"forbidden_zones": self._parse_area(header, data)}
|
229
|
+
case self.Types.FORBIDDEN_MOP_ZONES.value:
|
230
|
+
blocks[block_type] = {"forbidden_mop_zones": self._parse_area(header, data)}
|
231
|
+
case self.Types.GOTO_TARGET.value:
|
232
|
+
blocks[block_type] = {"position": self._parse_goto_target(data)}
|
233
|
+
case self.Types.VIRTUAL_WALLS.value:
|
234
|
+
blocks[block_type] = {"virtual_walls": self._parse_walls(data, header)}
|
235
|
+
case self.Types.CARPET_MAP.value:
|
236
|
+
data = RRMapParser._get_bytes(raw, block_data_start, block_data_length)
|
237
|
+
blocks[block_type] = {"carpet_map": self._parse_carpet_map(data)}
|
238
|
+
case self.Types.IMAGE.value:
|
239
|
+
header_length = self._get_int8(header, 2)
|
240
|
+
blocks[block_type] = self._parse_image_block(
|
241
|
+
raw, block_start_position, block_data_length, header_length, pixels)
|
242
|
+
|
243
|
+
block_start_position = block_start_position + block_data_length + self._get_int8(header, 2)
|
180
244
|
except (struct.error, IndexError):
|
181
245
|
break
|
182
|
-
|
183
246
|
return blocks
|
184
247
|
|
185
248
|
def _parse_image_block(
|
@@ -365,8 +428,32 @@ class RRMapParser:
|
|
365
428
|
]
|
366
429
|
|
367
430
|
# Add missing fields to match expected JSON format
|
368
|
-
parsed_map_data["
|
369
|
-
|
431
|
+
parsed_map_data["currently_cleaned_zones"] = (
|
432
|
+
blocks[self.Types.CURRENTLY_CLEANED_ZONES.value]["zones"]
|
433
|
+
if self.Types.CURRENTLY_CLEANED_ZONES.value in blocks
|
434
|
+
else []
|
435
|
+
)
|
436
|
+
parsed_map_data["forbidden_zones"] = (
|
437
|
+
blocks[self.Types.FORBIDDEN_ZONES.value]["forbidden_zones"]
|
438
|
+
if self.Types.FORBIDDEN_ZONES.value in blocks
|
439
|
+
else []
|
440
|
+
)
|
441
|
+
parsed_map_data["forbidden_mop_zones"] = (
|
442
|
+
blocks[self.Types.FORBIDDEN_MOP_ZONES.value]["forbidden_mop_zones"]
|
443
|
+
if self.Types.FORBIDDEN_MOP_ZONES.value in blocks
|
444
|
+
else []
|
445
|
+
)
|
446
|
+
parsed_map_data["virtual_walls"] = (
|
447
|
+
blocks[self.Types.VIRTUAL_WALLS.value]["virtual_walls"]
|
448
|
+
if self.Types.VIRTUAL_WALLS.value in blocks
|
449
|
+
else []
|
450
|
+
)
|
451
|
+
parsed_map_data["carpet_areas"] = (
|
452
|
+
blocks[self.Types.CARPET_MAP.value]["carpet_map"]
|
453
|
+
if self.Types.CARPET_MAP.value in blocks
|
454
|
+
else []
|
455
|
+
)
|
456
|
+
parsed_map_data["is_valid"] = self.is_valid
|
370
457
|
|
371
458
|
return parsed_map_data
|
372
459
|
|
@@ -388,8 +475,3 @@ class RRMapParser:
|
|
388
475
|
except (struct.error, IndexError, ValueError):
|
389
476
|
return None
|
390
477
|
return self.map_data
|
391
|
-
|
392
|
-
@staticmethod
|
393
|
-
def get_int32(data: bytes, address: int) -> int:
|
394
|
-
"""Get a 32-bit integer from the data - kept for compatibility."""
|
395
|
-
return struct.unpack_from("<i", data, address)[0]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Class Camera Shared.
|
3
3
|
Keep the data between the modules.
|
4
|
-
Version: v0.1.
|
4
|
+
Version: v0.1.12
|
5
5
|
"""
|
6
6
|
|
7
7
|
import asyncio
|
@@ -12,12 +12,13 @@ from PIL import Image
|
|
12
12
|
from .types import (
|
13
13
|
ATTR_CALIBRATION_POINTS,
|
14
14
|
ATTR_CAMERA_MODE,
|
15
|
+
ATTR_CONTENT_TYPE,
|
15
16
|
ATTR_MARGINS,
|
16
17
|
ATTR_OBSTACLES,
|
17
18
|
ATTR_POINTS,
|
18
19
|
ATTR_ROOMS,
|
19
20
|
ATTR_ROTATE,
|
20
|
-
|
21
|
+
ATTR_IMAGE_LAST_UPDATED,
|
21
22
|
ATTR_VACUUM_BATTERY,
|
22
23
|
ATTR_VACUUM_CHARGING,
|
23
24
|
ATTR_VACUUM_JSON_ID,
|
@@ -54,71 +55,68 @@ class CameraShared:
|
|
54
55
|
"""
|
55
56
|
|
56
57
|
def __init__(self, file_name):
|
57
|
-
self.camera_mode: str = CameraModes.MAP_VIEW
|
58
|
-
self.frame_number: int = 0
|
59
|
-
self.destinations: list = []
|
60
|
-
self.rand256_active_zone: list = []
|
61
|
-
self.rand256_zone_coordinates: list = []
|
62
|
-
self.is_rand: bool = False
|
63
|
-
self._new_mqtt_message = False
|
64
|
-
|
65
|
-
self.
|
66
|
-
|
67
|
-
|
68
|
-
self.
|
69
|
-
self.
|
70
|
-
self.
|
71
|
-
self.
|
72
|
-
self.
|
73
|
-
self.
|
74
|
-
self.
|
75
|
-
self.
|
76
|
-
self.
|
77
|
-
self.
|
78
|
-
self.
|
79
|
-
self.
|
80
|
-
self.
|
81
|
-
self.
|
82
|
-
self.
|
83
|
-
self.
|
84
|
-
self.
|
85
|
-
self.
|
86
|
-
self.
|
87
|
-
self.vacuum_state = None # Vacuum state
|
88
|
-
self.charger_position = None # Vacuum Charger position
|
89
|
-
self.show_vacuum_state = None # Show vacuum state on the map
|
58
|
+
self.camera_mode: str = CameraModes.MAP_VIEW
|
59
|
+
self.frame_number: int = 0
|
60
|
+
self.destinations: list = []
|
61
|
+
self.rand256_active_zone: list = []
|
62
|
+
self.rand256_zone_coordinates: list = []
|
63
|
+
self.is_rand: bool = False
|
64
|
+
self._new_mqtt_message = False
|
65
|
+
self.last_image = Image.new("RGBA", (250, 150), (128, 128, 128, 255))
|
66
|
+
self.new_image: PilPNG | None = None
|
67
|
+
self.binary_image: bytes | None = None
|
68
|
+
self.image_last_updated: float = 0.0
|
69
|
+
self.image_format = "image/pil"
|
70
|
+
self.image_size = None
|
71
|
+
self.robot_size = None
|
72
|
+
self.image_auto_zoom: bool = False
|
73
|
+
self.image_zoom_lock_ratio: bool = True
|
74
|
+
self.image_ref_height: int = 0
|
75
|
+
self.image_ref_width: int = 0
|
76
|
+
self.image_aspect_ratio: str = "None"
|
77
|
+
self.image_grab = True
|
78
|
+
self.image_rotate: int = 0
|
79
|
+
self.drawing_limit: float = 0.0
|
80
|
+
self.current_room = None
|
81
|
+
self.user_colors = Colors
|
82
|
+
self.rooms_colors = Colors
|
83
|
+
self.vacuum_battery = 0
|
84
|
+
self.vacuum_connection = False
|
85
|
+
self.vacuum_state = None
|
86
|
+
self.charger_position = None
|
87
|
+
self.show_vacuum_state = None
|
90
88
|
self.vacuum_status_font: str = (
|
91
|
-
"custom_components/mqtt_vacuum_camera/utils/fonts/FiraSans.ttf"
|
89
|
+
"custom_components/mqtt_vacuum_camera/utils/fonts/FiraSans.ttf"
|
92
90
|
)
|
93
|
-
self.vacuum_status_size: int = 50
|
94
|
-
self.vacuum_status_position: bool = True
|
95
|
-
self.snapshot_take = False
|
96
|
-
self.vacuum_error = None
|
97
|
-
self.vacuum_api = None
|
98
|
-
self.vacuum_ips = None
|
99
|
-
self.vac_json_id = None
|
100
|
-
self.margins = "100"
|
101
|
-
self.obstacles_data = None
|
102
|
-
self.obstacles_pos = None
|
103
|
-
self.offset_top = 0
|
104
|
-
self.offset_down = 0
|
105
|
-
self.offset_left = 0
|
106
|
-
self.offset_right = 0
|
107
|
-
self.export_svg = False
|
108
|
-
self.svg_path = None
|
109
|
-
self.enable_snapshots = False
|
110
|
-
self.file_name = file_name
|
111
|
-
self.attr_calibration_points = None
|
112
|
-
self.map_rooms = None
|
113
|
-
self.map_pred_zones = None
|
114
|
-
self.map_pred_points = None
|
115
|
-
self.map_new_path = None
|
116
|
-
self.map_old_path = None
|
117
|
-
self.user_language = None
|
91
|
+
self.vacuum_status_size: int = 50
|
92
|
+
self.vacuum_status_position: bool = True
|
93
|
+
self.snapshot_take = False
|
94
|
+
self.vacuum_error = None
|
95
|
+
self.vacuum_api = None
|
96
|
+
self.vacuum_ips = None
|
97
|
+
self.vac_json_id = None
|
98
|
+
self.margins = "100"
|
99
|
+
self.obstacles_data = None
|
100
|
+
self.obstacles_pos = None
|
101
|
+
self.offset_top = 0
|
102
|
+
self.offset_down = 0
|
103
|
+
self.offset_left = 0
|
104
|
+
self.offset_right = 0
|
105
|
+
self.export_svg = False
|
106
|
+
self.svg_path = None
|
107
|
+
self.enable_snapshots = False
|
108
|
+
self.file_name = file_name
|
109
|
+
self.attr_calibration_points = None
|
110
|
+
self.map_rooms = None
|
111
|
+
self.map_pred_zones = None
|
112
|
+
self.map_pred_points = None
|
113
|
+
self.map_new_path = None
|
114
|
+
self.map_old_path = None
|
115
|
+
self.user_language = None
|
118
116
|
self.trim_crop_data = None
|
119
|
-
self.trims = TrimsData.from_dict(DEFAULT_VALUES["trims_data"])
|
117
|
+
self.trims = TrimsData.from_dict(DEFAULT_VALUES["trims_data"])
|
120
118
|
self.skip_room_ids: List[str] = []
|
121
|
-
self.device_info = None
|
119
|
+
self.device_info = None
|
122
120
|
|
123
121
|
def vacuum_bat_charged(self) -> bool:
|
124
122
|
"""Check if the vacuum is charging."""
|
@@ -126,49 +124,35 @@ class CameraShared:
|
|
126
124
|
|
127
125
|
@staticmethod
|
128
126
|
def _compose_obstacle_links(vacuum_host_ip: str, obstacles: list) -> list | None:
|
129
|
-
"""
|
130
|
-
Compose JSON with obstacle details including the image link.
|
131
|
-
"""
|
127
|
+
"""Compose JSON with obstacle details including the image link."""
|
132
128
|
obstacle_links = []
|
133
129
|
if not obstacles or not vacuum_host_ip:
|
134
130
|
return None
|
135
131
|
|
136
132
|
for obstacle in obstacles:
|
137
|
-
# Extract obstacle details
|
138
133
|
label = obstacle.get("label", "")
|
139
134
|
points = obstacle.get("points", {})
|
140
135
|
image_id = obstacle.get("id", "None")
|
141
136
|
|
142
137
|
if label and points and image_id and vacuum_host_ip:
|
143
|
-
# Append formatted obstacle data
|
144
138
|
if image_id != "None":
|
145
|
-
# Compose the link
|
146
139
|
image_link = (
|
147
140
|
f"http://{vacuum_host_ip}"
|
148
141
|
f"/api/v2/robot/capabilities/ObstacleImagesCapability/img/{image_id}"
|
149
142
|
)
|
150
143
|
obstacle_links.append(
|
151
|
-
{
|
152
|
-
"point": points,
|
153
|
-
"label": label,
|
154
|
-
"link": image_link,
|
155
|
-
}
|
144
|
+
{"point": points, "label": label, "link": image_link}
|
156
145
|
)
|
157
146
|
else:
|
158
|
-
obstacle_links.append(
|
159
|
-
{
|
160
|
-
"point": points,
|
161
|
-
"label": label,
|
162
|
-
}
|
163
|
-
)
|
147
|
+
obstacle_links.append({"point": points, "label": label})
|
164
148
|
return obstacle_links
|
165
149
|
|
166
150
|
def update_user_colors(self, user_colors):
|
167
|
-
"""Update
|
151
|
+
"""Update user colors palette"""
|
168
152
|
self.user_colors = user_colors
|
169
153
|
|
170
154
|
def get_user_colors(self):
|
171
|
-
"""
|
155
|
+
"""Return user colors"""
|
172
156
|
return self.user_colors
|
173
157
|
|
174
158
|
def update_rooms_colors(self, user_colors):
|
@@ -176,7 +160,7 @@ class CameraShared:
|
|
176
160
|
self.rooms_colors = user_colors
|
177
161
|
|
178
162
|
def get_rooms_colors(self):
|
179
|
-
"""
|
163
|
+
"""Return rooms colors"""
|
180
164
|
return self.rooms_colors
|
181
165
|
|
182
166
|
def reset_trims(self) -> dict:
|
@@ -185,7 +169,7 @@ class CameraShared:
|
|
185
169
|
return self.trims
|
186
170
|
|
187
171
|
async def batch_update(self, **kwargs):
|
188
|
-
"""
|
172
|
+
"""Update the data of Shared in Batch"""
|
189
173
|
for key, value in kwargs.items():
|
190
174
|
setattr(self, key, value)
|
191
175
|
|
@@ -196,12 +180,14 @@ class CameraShared:
|
|
196
180
|
def generate_attributes(self) -> dict:
|
197
181
|
"""Generate and return the shared attribute's dictionary."""
|
198
182
|
attrs = {
|
183
|
+
ATTR_IMAGE_LAST_UPDATED: self.image_last_updated,
|
184
|
+
ATTR_CONTENT_TYPE: self.image_format,
|
185
|
+
ATTR_VACUUM_JSON_ID: self.vac_json_id,
|
199
186
|
ATTR_CAMERA_MODE: self.camera_mode,
|
187
|
+
ATTR_VACUUM_STATUS: self.vacuum_state,
|
200
188
|
ATTR_VACUUM_BATTERY: f"{self.vacuum_battery}%",
|
201
189
|
ATTR_VACUUM_CHARGING: self.vacuum_bat_charged(),
|
202
190
|
ATTR_VACUUM_POSITION: self.current_room,
|
203
|
-
ATTR_VACUUM_STATUS: self.vacuum_state,
|
204
|
-
ATTR_VACUUM_JSON_ID: self.vac_json_id,
|
205
191
|
ATTR_CALIBRATION_POINTS: self.attr_calibration_points,
|
206
192
|
}
|
207
193
|
if self.obstacles_pos and self.vacuum_ips:
|
@@ -210,24 +196,28 @@ class CameraShared:
|
|
210
196
|
)
|
211
197
|
attrs[ATTR_OBSTACLES] = self.obstacles_data
|
212
198
|
|
213
|
-
if self.enable_snapshots:
|
214
|
-
attrs[ATTR_SNAPSHOT] = self.snapshot_take
|
215
|
-
else:
|
216
|
-
attrs[ATTR_SNAPSHOT] = False
|
217
|
-
|
218
|
-
# Add dynamic shared attributes if they are available
|
219
199
|
shared_attrs = {
|
220
200
|
ATTR_ROOMS: self.map_rooms,
|
221
201
|
ATTR_ZONES: self.map_pred_zones,
|
222
202
|
ATTR_POINTS: self.map_pred_points,
|
223
203
|
}
|
224
|
-
|
225
204
|
for key, value in shared_attrs.items():
|
226
205
|
if value is not None:
|
227
206
|
attrs[key] = value
|
228
207
|
|
229
208
|
return attrs
|
230
209
|
|
210
|
+
def to_dict(self) -> dict:
|
211
|
+
"""Return a dictionary with image and attributes data."""
|
212
|
+
return {
|
213
|
+
"image": {
|
214
|
+
"binary": self.binary_image,
|
215
|
+
"pil_image": self.new_image,
|
216
|
+
"size": self.new_image.size if self.new_image else (10, 10),
|
217
|
+
},
|
218
|
+
"attributes": self.generate_attributes(),
|
219
|
+
}
|
220
|
+
|
231
221
|
|
232
222
|
class CameraSharedManager:
|
233
223
|
"""Camera Shared Manager class."""
|
@@ -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, Optional, Tuple, TypedDict, Union, List, NotRequired
|
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]]
|
@@ -210,9 +233,11 @@ NumpyArray = np.ndarray
|
|
210
233
|
Point = Tuple[int, int]
|
211
234
|
|
212
235
|
CAMERA_STORAGE = "valetudo_camera"
|
236
|
+
ATTR_IMAGE_LAST_UPDATED = "image_last_updated"
|
213
237
|
ATTR_ROTATE = "rotate_image"
|
214
238
|
ATTR_CROP = "crop_image"
|
215
239
|
ATTR_MARGINS = "margins"
|
240
|
+
ATTR_CONTENT_TYPE = "content_type"
|
216
241
|
CONF_OFFSET_TOP = "offset_top"
|
217
242
|
CONF_OFFSET_BOTTOM = "offset_bottom"
|
218
243
|
CONF_OFFSET_LEFT = "offset_left"
|