python-roborock 5.34.0__tar.gz → 5.35.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 (127) hide show
  1. {python_roborock-5.34.0 → python_roborock-5.35.0}/PKG-INFO +1 -1
  2. {python_roborock-5.34.0 → python_roborock-5.35.0}/pyproject.toml +1 -1
  3. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/__init__.py +8 -3
  4. python_roborock-5.35.0/roborock/devices/traits/b01/q10/map.py +151 -0
  5. python_roborock-5.34.0/roborock/devices/traits/b01/q10/map.py +0 -110
  6. {python_roborock-5.34.0 → python_roborock-5.35.0}/.gitignore +0 -0
  7. {python_roborock-5.34.0 → python_roborock-5.35.0}/LICENSE +0 -0
  8. {python_roborock-5.34.0 → python_roborock-5.35.0}/README.md +0 -0
  9. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/__init__.py +0 -0
  10. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/broadcast_protocol.py +0 -0
  11. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/callbacks.py +0 -0
  12. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/cli.py +0 -0
  13. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/const.py +0 -0
  14. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/__init__.py +0 -0
  15. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/b01_q10/__init__.py +0 -0
  16. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  17. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  18. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/b01_q7/__init__.py +0 -0
  19. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  20. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  21. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/code_mappings.py +0 -0
  22. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/containers.py +0 -0
  23. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/dyad/__init__.py +0 -0
  24. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  25. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/dyad/dyad_containers.py +0 -0
  26. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/mower/__init__.py +0 -0
  27. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/mower/mower_containers.py +0 -0
  28. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/v1/__init__.py +0 -0
  29. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/v1/v1_clean_modes.py +0 -0
  30. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/v1/v1_code_mappings.py +0 -0
  31. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/v1/v1_containers.py +0 -0
  32. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/zeo/__init__.py +0 -0
  33. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  34. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/data/zeo/zeo_containers.py +0 -0
  35. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/device_features.py +0 -0
  36. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/README.md +0 -0
  37. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/__init__.py +0 -0
  38. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/cache.py +0 -0
  39. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/device.py +0 -0
  40. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/device_manager.py +0 -0
  41. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/file_cache.py +0 -0
  42. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/rpc/__init__.py +0 -0
  43. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/rpc/a01_channel.py +0 -0
  44. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
  45. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/rpc/b01_q7_channel.py +0 -0
  46. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/rpc/v1_channel.py +0 -0
  47. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/__init__.py +0 -0
  48. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/a01/__init__.py +0 -0
  49. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/__init__.py +0 -0
  50. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/button_light.py +0 -0
  51. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/child_lock.py +0 -0
  52. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/clean_history.py +0 -0
  53. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/command.py +0 -0
  54. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/common.py +0 -0
  55. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/consumable.py +0 -0
  56. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/do_not_disturb.py +0 -0
  57. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/dust_collection.py +0 -0
  58. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/network_info.py +0 -0
  59. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/remote.py +0 -0
  60. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/status.py +0 -0
  61. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/vacuum.py +0 -0
  62. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q10/volume.py +0 -0
  63. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
  64. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
  65. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q7/map.py +0 -0
  66. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/b01/q7/map_content.py +0 -0
  67. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/common.py +0 -0
  68. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/traits_mixin.py +0 -0
  69. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/__init__.py +0 -0
  70. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/child_lock.py +0 -0
  71. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
  72. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/command.py +0 -0
  73. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/common.py +0 -0
  74. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/consumeable.py +0 -0
  75. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/device_features.py +0 -0
  76. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  77. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  78. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  79. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/home.py +0 -0
  80. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/led_status.py +0 -0
  81. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/map_content.py +0 -0
  82. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/maps.py +0 -0
  83. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/network_info.py +0 -0
  84. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/rooms.py +0 -0
  85. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/routines.py +0 -0
  86. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  87. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/status.py +0 -0
  88. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  89. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/volume.py +0 -0
  90. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  91. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/transport/__init__.py +0 -0
  92. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/transport/channel.py +0 -0
  93. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/transport/local_channel.py +0 -0
  94. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/devices/transport/mqtt_channel.py +0 -0
  95. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/diagnostics.py +0 -0
  96. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/exceptions.py +0 -0
  97. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/__init__.py +0 -0
  98. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/b01_grid_layers.py +0 -0
  99. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/b01_map_parser.py +0 -0
  100. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/b01_q10_map_parser.py +0 -0
  101. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/b01_q10_overlays.py +0 -0
  102. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/b01_q10_render.py +0 -0
  103. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/map_parser.py +0 -0
  104. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/proto/__init__.py +0 -0
  105. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/proto/b01_scmap.proto +0 -0
  106. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/map/proto/b01_scmap_pb2.py +0 -0
  107. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/mqtt/__init__.py +0 -0
  108. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/mqtt/health_manager.py +0 -0
  109. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/mqtt/roborock_session.py +0 -0
  110. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/mqtt/session.py +0 -0
  111. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/protocol.py +0 -0
  112. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/protocols/__init__.py +0 -0
  113. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/protocols/a01_protocol.py +0 -0
  114. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/protocols/b01_q10_protocol.py +0 -0
  115. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/protocols/b01_q7_protocol.py +0 -0
  116. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/protocols/v1_protocol.py +0 -0
  117. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/py.typed +0 -0
  118. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/roborock_message.py +0 -0
  119. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/roborock_typing.py +0 -0
  120. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/testing/__init__.py +0 -0
  121. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/testing/b01_q10_simulator.py +0 -0
  122. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/testing/channel.py +0 -0
  123. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/testing/cloud.py +0 -0
  124. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/testing/simulator.py +0 -0
  125. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/testing/v1_simulator.py +0 -0
  126. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/util.py +0 -0
  127. {python_roborock-5.34.0 → python_roborock-5.35.0}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 5.34.0
3
+ Version: 5.35.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 = "5.34.0"
3
+ version = "5.35.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"
@@ -16,7 +16,7 @@ from .command import CommandTrait
16
16
  from .consumable import ConsumableTrait
17
17
  from .do_not_disturb import DoNotDisturbTrait
18
18
  from .dust_collection import DustCollectionTrait
19
- from .map import MapContentTrait
19
+ from .map import MapContentTrait, MapDpsTrait
20
20
  from .network_info import NetworkInfoTrait
21
21
  from .remote import RemoteTrait
22
22
  from .status import StatusTrait
@@ -77,7 +77,10 @@ class Q10PropertiesApi(Trait):
77
77
  """Trait exposing remaining life of consumables."""
78
78
 
79
79
  map: MapContentTrait
80
- """Trait for fetching the current parsed map (image + rooms)."""
80
+ """Composed map image plus caller-facing map and trace data."""
81
+
82
+ _map_dps: MapDpsTrait
83
+ """Private source of restricted zones and virtual walls received through DPS."""
81
84
 
82
85
  clean_history: CleanHistoryTrait
83
86
  """Trait for fetching the device clean-record history (``dpCleanRecord``)."""
@@ -96,7 +99,8 @@ class Q10PropertiesApi(Trait):
96
99
  self.button_light = ButtonLightTrait(self.command)
97
100
  self.network_info = NetworkInfoTrait()
98
101
  self.consumable = ConsumableTrait()
99
- self.map = MapContentTrait()
102
+ self._map_dps = MapDpsTrait()
103
+ self.map = MapContentTrait(self._map_dps)
100
104
  self.clean_history = CleanHistoryTrait(self.command)
101
105
  # Read-model traits updated from the device's DPS push stream.
102
106
  self._updatable_traits = [
@@ -108,6 +112,7 @@ class Q10PropertiesApi(Trait):
108
112
  self.network_info,
109
113
  self.consumable,
110
114
  self.clean_history,
115
+ self._map_dps,
111
116
  ]
112
117
  self._subscribe_task: asyncio.Task[None] | None = None
113
118
 
@@ -0,0 +1,151 @@
1
+ """Push-driven map traits for B01 Q10 devices.
2
+
3
+ Map-related state arrives on three independent streams:
4
+
5
+ * map packets are decoded from map-protocol responses;
6
+ * trace packets are decoded from trace-protocol responses;
7
+ * restricted zones and virtual walls arrive as ordinary DPS values.
8
+
9
+ ``MapDpsTrait`` owns the low-level DPS read model. ``MapContentTrait`` depends
10
+ on it and combines that state with the latest map/trace packets through the pure
11
+ functions in :mod:`roborock.map.b01_q10_render`. The high-level trait keeps only
12
+ the latest value from each source and one replace-whole rendered image;
13
+ calibration, path placement and overlay placement remain inside the renderer.
14
+ """
15
+
16
+ import logging
17
+ from dataclasses import dataclass, field
18
+ from typing import Any
19
+
20
+ from roborock.data import RoborockBase
21
+ from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
22
+ from roborock.devices.traits.common import DpsDataConverter, TraitUpdateListener
23
+ from roborock.exceptions import RoborockException
24
+ from roborock.map.b01_q10_map_parser import (
25
+ B01Q10MapParserConfig,
26
+ Q10MapPacket,
27
+ Q10Point,
28
+ Q10Room,
29
+ Q10TracePacket,
30
+ )
31
+ from roborock.map.b01_q10_overlays import parse_virtual_wall_blob, parse_zone_blob
32
+ from roborock.map.b01_q10_render import Q10MapOverlays, render_q10_map
33
+
34
+ from .common import UpdatableTrait
35
+
36
+ _LOGGER = logging.getLogger(__name__)
37
+
38
+
39
+ @dataclass
40
+ class MapDps(RoborockBase):
41
+ """Low-level map values delivered in the Q10 DPS stream."""
42
+
43
+ restricted_zone_up: str | None = field(default=None, metadata={"dps": B01_Q10_DP.RESTRICTED_ZONE_UP})
44
+ virtual_wall_up: str | None = field(default=None, metadata={"dps": B01_Q10_DP.VIRTUAL_WALL_UP})
45
+
46
+
47
+ class MapDpsTrait(MapDps, UpdatableTrait):
48
+ """Private read model for map-related DPS values and decoded overlays."""
49
+
50
+ _CONVERTER = DpsDataConverter.from_dataclass(MapDps)
51
+
52
+ def __init__(self) -> None:
53
+ MapDps.__init__(self)
54
+ UpdatableTrait.__init__(self, command=None, logger=_LOGGER)
55
+ self._overlays = Q10MapOverlays()
56
+
57
+ @property
58
+ def overlays(self) -> Q10MapOverlays:
59
+ """Overlays decoded once from the latest relevant DPS update."""
60
+ return self._overlays
61
+
62
+ def update_from_dps(self, decoded_dps: dict[B01_Q10_DP, Any]) -> None:
63
+ """Decode overlay blobs when they arrive, then notify dependents."""
64
+ if not self._CONVERTER.update_from_dps(self, decoded_dps):
65
+ return
66
+ self._overlays = Q10MapOverlays(
67
+ zones=tuple(parse_zone_blob(self.restricted_zone_up)),
68
+ virtual_walls=tuple(parse_virtual_wall_blob(self.virtual_wall_up)),
69
+ )
70
+ self._notify_update()
71
+
72
+
73
+ class MapContentTrait(TraitUpdateListener):
74
+ """High-level composed Q10 map view.
75
+
76
+ The latest map and trace packets are combined with the injected
77
+ :class:`MapDpsTrait` whenever any of those three sources changes.
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ map_dps: MapDpsTrait,
83
+ *,
84
+ map_parser_config: B01Q10MapParserConfig | None = None,
85
+ ) -> None:
86
+ TraitUpdateListener.__init__(self, logger=_LOGGER)
87
+ self._config = map_parser_config or B01Q10MapParserConfig()
88
+ self._map_dps = map_dps
89
+ self._map_packet: Q10MapPacket | None = None
90
+ self._trace_packet: Q10TracePacket | None = None
91
+ self._image_content: bytes | None = None
92
+ self._map_dps.add_update_listener(self._map_dps_updated)
93
+
94
+ @property
95
+ def image_content(self) -> bytes | None:
96
+ """The composed map PNG, if the latest map rendered successfully."""
97
+ return self._image_content
98
+
99
+ @property
100
+ def rooms(self) -> list[Q10Room]:
101
+ """Rooms reported by the device."""
102
+ return self._map_packet.rooms if self._map_packet else []
103
+
104
+ @property
105
+ def path(self) -> list[Q10Point]:
106
+ """Full path for live status and callers drawing their own map overlay."""
107
+ return self._trace_packet.points if self._trace_packet else []
108
+
109
+ @property
110
+ def robot_position(self) -> Q10Point | None:
111
+ """Current position for live status and caller-rendered map overlays."""
112
+ return self._trace_packet.robot_position if self._trace_packet else None
113
+
114
+ @property
115
+ def robot_heading(self) -> int | None:
116
+ """Current heading for orienting a robot marker on a caller-rendered map."""
117
+ return self._trace_packet.heading if self._trace_packet else None
118
+
119
+ def update_from_map_packet(self, packet: Q10MapPacket) -> None:
120
+ """Store a map-protocol update and render the latest sources."""
121
+ self._map_packet = packet
122
+ self._render()
123
+ self._notify_update()
124
+
125
+ def update_from_trace_packet(self, packet: Q10TracePacket) -> None:
126
+ """Store a trace-protocol update and render the latest sources."""
127
+ self._trace_packet = packet
128
+ self._render()
129
+ self._notify_update()
130
+
131
+ def _map_dps_updated(self) -> None:
132
+ """Render after the low-level DPS source changes."""
133
+ if self._map_packet is None:
134
+ return
135
+ self._render()
136
+ self._notify_update()
137
+
138
+ def _render(self) -> None:
139
+ """Render the required map with the latest optional trace and overlays."""
140
+ if self._map_packet is None:
141
+ return
142
+ try:
143
+ self._image_content = render_q10_map(
144
+ self._map_packet,
145
+ self._trace_packet,
146
+ self._map_dps.overlays,
147
+ config=self._config,
148
+ )
149
+ except RoborockException as ex:
150
+ _LOGGER.debug("Failed to render Q10 map packet: %s", ex)
151
+ self._image_content = None
@@ -1,110 +0,0 @@
1
- """Map content trait for B01 Q10 devices.
2
-
3
- Unlike the v1 / Q7 maps, the Q10 has no synchronous "get map" command, so this
4
- trait is purely push-driven and mirrors the Q10 ``StatusTrait`` contract:
5
-
6
- - The device pushes its current map/path as protocol-301 ``MAP_RESPONSE``
7
- messages (a ``dpRequestDps`` nudges it to do so). The protocol layer decodes
8
- those into :class:`Q10MapPacket` / :class:`Q10TracePacket` objects and the
9
- ``Q10PropertiesApi`` subscribe loop routes them to
10
- :meth:`MapContentTrait.update_from_map_packet` /
11
- :meth:`MapContentTrait.update_from_trace_packet`.
12
- - Those methods render/cache the content and notify update listeners (register
13
- via :meth:`add_update_listener`).
14
- - ``image_content``, ``map_data``, ``rooms``, ``path`` and ``robot_position``
15
- are readable and reflect the most recently pushed map.
16
-
17
- Unlike the Q7, the Q10 map payload is unencrypted, so no map key is required.
18
- """
19
-
20
- import logging
21
- from dataclasses import dataclass, field
22
-
23
- from vacuum_map_parser_base.map_data import MapData
24
-
25
- from roborock.data import RoborockBase
26
- from roborock.devices.traits.common import TraitUpdateListener
27
- from roborock.map.b01_q10_map_parser import (
28
- B01Q10MapParser,
29
- B01Q10MapParserConfig,
30
- Q10MapPacket,
31
- Q10Point,
32
- Q10Room,
33
- Q10TracePacket,
34
- )
35
-
36
- _LOGGER = logging.getLogger(__name__)
37
-
38
- _TRUNCATE_LENGTH = 20
39
-
40
-
41
- @dataclass
42
- class MapContent(RoborockBase):
43
- """Dataclass representing Q10 map content."""
44
-
45
- image_content: bytes | None = None
46
- """The rendered image of the map in PNG format."""
47
-
48
- map_data: MapData | None = None
49
- """Parsed map data (image metadata + room names)."""
50
-
51
- rooms: list[Q10Room] = field(default_factory=list)
52
- """Rooms (segments) reported by the device, with ids and names."""
53
-
54
- path: list[Q10Point] = field(default_factory=list)
55
- """Full path of the current cleaning session (oldest point first).
56
-
57
- The robot accumulates this server-side and serves the whole trajectory so
58
- far in one packet, so it is complete even if we connect mid-session. Only
59
- populated while a cleaning session is active."""
60
-
61
- robot_position: Q10Point | None = None
62
- """Current robot position (the most recent path point), if known."""
63
-
64
- def __repr__(self) -> str:
65
- img = self.image_content
66
- if img and len(img) > _TRUNCATE_LENGTH:
67
- img = img[: _TRUNCATE_LENGTH - 3] + b"..."
68
- return f"MapContent(image_content={img!r}, rooms={self.rooms!r})"
69
-
70
-
71
- class MapContentTrait(MapContent, TraitUpdateListener):
72
- """Trait holding the most recently pushed parsed map content for Q10 devices.
73
-
74
- The Q10 has no synchronous get-map request; the device pushes map and trace
75
- packets, which the protocol layer decodes and the ``Q10PropertiesApi``
76
- subscribe loop feeds into :meth:`update_from_map_packet` /
77
- :meth:`update_from_trace_packet`. Consumers read the cached fields and/or
78
- register a callback with :meth:`add_update_listener` to be notified when new
79
- map content arrives.
80
- """
81
-
82
- def __init__(
83
- self,
84
- *,
85
- map_parser_config: B01Q10MapParserConfig | None = None,
86
- ) -> None:
87
- super().__init__()
88
- TraitUpdateListener.__init__(self, logger=_LOGGER)
89
- self._map_parser = B01Q10MapParser(map_parser_config)
90
-
91
- def update_from_map_packet(self, packet: Q10MapPacket) -> None:
92
- """Render a pushed full-map packet into the cached image/rooms.
93
-
94
- Rendering failures are logged and skipped (listeners are not notified) so
95
- a single bad push cannot tear down the subscribe loop.
96
- """
97
- parsed = self._map_parser.parse_packet(packet)
98
- if parsed.image_content is None:
99
- _LOGGER.debug("Failed to render Q10 map image")
100
- return
101
- self.image_content = parsed.image_content
102
- self.map_data = parsed.map_data
103
- self.rooms = packet.rooms
104
- self._notify_update()
105
-
106
- def update_from_trace_packet(self, packet: Q10TracePacket) -> None:
107
- """Cache the path/robot position from a pushed trace packet."""
108
- self.path = packet.points
109
- self.robot_position = packet.robot_position
110
- self._notify_update()