python-roborock 6.1.0__tar.gz → 6.2.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 (128) hide show
  1. {python_roborock-6.1.0 → python_roborock-6.2.0}/PKG-INFO +1 -1
  2. {python_roborock-6.1.0 → python_roborock-6.2.0}/pyproject.toml +1 -1
  3. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/b01_q10_map_parser.py +34 -16
  4. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/map_parser.py +61 -3
  5. python_roborock-6.2.0/roborock/map/room_colors.py +59 -0
  6. {python_roborock-6.1.0 → python_roborock-6.2.0}/.gitignore +0 -0
  7. {python_roborock-6.1.0 → python_roborock-6.2.0}/LICENSE +0 -0
  8. {python_roborock-6.1.0 → python_roborock-6.2.0}/README.md +0 -0
  9. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/__init__.py +0 -0
  10. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/broadcast_protocol.py +0 -0
  11. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/callbacks.py +0 -0
  12. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/cli.py +0 -0
  13. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/const.py +0 -0
  14. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/__init__.py +0 -0
  15. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/b01_q10/__init__.py +0 -0
  16. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  17. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  18. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/b01_q7/__init__.py +0 -0
  19. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  20. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  21. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/code_mappings.py +0 -0
  22. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/containers.py +0 -0
  23. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/dyad/__init__.py +0 -0
  24. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  25. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/dyad/dyad_containers.py +0 -0
  26. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/mower/__init__.py +0 -0
  27. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/mower/mower_containers.py +0 -0
  28. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/v1/__init__.py +0 -0
  29. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/v1/v1_clean_modes.py +0 -0
  30. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/v1/v1_code_mappings.py +0 -0
  31. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/v1/v1_containers.py +0 -0
  32. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/zeo/__init__.py +0 -0
  33. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  34. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/data/zeo/zeo_containers.py +0 -0
  35. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/device_features.py +0 -0
  36. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/README.md +0 -0
  37. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/__init__.py +0 -0
  38. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/cache.py +0 -0
  39. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/device.py +0 -0
  40. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/device_manager.py +0 -0
  41. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/file_cache.py +0 -0
  42. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/rpc/__init__.py +0 -0
  43. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/rpc/a01_channel.py +0 -0
  44. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
  45. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/rpc/b01_q7_channel.py +0 -0
  46. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/rpc/v1_channel.py +0 -0
  47. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/__init__.py +0 -0
  48. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/a01/__init__.py +0 -0
  49. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/__init__.py +0 -0
  50. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
  51. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/button_light.py +0 -0
  52. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
  53. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
  54. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/command.py +0 -0
  55. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/common.py +0 -0
  56. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/consumable.py +0 -0
  57. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
  58. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
  59. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/map.py +0 -0
  60. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/network_info.py +0 -0
  61. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/remote.py +0 -0
  62. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/status.py +0 -0
  63. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
  64. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q10/volume.py +0 -0
  65. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
  66. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
  67. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q7/map.py +0 -0
  68. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/b01/q7/map_content.py +0 -0
  69. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/common.py +0 -0
  70. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/traits_mixin.py +0 -0
  71. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/__init__.py +0 -0
  72. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/child_lock.py +0 -0
  73. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
  74. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/command.py +0 -0
  75. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/common.py +0 -0
  76. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/consumeable.py +0 -0
  77. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/device_features.py +0 -0
  78. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  79. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  80. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  81. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/home.py +0 -0
  82. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/led_status.py +0 -0
  83. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/map_content.py +0 -0
  84. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/maps.py +0 -0
  85. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/network_info.py +0 -0
  86. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/obstacle_photos.py +0 -0
  87. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/rooms.py +0 -0
  88. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/routines.py +0 -0
  89. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  90. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/status.py +0 -0
  91. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  92. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/volume.py +0 -0
  93. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  94. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/transport/__init__.py +0 -0
  95. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/transport/channel.py +0 -0
  96. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/transport/local_channel.py +0 -0
  97. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/devices/transport/mqtt_channel.py +0 -0
  98. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/diagnostics.py +0 -0
  99. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/exceptions.py +0 -0
  100. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/__init__.py +0 -0
  101. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/b01_grid_layers.py +0 -0
  102. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/b01_map_parser.py +0 -0
  103. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/b01_q10_overlays.py +0 -0
  104. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/b01_q10_render.py +0 -0
  105. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/proto/__init__.py +0 -0
  106. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/proto/b01_scmap.proto +0 -0
  107. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/map/proto/b01_scmap_pb2.py +0 -0
  108. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/mqtt/__init__.py +0 -0
  109. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/mqtt/health_manager.py +0 -0
  110. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/mqtt/roborock_session.py +0 -0
  111. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/mqtt/session.py +0 -0
  112. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/protocol.py +0 -0
  113. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/protocols/__init__.py +0 -0
  114. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/protocols/a01_protocol.py +0 -0
  115. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/protocols/b01_q10_protocol.py +0 -0
  116. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/protocols/b01_q7_protocol.py +0 -0
  117. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/protocols/v1_protocol.py +0 -0
  118. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/py.typed +0 -0
  119. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/roborock_message.py +0 -0
  120. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/roborock_typing.py +0 -0
  121. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/testing/__init__.py +0 -0
  122. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/testing/b01_q10_simulator.py +0 -0
  123. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/testing/channel.py +0 -0
  124. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/testing/cloud.py +0 -0
  125. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/testing/simulator.py +0 -0
  126. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/testing/v1_simulator.py +0 -0
  127. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/util.py +0 -0
  128. {python_roborock-6.1.0 → python_roborock-6.2.0}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 6.1.0
3
+ Version: 6.2.0
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Project-URL: Repository, https://github.com/python-roborock/python-roborock
6
6
  Project-URL: Documentation, https://python-roborock.readthedocs.io/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-roborock"
3
- version = "6.1.0"
3
+ version = "6.2.0"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
6
6
  requires-python = ">=3.11, <4"
@@ -19,13 +19,13 @@ documentation that informed this clean-room implementation comes from the
19
19
  https://github.com/v1b3c0d3x3r/roborock-qseries-map-bridge
20
20
  """
21
21
 
22
- import colorsys
23
22
  import io
24
23
  import math
25
24
  import statistics
26
25
  from dataclasses import dataclass, field, replace
27
26
 
28
27
  from PIL import Image
28
+ from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
29
29
  from vacuum_map_parser_base.config.image_config import ImageConfig
30
30
  from vacuum_map_parser_base.map_data import ImageData, MapData
31
31
 
@@ -40,6 +40,7 @@ from .b01_grid_layers import (
40
40
  decompose_grid,
41
41
  )
42
42
  from .map_parser import ParsedMapData
43
+ from .room_colors import adjacency_aware_room_colors
43
44
 
44
45
  _MAP_FILE_FORMAT = "PNG"
45
46
 
@@ -655,12 +656,12 @@ class B01Q10MapParser:
655
656
  return ParsedMapData(image_content=image_bytes.getvalue(), map_data=map_data)
656
657
 
657
658
  def _render(self, packet: Q10MapPacket) -> Image.Image:
658
- """Render the Q10 grid: rooms get distinct colors, walls white, rest dark."""
659
- palette = _build_palette(packet.grid)
660
- rgb = bytearray()
659
+ """Render the Q10 grid with the V1 map palette."""
660
+ palette = _build_palette(packet.grid, packet.width)
661
+ rgba = bytearray()
661
662
  for value in packet.grid:
662
- rgb.extend(palette[value])
663
- img = Image.frombytes("RGB", (packet.width, packet.height), bytes(rgb))
663
+ rgba.extend(palette[value])
664
+ img = Image.frombytes("RGBA", (packet.width, packet.height), bytes(rgba))
664
665
  # The ss07 grid is stored top-down (row 0 = top of the home), so it is
665
666
  # rendered as-is -- unlike the V1/Q7 convention, no vertical flip.
666
667
  scale = self._config.map_scale
@@ -669,15 +670,32 @@ class B01Q10MapParser:
669
670
  return img
670
671
 
671
672
 
672
- def _build_palette(grid: bytes) -> list[tuple[int, int, int]]:
673
- """Map each grid value to an RGB color (rooms distinct, walls white)."""
674
- palette: list[tuple[int, int, int]] = [(28, 30, 38)] * 256 # default: unknown/outside
675
- room_values = sorted({v for v in set(grid) if 0 < v < _WALL_THRESHOLD})
676
- for index, value in enumerate(room_values):
677
- hue = (index * 0.139) % 1.0
678
- r, g, b = colorsys.hsv_to_rgb(hue, 0.5, 0.95)
679
- palette[value] = (int(r * 255), int(g * 255), int(b * 255))
673
+ def _opaque(color: tuple[int, ...]) -> tuple[int, int, int, int]:
674
+ """Return a palette color as RGBA."""
675
+ return (color[0], color[1], color[2], color[3] if len(color) == 4 else 255)
676
+
677
+
678
+ def _build_palette(grid: bytes, width: int) -> list[tuple[int, int, int, int]]:
679
+ """Map Q10 cells onto the same colors used by the V1 map renderer."""
680
+
681
+ def room_id(value: int) -> int | None:
682
+ return max(1, value // 4) if 0 < value < _WALL_THRESHOLD else None
683
+
684
+ colors = ColorsPalette()
685
+ room_colors = adjacency_aware_room_colors(
686
+ grid,
687
+ width,
688
+ colors,
689
+ room_id,
690
+ )
691
+ outside = (0, 0, 0, 0)
692
+ palette = [outside] * 256
693
+ for value in {value for value in grid if 0 < value < _WALL_THRESHOLD}:
694
+ palette[value] = _opaque(room_colors[max(1, value // 4)])
695
+ wall = _opaque(colors.get_color(SupportedColor.GREY_WALL))
680
696
  for value in range(_WALL_THRESHOLD, 256):
681
- palette[value] = (235, 235, 240) # walls / borders
682
- palette[0] = (28, 30, 38)
697
+ palette[value] = wall
698
+ palette[_UNSEGMENTED_FLOOR_VALUE] = _opaque(colors.get_color(SupportedColor.MAP_INSIDE))
699
+ palette[_BACKGROUND_VALUE] = outside
700
+ palette[0] = outside
683
701
  return palette
@@ -2,6 +2,7 @@
2
2
 
3
3
  import io
4
4
  import logging
5
+ import threading
5
6
  from dataclasses import dataclass, field
6
7
 
7
8
  from vacuum_map_parser_base.config.color import ColorsPalette, SupportedColor
@@ -9,10 +10,13 @@ from vacuum_map_parser_base.config.drawable import Drawable
9
10
  from vacuum_map_parser_base.config.image_config import ImageConfig
10
11
  from vacuum_map_parser_base.config.size import Size, Sizes
11
12
  from vacuum_map_parser_base.map_data import MapData
13
+ from vacuum_map_parser_roborock.image_parser import RoborockImageParser
12
14
  from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser
13
15
 
14
16
  from roborock.exceptions import RoborockException
15
17
 
18
+ from .room_colors import adjacency_aware_room_colors
19
+
16
20
  _LOGGER = logging.getLogger(__name__)
17
21
 
18
22
  DEFAULT_DRAWABLES = {
@@ -98,6 +102,52 @@ class MapParser:
98
102
  return ParsedMapData(image_content=img_byte_arr.getvalue(), map_data=parsed_map)
99
103
 
100
104
 
105
+ class _AdjacencyAwareRoborockImageParser(RoborockImageParser):
106
+ """Apply the shared adjacency color policy to V1 room cells."""
107
+
108
+ def __init__(
109
+ self,
110
+ palette: ColorsPalette,
111
+ image_config: ImageConfig,
112
+ *,
113
+ recolor_rooms: bool = True,
114
+ ) -> None:
115
+ super().__init__(palette, image_config)
116
+ self._room_palette = palette
117
+ self._base_room_colors = palette.cached_room_colors.copy()
118
+ self._recolor_rooms = recolor_rooms
119
+ self._palette_lock = threading.Lock()
120
+
121
+ def parse(
122
+ self,
123
+ raw_data: bytes,
124
+ width: int,
125
+ height: int,
126
+ carpet_map: set[int] | None,
127
+ removed_map: set[int] | None = None,
128
+ ):
129
+ """Assign non-conflicting room colors before the V1 image pass."""
130
+ with self._palette_lock:
131
+ # cached_room_colors is a read-only property, so reset its dict in place.
132
+ cached_room_colors = self._room_palette.cached_room_colors
133
+ cached_room_colors.clear()
134
+ cached_room_colors.update(self._base_room_colors)
135
+
136
+ if self._recolor_rooms:
137
+
138
+ def room_id(value: int) -> int | None:
139
+ if value in (self.MAP_OUTSIDE, self.MAP_WALL, self.MAP_INSIDE, self.MAP_SCAN):
140
+ return None
141
+ return self._get_room_number(value) if value & 0x07 == 0x07 else None
142
+
143
+ room_colors = adjacency_aware_room_colors(raw_data, width, self._room_palette, room_id)
144
+ for number, color in room_colors.items():
145
+ # ColorsPalette caches both forms for get_room_color(str | int).
146
+ cached_room_colors[number] = color
147
+ cached_room_colors[str(number)] = color
148
+ return super().parse(raw_data, width, height, carpet_map, removed_map)
149
+
150
+
101
151
  def _create_map_data_parser(config: MapParserConfig) -> RoborockMapDataParser:
102
152
  """Create a RoborockMapDataParser based on the config entry."""
103
153
  color_dicts = {}
@@ -114,10 +164,18 @@ def _create_map_data_parser(config: MapParserConfig) -> RoborockMapDataParser:
114
164
  if not config.show_rooms:
115
165
  room_colors = {str(x): (0, 0, 0, 0) for x in range(1, 32)}
116
166
 
117
- return RoborockMapDataParser(
118
- ColorsPalette(color_dicts, room_colors),
167
+ palette = ColorsPalette(color_dicts, room_colors)
168
+ image_config = ImageConfig(scale=config.map_scale)
169
+ parser = RoborockMapDataParser(
170
+ palette,
119
171
  Sizes({k: v * config.map_scale for k, v in Sizes.SIZES.items() if k != Size.MOP_PATH_WIDTH}),
120
172
  config.drawables,
121
- ImageConfig(scale=config.map_scale),
173
+ image_config,
122
174
  [],
123
175
  )
176
+ parser._image_parser = _AdjacencyAwareRoborockImageParser(
177
+ palette,
178
+ image_config,
179
+ recolor_rooms=config.show_rooms,
180
+ )
181
+ return parser
@@ -0,0 +1,59 @@
1
+ """Deterministic room colors that keep adjacent segments distinguishable."""
2
+
3
+ from collections.abc import Callable, Sequence
4
+
5
+ from vacuum_map_parser_base.config.color import Color, ColorsPalette
6
+
7
+ RoomIdFromCell = Callable[[int], int | None]
8
+
9
+
10
+ def adjacency_aware_room_colors(
11
+ grid: Sequence[int],
12
+ width: int,
13
+ palette: ColorsPalette,
14
+ room_id_from_cell: RoomIdFromCell,
15
+ ) -> dict[int, Color]:
16
+ """Return room colors, changing only adjacent same-color conflicts.
17
+
18
+ Room IDs remain the stable preference, matching the existing V1 palette.
19
+ When two rooms sharing an edge resolve to the same RGB value, the
20
+ higher-numbered room receives the first palette color not already used by
21
+ one of its colored neighbors.
22
+ """
23
+ if width <= 0:
24
+ return {}
25
+
26
+ room_ids: set[int] = set()
27
+ neighbors: dict[int, set[int]] = {}
28
+ for index, value in enumerate(grid):
29
+ room_id = room_id_from_cell(value)
30
+ if room_id is None:
31
+ continue
32
+ room_ids.add(room_id)
33
+ neighbors.setdefault(room_id, set())
34
+
35
+ for neighbor_index in (index - 1 if index % width else -1, index - width):
36
+ if neighbor_index < 0:
37
+ continue
38
+ neighbor_id = room_id_from_cell(grid[neighbor_index])
39
+ if neighbor_id is None or neighbor_id == room_id:
40
+ continue
41
+ neighbors[room_id].add(neighbor_id)
42
+ neighbors.setdefault(neighbor_id, set()).add(room_id)
43
+
44
+ candidates: list[Color] = []
45
+ for palette_id in map(int, ColorsPalette.ROOM_COLORS):
46
+ color = palette.get_room_color(palette_id)
47
+ if color not in candidates:
48
+ candidates.append(color)
49
+
50
+ assigned: dict[int, Color] = {}
51
+ for room_id in sorted(room_ids):
52
+ preferred = palette.get_room_color(room_id)
53
+ neighbor_colors = {assigned[neighbor] for neighbor in neighbors[room_id] if neighbor in assigned}
54
+ assigned[room_id] = (
55
+ preferred
56
+ if preferred not in neighbor_colors
57
+ else next((color for color in candidates if color not in neighbor_colors), preferred)
58
+ )
59
+ return assigned
File without changes