66
77import asyncio
88import datetime
9- import json
109import logging
1110from abc import ABC
1211from collections .abc import Callable
1615from roborock .data import HomeDataDevice , HomeDataProduct
1716from roborock .diagnostics import redact_device_data
1817from roborock .exceptions import RoborockException
19- from roborock .roborock_message import (
20- ROBOROCK_DATA_STATUS_PROTOCOL ,
21- RoborockDataProtocol ,
22- RoborockMessage ,
23- RoborockMessageProtocol ,
24- )
2518from roborock .util import RoborockLoggerAdapter
2619
2720from .traits import Trait
@@ -81,6 +74,7 @@ def __init__(
8174 self ._channel = channel
8275 self ._connect_task : asyncio .Task [None ] | None = None
8376 self ._unsub : Callable [[], None ] | None = None
77+ self ._v1_unsub : Callable [[], None ] | None = None
8478 self ._ready_callbacks = CallbackList ["RoborockDevice" ]()
8579 self ._has_connected = False
8680
@@ -202,15 +196,23 @@ async def connect(self) -> None:
202196 """Connect to the device using the appropriate protocol channel."""
203197 if self ._unsub :
204198 raise ValueError ("Already connected to the device" )
205- unsub = await self . _channel . subscribe ( self . _on_message )
199+
206200 if self .v1_properties is not None :
207201 try :
202+ # V1 layer subscribes to the channel and handles protocol updates.
203+ # Note: V1Channel only allows one subscription, so the V1 layer
204+ # is the sole subscriber for V1 devices.
205+ self ._v1_unsub = await self .v1_properties .subscribe_async (self ._channel )
208206 await self .v1_properties .discover_features ()
209207 except RoborockException :
210- unsub ()
208+ if self ._v1_unsub :
209+ self ._v1_unsub ()
211210 raise
211+ else :
212+ # Non-V1 devices subscribe directly (no protocol update handling needed)
213+ self ._unsub = await self ._channel .subscribe (lambda msg : None )
214+
212215 self ._logger .info ("Connected to device" )
213- self ._unsub = unsub
214216
215217 async def close (self ) -> None :
216218 """Close all connections to the device."""
@@ -220,70 +222,13 @@ async def close(self) -> None:
220222 await self ._connect_task
221223 except asyncio .CancelledError :
222224 pass
225+ if self ._v1_unsub :
226+ self ._v1_unsub ()
227+ self ._v1_unsub = None
223228 if self ._unsub :
224229 self ._unsub ()
225230 self ._unsub = None
226231
227- def _on_message (self , message : RoborockMessage ) -> None :
228- """Handle incoming messages from the device.
229-
230- Note: Protocol updates (data points) are only sent via cloud/MQTT, not local connection.
231- """
232- self ._logger .debug ("Received message from device: %s" , message )
233- if self .v1_properties is None :
234- # Ensure we are only doing below logic for set-up V1 devices.
235- return
236-
237- # Only process messages that can contain protocol updates
238- # RPC_RESPONSE (102), and GENERAL_RESPONSE (5)
239- if message .protocol not in {
240- RoborockMessageProtocol .RPC_RESPONSE ,
241- RoborockMessageProtocol .GENERAL_RESPONSE ,
242- }:
243- return
244-
245- if not message .payload :
246- return
247-
248- try :
249- payload = json .loads (message .payload .decode ("utf-8" ))
250- dps = payload .get ("dps" , {})
251-
252- if not dps :
253- return
254-
255- # Process each data point in the message
256- for data_point_number , data_point in dps .items ():
257- # Skip RPC responses (102) as they're handled by the RPC channel
258- if data_point_number == "102" :
259- continue
260-
261- try :
262- data_protocol = RoborockDataProtocol (int (data_point_number ))
263- self ._logger .debug ("Got device update for %s: %s" , data_protocol .name , data_point )
264- self ._handle_protocol_update (data_protocol , data_point )
265- except ValueError :
266- # Unknown protocol number
267- self ._logger .debug (
268- f"Got unknown data protocol { data_point_number } , data: { data_point } . "
269- f"This may allow for faster updates in the future."
270- )
271- except (json .JSONDecodeError , UnicodeDecodeError , KeyError ) as ex :
272- self ._logger .debug ("Failed to parse protocol message: %s" , ex )
273-
274- def _handle_protocol_update (self , protocol : RoborockDataProtocol , data_point : Any ) -> None :
275- """Handle a protocol update for a specific data protocol.
276-
277- Args:
278- protocol: The data protocol number.
279- data_point: The data value for this protocol.
280- """
281- # Handle status protocol updates
282- if protocol in ROBOROCK_DATA_STATUS_PROTOCOL and self .v1_properties and self .v1_properties .status :
283- if self .v1_properties .status .handle_protocol_update (protocol , data_point ):
284- self ._logger .debug ("Updated status.%s to %s" , protocol .name .lower (), data_point )
285- self .v1_properties .status .notify_update ()
286-
287232 def diagnostic_data (self ) -> dict [str , Any ]:
288233 """Return diagnostics information about the device."""
289234 extra : dict [str , Any ] = {}
0 commit comments