python-roborock 2.48.0__tar.gz → 2.49.0__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.
Files changed (61) hide show
  1. {python_roborock-2.48.0 → python_roborock-2.49.0}/PKG-INFO +1 -1
  2. {python_roborock-2.48.0 → python_roborock-2.49.0}/pyproject.toml +1 -1
  3. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/clean_modes.py +50 -10
  4. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/device_features.py +52 -7
  5. python_roborock-2.49.0/roborock/map/map_parser.py +106 -0
  6. {python_roborock-2.48.0 → python_roborock-2.49.0}/LICENSE +0 -0
  7. {python_roborock-2.48.0 → python_roborock-2.49.0}/README.md +0 -0
  8. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/__init__.py +0 -0
  9. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/api.py +0 -0
  10. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/b01_containers.py +0 -0
  11. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/broadcast_protocol.py +0 -0
  12. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/callbacks.py +0 -0
  13. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/cli.py +0 -0
  14. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/cloud_api.py +0 -0
  15. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/code_mappings.py +0 -0
  16. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/command_cache.py +0 -0
  17. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/const.py +0 -0
  18. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/containers.py +0 -0
  19. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/README.md +0 -0
  20. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/__init__.py +0 -0
  21. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/a01_channel.py +0 -0
  22. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/b01_channel.py +0 -0
  23. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/cache.py +0 -0
  24. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/channel.py +0 -0
  25. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/device.py +0 -0
  26. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/device_manager.py +0 -0
  27. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/local_channel.py +0 -0
  28. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/mqtt_channel.py +0 -0
  29. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/__init__.py +0 -0
  30. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/a01/__init__.py +0 -0
  31. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/b01/__init__.py +0 -0
  32. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/traits_mixin.py +0 -0
  33. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/v1/__init__.py +0 -0
  34. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
  35. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/v1/common.py +0 -0
  36. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  37. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/v1/status.py +0 -0
  38. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/traits/v1/volume.py +0 -0
  39. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/v1_channel.py +0 -0
  40. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/devices/v1_rpc_channel.py +0 -0
  41. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/exceptions.py +0 -0
  42. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/mqtt/__init__.py +0 -0
  43. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/mqtt/roborock_session.py +0 -0
  44. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/mqtt/session.py +0 -0
  45. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/protocol.py +0 -0
  46. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/protocols/a01_protocol.py +0 -0
  47. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/protocols/b01_protocol.py +0 -0
  48. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/protocols/v1_protocol.py +0 -0
  49. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/py.typed +0 -0
  50. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/roborock_future.py +0 -0
  51. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/roborock_message.py +0 -0
  52. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/roborock_typing.py +0 -0
  53. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/util.py +0 -0
  54. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/version_1_apis/__init__.py +0 -0
  55. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  56. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
  57. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  58. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/version_a01_apis/__init__.py +0 -0
  59. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  60. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
  61. {python_roborock-2.48.0 → python_roborock-2.49.0}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-roborock
3
- Version: 2.48.0
3
+ Version: 2.49.0
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Home-page: https://github.com/humbertogontijo/python-roborock
6
6
  License: GPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-roborock"
3
- version = "2.48.0"
3
+ version = "2.49.0"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
6
6
  license = "GPL-3.0-only"
@@ -1,11 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- from roborock import DeviceFeatures
4
-
5
3
  from .code_mappings import RoborockModeEnum
4
+ from .device_features import DeviceFeatures
6
5
 
7
6
 
8
- class CleanModes(RoborockModeEnum):
7
+ class VacuumModes(RoborockModeEnum):
9
8
  GENTLE = ("gentle", 105)
10
9
  OFF = ("off", 105)
11
10
  QUIET = ("quiet", 101)
@@ -27,7 +26,7 @@ class CleanRoutes(RoborockModeEnum):
27
26
  CUSTOMIZED = ("custom", 302)
28
27
 
29
28
 
30
- class CleanModesOld(RoborockModeEnum):
29
+ class VacuumModesOld(RoborockModeEnum):
31
30
  QUIET = ("quiet", 38)
32
31
  BALANCED = ("balanced", 60)
33
32
  TURBO = ("turbo", 75)
@@ -48,18 +47,40 @@ class WaterModes(RoborockModeEnum):
48
47
  SMART_MODE = ("smart_mode", 209)
49
48
 
50
49
 
51
- def get_clean_modes(features: DeviceFeatures) -> list[CleanModes]:
50
+ class WashTowelModes(RoborockModeEnum):
51
+ SMART = ("smart", 10)
52
+ LIGHT = ("light", 0)
53
+ BALANCED = ("balanced", 1)
54
+ DEEP = ("deep", 2)
55
+ SUPER_DEEP = ("super_deep", 8)
56
+
57
+
58
+ def get_wash_towel_modes(features: DeviceFeatures) -> list[WashTowelModes]:
59
+ """Get the valid wash towel modes for the device"""
60
+ modes = [WashTowelModes.LIGHT, WashTowelModes.BALANCED, WashTowelModes.DEEP]
61
+ if features.is_super_deep_wash_supported and not features.is_dirty_replenish_clean_supported:
62
+ modes.append(WashTowelModes.SUPER_DEEP)
63
+ elif features.is_dirty_replenish_clean_supported:
64
+ modes.append(WashTowelModes.SMART)
65
+ return modes
66
+
67
+
68
+ def get_clean_modes(features: DeviceFeatures) -> list[VacuumModes]:
52
69
  """Get the valid clean modes for the device - also known as 'fan power' or 'suction mode'"""
53
- modes = [CleanModes.QUIET, CleanModes.BALANCED, CleanModes.TURBO, CleanModes.MAX]
70
+ modes = [VacuumModes.QUIET, VacuumModes.BALANCED, VacuumModes.TURBO, VacuumModes.MAX]
54
71
  if features.is_max_plus_mode_supported or features.is_none_pure_clean_mop_with_max_plus:
55
72
  # If the vacuum has max plus mode supported
56
- modes.append(CleanModes.MAX_PLUS)
73
+ modes.append(VacuumModes.MAX_PLUS)
57
74
  if features.is_pure_clean_mop_supported:
58
75
  # If the vacuum is capable of 'pure mop clean' aka no vacuum
59
- modes.append(CleanModes.OFF)
76
+ modes.append(VacuumModes.OFF)
60
77
  else:
61
78
  # If not, we can add gentle
62
- modes.append(CleanModes.GENTLE)
79
+ modes.append(VacuumModes.GENTLE)
80
+ if features.is_smart_clean_mode_set_supported:
81
+ modes.append(VacuumModes.SMART_MODE)
82
+ if features.is_customized_clean_supported:
83
+ modes.append(VacuumModes.CUSTOMIZED)
63
84
  return modes
64
85
 
65
86
 
@@ -72,7 +93,7 @@ def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]
72
93
  if not (
73
94
  features.is_corner_clean_mode_supported
74
95
  and features.is_clean_route_deep_slow_plus_supported
75
- and region == "CN"
96
+ and region == "cn"
76
97
  ):
77
98
  # for some reason there is a china specific deep plus mode
78
99
  supported.append(CleanRoutes.DEEP_PLUS_CN)
@@ -81,6 +102,11 @@ def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]
81
102
 
82
103
  if features.is_clean_route_fast_mode_supported:
83
104
  supported.append(CleanRoutes.FAST)
105
+ if features.is_smart_clean_mode_set_supported:
106
+ supported.append(CleanRoutes.SMART_MODE)
107
+ if features.is_customized_clean_supported:
108
+ supported.append(CleanRoutes.CUSTOMIZED)
109
+
84
110
  return supported
85
111
 
86
112
 
@@ -97,4 +123,18 @@ def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
97
123
  supported_modes.append(WaterModes.CUSTOM)
98
124
  if features.is_mop_shake_module_supported and features.is_mop_shake_water_max_supported:
99
125
  supported_modes.append(WaterModes.EXTREME)
126
+ if features.is_smart_clean_mode_set_supported:
127
+ supported_modes.append(WaterModes.SMART_MODE)
128
+ if features.is_customized_clean_supported:
129
+ supported_modes.append(WaterModes.CUSTOMIZED)
130
+
100
131
  return supported_modes
132
+
133
+
134
+ def is_smart_mode_set(water_mode: WaterModes, clean_mode: VacuumModes, mop_mode: CleanRoutes) -> bool:
135
+ """Check if the smart mode is set for the given water mode and clean mode"""
136
+ return (
137
+ water_mode == WaterModes.SMART_MODE
138
+ or clean_mode == VacuumModes.SMART_MODE
139
+ or mop_mode == CleanRoutes.SMART_MODE
140
+ )
@@ -116,14 +116,45 @@ DUAL_LINE_CAMERA_FEATURES = [
116
116
  NEW_DEFAULT_FEATURES = [ProductFeatures.REMOTE_BACK, ProductFeatures.CLEANMODE_MAXPLUS]
117
117
 
118
118
 
119
- PEARL_FEATURES = NEW_DEFAULT_FEATURES + SINGLE_LINE_CAMERA_FEATURES + [ProductFeatures.MOP_SPIN_MODULE]
119
+ PEARL_FEATURES = SINGLE_LINE_CAMERA_FEATURES + [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SPIN_MODULE]
120
120
  PEARL_PLUS_FEATURES = NEW_DEFAULT_FEATURES + RGB_CAMERA_FEATURES + [ProductFeatures.MOP_SPIN_MODULE]
121
121
  ULTRON_FEATURES = NEW_DEFAULT_FEATURES + DUAL_LINE_CAMERA_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
122
122
  ULTRONSV_FEATURES = NEW_DEFAULT_FEATURES + RGB_CAMERA_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
123
- TANOSS_FEATURES = NEW_DEFAULT_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
124
- TOPAZSPOWER_FEATURES = NEW_DEFAULT_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
123
+ TANOSS_FEATURES = [ProductFeatures.REMOTE_BACK, ProductFeatures.MOP_SHAKE_MODULE]
124
+ TOPAZSPOWER_FEATURES = [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SHAKE_MODULE]
125
125
 
126
- product_feature_map = {
126
+ PRODUCTS_WITHOUT_CUSTOM_CLEAN: set[RoborockProductNickname] = {
127
+ RoborockProductNickname.TANOS,
128
+ RoborockProductNickname.RUBYPLUS,
129
+ RoborockProductNickname.RUBYSC,
130
+ RoborockProductNickname.RUBYSE,
131
+ }
132
+ PRODUCTS_WITHOUT_DEFAULT_3D_MAP: set[RoborockProductNickname] = {
133
+ RoborockProductNickname.TANOS,
134
+ RoborockProductNickname.TANOSSPLUS,
135
+ RoborockProductNickname.TANOSE,
136
+ RoborockProductNickname.TANOSV,
137
+ RoborockProductNickname.RUBYPLUS,
138
+ RoborockProductNickname.RUBYSC,
139
+ RoborockProductNickname.RUBYSE,
140
+ }
141
+ PRODUCTS_WITHOUT_PURE_CLEAN_MOP: set[RoborockProductNickname] = {
142
+ RoborockProductNickname.TANOS,
143
+ RoborockProductNickname.TANOSE,
144
+ RoborockProductNickname.TANOSV,
145
+ RoborockProductNickname.TANOSSLITE,
146
+ RoborockProductNickname.TANOSSE,
147
+ RoborockProductNickname.TANOSSC,
148
+ RoborockProductNickname.ULTRONLITE,
149
+ RoborockProductNickname.ULTRONE,
150
+ RoborockProductNickname.RUBYPLUS,
151
+ RoborockProductNickname.RUBYSLITE,
152
+ RoborockProductNickname.RUBYSC,
153
+ RoborockProductNickname.RUBYSE,
154
+ }
155
+
156
+ # Base map containing the initial, unconditional features for each product.
157
+ _BASE_PRODUCT_FEATURE_MAP: dict[RoborockProductNickname, list[ProductFeatures]] = {
127
158
  RoborockProductNickname.PEARL: PEARL_FEATURES,
128
159
  RoborockProductNickname.PEARLS: PEARL_FEATURES,
129
160
  RoborockProductNickname.PEARLPLUS: PEARL_PLUS_FEATURES,
@@ -139,7 +170,8 @@ product_feature_map = {
139
170
  RoborockProductNickname.PEARLSLITE: PEARL_FEATURES,
140
171
  RoborockProductNickname.PEARLE: PEARL_FEATURES,
141
172
  RoborockProductNickname.PEARLELITE: PEARL_FEATURES,
142
- RoborockProductNickname.VIVIANC: PEARL_PLUS_FEATURES,
173
+ RoborockProductNickname.VIVIANC: [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SPIN_MODULE]
174
+ + SINGLE_LINE_CAMERA_FEATURES,
143
175
  RoborockProductNickname.CORALPRO: PEARL_PLUS_FEATURES,
144
176
  RoborockProductNickname.ULTRONLITE: SINGLE_LINE_CAMERA_FEATURES
145
177
  + [ProductFeatures.CLEANMODE_NONE_PURECLEANMOP_WITH_MAXPLUS, ProductFeatures.MOP_ELECTRONIC_MODULE],
@@ -150,7 +182,7 @@ product_feature_map = {
150
182
  ],
151
183
  RoborockProductNickname.ULTRONSPLUS: ULTRON_FEATURES,
152
184
  RoborockProductNickname.VERDELITE: ULTRONSV_FEATURES,
153
- RoborockProductNickname.TOPAZS: NEW_DEFAULT_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE],
185
+ RoborockProductNickname.TOPAZS: [ProductFeatures.REMOTE_BACK, ProductFeatures.MOP_SHAKE_MODULE],
154
186
  RoborockProductNickname.TOPAZSPLUS: NEW_DEFAULT_FEATURES
155
187
  + DUAL_LINE_CAMERA_FEATURES
156
188
  + [ProductFeatures.MOP_SHAKE_MODULE],
@@ -173,6 +205,16 @@ product_feature_map = {
173
205
  RoborockProductNickname.RUBYSLITE: [ProductFeatures.MOP_ELECTRONIC_MODULE],
174
206
  }
175
207
 
208
+ PRODUCT_FEATURE_MAP: dict[RoborockProductNickname, list[ProductFeatures]] = {
209
+ product: (
210
+ features
211
+ + ([ProductFeatures.DEFAULT_CLEANMODECUSTOM] if product not in PRODUCTS_WITHOUT_CUSTOM_CLEAN else [])
212
+ + ([ProductFeatures.DEFAULT_MAP3D] if product not in PRODUCTS_WITHOUT_DEFAULT_3D_MAP else [])
213
+ + ([ProductFeatures.CLEANMODE_PURECLEANMOP] if product not in PRODUCTS_WITHOUT_PURE_CLEAN_MOP else [])
214
+ )
215
+ for product, features in _BASE_PRODUCT_FEATURE_MAP.items()
216
+ }
217
+
176
218
 
177
219
  @dataclass
178
220
  class DeviceFeatures:
@@ -424,6 +466,9 @@ class DeviceFeatures:
424
466
  metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE, ProductFeatures.MOP_SPIN_MODULE]}
425
467
  )
426
468
  is_mop_shake_module_supported: bool = field(metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE]})
469
+ is_customized_clean_supported: bool = field(
470
+ metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE, ProductFeatures.MOP_SPIN_MODULE]}
471
+ )
427
472
 
428
473
  @classmethod
429
474
  def from_feature_flags(
@@ -493,7 +538,7 @@ class DeviceFeatures:
493
538
  kwargs[f.name] = product_nickname not in blacklist
494
539
  elif (product_features := f.metadata.get("product_features")) is not None:
495
540
  if product_nickname is not None:
496
- available_features = product_feature_map.get(product_nickname, [])
541
+ available_features = PRODUCT_FEATURE_MAP.get(product_nickname, [])
497
542
  if any(feat in available_features for feat in product_features): # type: ignore
498
543
  kwargs[f.name] = True
499
544
 
@@ -0,0 +1,106 @@
1
+ """Module for parsing v1 Roborock map content."""
2
+
3
+ import io
4
+ import logging
5
+ from dataclasses import dataclass, field
6
+
7
+ from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
8
+ from vacuum_map_parser_base.config.drawable import Drawable
9
+ from vacuum_map_parser_base.config.image_config import ImageConfig
10
+ from vacuum_map_parser_base.config.size import Size, Sizes
11
+ from vacuum_map_parser_base.map_data import MapData
12
+ from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser
13
+
14
+ from roborock.exceptions import RoborockException
15
+
16
+ _LOGGER = logging.getLogger(__name__)
17
+
18
+ DEFAULT_DRAWABLES = {
19
+ Drawable.CHARGER: True,
20
+ Drawable.CLEANED_AREA: False,
21
+ Drawable.GOTO_PATH: False,
22
+ Drawable.IGNORED_OBSTACLES: False,
23
+ Drawable.IGNORED_OBSTACLES_WITH_PHOTO: False,
24
+ Drawable.MOP_PATH: False,
25
+ Drawable.NO_CARPET_AREAS: False,
26
+ Drawable.NO_GO_AREAS: False,
27
+ Drawable.NO_MOPPING_AREAS: False,
28
+ Drawable.OBSTACLES: False,
29
+ Drawable.OBSTACLES_WITH_PHOTO: False,
30
+ Drawable.PATH: True,
31
+ Drawable.PREDICTED_PATH: False,
32
+ Drawable.VACUUM_POSITION: True,
33
+ Drawable.VIRTUAL_WALLS: False,
34
+ Drawable.ZONES: False,
35
+ }
36
+ DEFAULT_MAP_SCALE = 4
37
+ MAP_FILE_FORMAT = "PNG"
38
+
39
+
40
+ def _default_drawable_factory() -> list[Drawable]:
41
+ return [drawable for drawable, default_value in DEFAULT_DRAWABLES.items() if default_value]
42
+
43
+
44
+ @dataclass
45
+ class MapParserConfig:
46
+ """Configuration for the Roborock map parser."""
47
+
48
+ drawables: list[Drawable] = field(default_factory=_default_drawable_factory)
49
+ """List of drawables to include in the map rendering."""
50
+
51
+ show_background: bool = True
52
+ """Whether to show the background of the map."""
53
+
54
+ map_scale: int = DEFAULT_MAP_SCALE
55
+ """Scale factor for the map."""
56
+
57
+
58
+ @dataclass
59
+ class ParsedMapData:
60
+ """Roborock Map Data.
61
+
62
+ This class holds the parsed map data and the rendered image.
63
+ """
64
+
65
+ image_content: bytes | None
66
+ """The rendered image of the map in PNG format."""
67
+
68
+ map_data: MapData | None
69
+ """The parsed map data which contains metadata for points on the map."""
70
+
71
+
72
+ class MapParser:
73
+ """Roborock Map Parser.
74
+
75
+ This class is used to parse the map data from the device and render it into an image.
76
+ """
77
+
78
+ def __init__(self, config: MapParserConfig) -> None:
79
+ """Initialize the MapParser."""
80
+ self._map_parser = _create_map_data_parser(config)
81
+
82
+ def parse(self, map_bytes: bytes) -> ParsedMapData | None:
83
+ """Parse map_bytes and return MapData and the image."""
84
+ try:
85
+ parsed_map = self._map_parser.parse(map_bytes)
86
+ except (IndexError, ValueError) as err:
87
+ raise RoborockException("Failed to parse map data") from err
88
+ if parsed_map.image is None:
89
+ raise RoborockException("Failed to render map image")
90
+ img_byte_arr = io.BytesIO()
91
+ parsed_map.image.data.save(img_byte_arr, format=MAP_FILE_FORMAT)
92
+ return ParsedMapData(image_content=img_byte_arr.getvalue(), map_data=parsed_map)
93
+
94
+
95
+ def _create_map_data_parser(config: MapParserConfig) -> RoborockMapDataParser:
96
+ """Create a RoborockMapDataParser based on the config entry."""
97
+ colors = ColorsPalette()
98
+ if not config.show_background:
99
+ colors = ColorsPalette({SupportedColor.MAP_OUTSIDE: (0, 0, 0, 0)})
100
+ return RoborockMapDataParser(
101
+ colors,
102
+ Sizes({k: v * config.map_scale for k, v in Sizes.SIZES.items() if k != Size.MOP_PATH_WIDTH}),
103
+ config.drawables,
104
+ ImageConfig(scale=config.map_scale),
105
+ [],
106
+ )