66* trace packets are decoded from trace-protocol responses;
77* restricted zones and virtual walls arrive as ordinary DPS values.
88
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.
9+ ``MapDpsTrait`` owns the low-level DPS read model. ``MapContentTrait`` uses a
10+ stored ID from ``MapsTrait`` only when it requests content. It combines map,
11+ trace, and overlay state through the pure functions in
12+ :mod:`roborock.map.b01_q10_render`. Map-list updates do not refresh content.
1413"""
1514
1615import logging
2120from roborock .callbacks import CallbackList
2221from roborock .data import RoborockBase
2322from roborock .data .b01_q10 .b01_q10_code_mappings import B01_Q10_DP
24- from roborock .data .b01_q10 .b01_q10_containers import dpMultiMap
2523from roborock .devices .traits .common import DpsDataConverter , TraitUpdateListener
2624from roborock .exceptions import RoborockException
2725from roborock .map .b01_q10_map_parser import (
3634
3735from .command import CommandTrait
3836from .common import UpdatableTrait
37+ from .maps import MapsTrait
3938
4039_LOGGER = logging .getLogger (__name__ )
4140
@@ -74,33 +73,27 @@ def update_from_dps(self, decoded_dps: dict[B01_Q10_DP, Any]) -> None:
7473 self ._notify_update ()
7574
7675
77- @dataclass
78- class MapListDps (RoborockBase ):
79- """Typed ``dpMultiMap`` state delivered through the Q10 DPS stream."""
80-
81- multi_map : dpMultiMap | None = field (default = None , metadata = {"dps" : B01_Q10_DP .MULTI_MAP })
82-
83-
84- class MapContentTrait (MapListDps , TraitUpdateListener ):
76+ class MapContentTrait (TraitUpdateListener ):
8577 """High-level composed Q10 map view.
8678
8779 The latest map and trace packets are combined with the injected
88- :class:`MapDpsTrait` whenever any of those three sources changes.
80+ :class:`MapDpsTrait` whenever any of those three sources changes. The
81+ :class:`MapsTrait` supplies a stored ID only when this trait requests
82+ content.
8983 """
9084
91- _CONVERTER = DpsDataConverter .from_dataclass (MapListDps )
92-
9385 def __init__ (
9486 self ,
9587 map_dps : MapDpsTrait ,
96- command : CommandTrait | None = None ,
88+ maps : MapsTrait ,
89+ command : CommandTrait ,
9790 * ,
9891 map_parser_config : B01Q10MapParserConfig | None = None ,
9992 ) -> None :
100- MapListDps .__init__ (self )
10193 TraitUpdateListener .__init__ (self , logger = _LOGGER )
10294 self ._config = map_parser_config or B01Q10MapParserConfig ()
10395 self ._map_dps = map_dps
96+ self ._maps = maps
10497 self ._command = command
10598 self ._map_packet : Q10MapPacket | None = None
10699 self ._trace_packet : Q10TracePacket | None = None
@@ -110,37 +103,21 @@ def __init__(
110103 self ._map_dps .add_update_listener (self ._map_dps_updated )
111104
112105 async def refresh (self ) -> None :
113- """Request the current saved map independently of general status."""
114- if self ._command is None :
115- raise ValueError ("Trait is read-only; no command channel was provided" )
106+ """Request content for the first map in the latest saved-map list."""
107+ if (map_id := self ._maps .current_map_id ) is None :
108+ raise RoborockException ("Cannot request Q10 map content before the map list is available" )
109+ # Map lists and map content can change at different times. Reuse the
110+ # stored ID so a content refresh does not also refresh the list.
116111 await self ._command .send (
117112 B01_Q10_DP .COMMON ,
118- {str (B01_Q10_DP .MULTI_MAP .code ): {"op" : "list" }},
113+ {
114+ str (B01_Q10_DP .MULTI_MAP .code ): {
115+ "op" : "get" ,
116+ "id" : map_id ,
117+ }
118+ },
119119 )
120120
121- async def update_from_dps (self , decoded_dps : dict [B01_Q10_DP , Any ]) -> None :
122- """Request map content when a typed ``dpMultiMap`` list response arrives."""
123- if not self ._CONVERTER .update_from_dps (self , decoded_dps ):
124- return
125- if self ._command is None or self .multi_map is None or self .multi_map .op != "list" or self .multi_map .result != 1 :
126- return
127- if (map_id := self .multi_map .current_map_id ) is None :
128- _LOGGER .debug ("Q10 map list response did not contain a map ID" )
129- return
130- try :
131- await self ._command .send (
132- B01_Q10_DP .COMMON ,
133- {
134- str (B01_Q10_DP .MULTI_MAP .code ): {
135- "op" : "get" ,
136- "id" : map_id ,
137- }
138- },
139- )
140- except RoborockException as ex :
141- # A failed follow-up must not kill the persistent subscribe loop.
142- _LOGGER .debug ("Failed to request Q10 map content: %s" , ex )
143-
144121 @property
145122 def image_content (self ) -> bytes | None :
146123 """The composed map PNG, if the latest map rendered successfully."""
0 commit comments