22
33import asyncio
44import logging
5- from typing import Any
65
76from roborock .data .b01_q10 .b01_q10_code_mappings import B01_Q10_DP
87from roborock .devices .rpc .b01_q10_channel import B01Q10Channel
98from roborock .devices .traits import Trait
10- from roborock .exceptions import RoborockException
119from roborock .map .b01_q10_map_parser import Q10MapPacket , Q10TracePacket
1210from roborock .protocols .b01_q10_protocol import Q10DpsUpdate , Q10Message
1311
4038]
4139
4240_LOGGER = logging .getLogger (__name__ )
43- _MAP_LIST_REQUEST_TIMEOUT = 30.0
44-
45-
46- def _map_id_from_list_response (response : Any ) -> int | str | None :
47- """Return the first usable map ID from a ``dpMultiMap`` list response."""
48- if not isinstance (response , dict ) or response .get ("op" ) != "list" :
49- return None
50- data = response .get ("data" )
51- if not isinstance (data , list ):
52- return None
53- for map_info in data :
54- if not isinstance (map_info , dict ):
55- continue
56- map_id = map_info .get ("id" )
57- if isinstance (map_id , int ) and not isinstance (map_id , bool ):
58- return map_id
59- if isinstance (map_id , str ) and map_id :
60- return map_id
61- return None
6241
6342
6443class Q10PropertiesApi (Trait ):
@@ -121,7 +100,7 @@ def __init__(self, channel: B01Q10Channel) -> None:
121100 self .network_info = NetworkInfoTrait ()
122101 self .consumable = ConsumableTrait ()
123102 self ._map_dps = MapDpsTrait ()
124- self .map = MapContentTrait (self ._map_dps )
103+ self .map = MapContentTrait (self ._map_dps , self . command )
125104 self .clean_history = CleanHistoryTrait (self .command )
126105 # Read-model traits updated from the device's DPS push stream.
127106 self ._updatable_traits = [
@@ -136,9 +115,6 @@ def __init__(self, channel: B01Q10Channel) -> None:
136115 self ._map_dps ,
137116 ]
138117 self ._subscribe_task : asyncio .Task [None ] | None = None
139- self ._map_request_lock = asyncio .Lock ()
140- self ._map_list_request_token : object | None = None
141- self ._map_list_requested_at : float | None = None
142118
143119 async def start (self ) -> None :
144120 """Start any necessary subscriptions for the trait."""
@@ -156,42 +132,9 @@ async def close(self) -> None:
156132
157133 async def refresh (self ) -> None :
158134 """Refresh all traits."""
159- # Status and map retrieval use separate Q10 requests. A bare REQUEST_DPS
160- # reliably refreshes status but does not reliably make every firmware
161- # publish its map.
135+ # Sending REQUEST_DPS causes the device to publish its ordinary status
136+ # values. Map refreshes have their own cadence through ``map.refresh()``.
162137 await self .command .send (B01_Q10_DP .REQUEST_DPS , params = {})
163- await self .request_map ()
164-
165- async def request_map (self ) -> None :
166- """Request the current saved map through the Q10 multi-map protocol.
167-
168- The list response arrives asynchronously on the subscribe stream.
169- ``_handle_message`` extracts its first map ID and follows up with the
170- matching ``get`` command; the resulting protocol-301 map packet is
171- routed to :attr:`map`.
172- """
173- async with self ._map_request_lock :
174- now = asyncio .get_running_loop ().time ()
175- if (
176- self ._map_list_request_token is not None
177- and self ._map_list_requested_at is not None
178- and now - self ._map_list_requested_at < _MAP_LIST_REQUEST_TIMEOUT
179- ):
180- return
181- token = object ()
182- self ._map_list_request_token = token
183- self ._map_list_requested_at = now
184- sent = False
185- try :
186- await self .command .send (
187- B01_Q10_DP .COMMON ,
188- {str (B01_Q10_DP .MULTI_MAP .code ): {"op" : "list" }},
189- )
190- sent = True
191- finally :
192- if not sent and self ._map_list_request_token is token :
193- self ._map_list_request_token = None
194- self ._map_list_requested_at = None
195138
196139 async def _subscribe_loop (self ) -> None :
197140 """Persistent loop dispatching decoded messages to the read-model traits."""
@@ -202,9 +145,8 @@ async def _handle_message(self, message: Q10Message) -> None:
202145 """Route a single decoded message to the trait responsible for it.
203146
204147 Map and trace packets arrive as protocol-301 ``MAP_RESPONSE`` pushes.
205- A ``dpMultiMap`` list response completes the asynchronous request flow
206- started by :meth:`request_map`; other DPS updates feed the read-model
207- traits.
148+ Map-list DPS responses are handed to the map trait; other DPS updates
149+ feed the read-model traits.
208150 """
209151 if isinstance (message , Q10MapPacket ):
210152 self .map .update_from_map_packet (message )
@@ -216,39 +158,7 @@ async def _handle_message(self, message: Q10Message) -> None:
216158 # only updates the fields that it is responsible for.
217159 for trait in self ._updatable_traits :
218160 trait .update_from_dps (message .dps )
219- await self ._request_map_from_list_response (message .dps )
220-
221- async def _request_map_from_list_response (self , decoded_dps : dict [B01_Q10_DP , Any ]) -> None :
222- """Request map content after receiving our pending map-list response."""
223- response = decoded_dps .get (B01_Q10_DP .MULTI_MAP )
224- if self ._map_list_request_token is None or not isinstance (response , dict ) or response .get ("op" ) != "list" :
225- return
226-
227- token = self ._map_list_request_token
228- if (map_id := _map_id_from_list_response (response )) is None :
229- if self ._map_list_request_token is token :
230- self ._map_list_request_token = None
231- self ._map_list_requested_at = None
232- _LOGGER .debug ("Q10 map list response did not contain a usable map ID" )
233- return
234-
235- try :
236- await self .command .send (
237- B01_Q10_DP .COMMON ,
238- {
239- str (B01_Q10_DP .MULTI_MAP .code ): {
240- "op" : "get" ,
241- "id" : map_id ,
242- }
243- },
244- )
245- except RoborockException as ex :
246- # A failed follow-up must not kill the persistent subscribe loop.
247- _LOGGER .debug ("Failed to request Q10 map content: %s" , ex )
248- finally :
249- if self ._map_list_request_token is token :
250- self ._map_list_request_token = None
251- self ._map_list_requested_at = None
161+ await self .map .update_from_dps (message .dps )
252162
253163
254164def create (channel : B01Q10Channel ) -> Q10PropertiesApi :
0 commit comments