5555)
5656from .protocol import Utils
5757from .roborock_future import RoborockFuture
58- from .roborock_message import RoborockDataProtocol , RoborockMessage , RoborockMessageProtocol
58+ from .roborock_message import (
59+ ROBOROCK_DATA_CONSUMABLE_PROTOCOL ,
60+ ROBOROCK_DATA_STATUS_PROTOCOL ,
61+ RoborockDataProtocol ,
62+ RoborockMessage ,
63+ RoborockMessageProtocol ,
64+ )
5965from .roborock_typing import DeviceProp , DockSummary , RoborockCommand
6066from .util import RepeatableTask , get_running_loop_or_create_one , unpack_list
6167
@@ -124,18 +130,23 @@ def stop(self):
124130 self .task .cancel ()
125131
126132 async def update_value (self , params ):
133+ if self .attribute .set_command is None :
134+ raise RoborockException (f"{ self .attribute .attribute } have no set command" )
127135 response = await self .api ._send_command (self .attribute .set_command , params )
128136 await self ._async_value ()
129137 return response
130138
131139 async def close_value (self ):
132140 if self .attribute .close_command is None :
133- raise RoborockException (f"{ self .attribute .attribute } is not closeable " )
141+ raise RoborockException (f"{ self .attribute .attribute } have no close command " )
134142 response = await self .api ._send_command (self .attribute .close_command )
135143 await self ._async_value ()
136144 return response
137145
138146
147+ device_cache : dict [str , dict [CacheableAttribute , AttributeCache ]] = {}
148+
149+
139150class RoborockClient :
140151 def __init__ (self , endpoint : str , device_info : DeviceData ) -> None :
141152 self .event_loop = get_running_loop_or_create_one ()
@@ -147,9 +158,15 @@ def __init__(self, endpoint: str, device_info: DeviceData) -> None:
147158 self ._last_disconnection = self .time_func ()
148159 self .keep_alive = KEEPALIVE
149160 self ._diagnostic_data : dict [str , dict [str , Any ]] = {}
150- self .cache : dict [CacheableAttribute , AttributeCache ] = {
151- cacheable_attribute : AttributeCache (attr , self ) for cacheable_attribute , attr in create_cache_map ().items ()
152- }
161+ cache = device_cache .get (device_info .device .duid )
162+ if not cache :
163+ cache = {
164+ cacheable_attribute : AttributeCache (attr , self )
165+ for cacheable_attribute , attr in create_cache_map ().items ()
166+ }
167+ device_cache [device_info .device .duid ] = cache
168+ self .cache = cache
169+ self ._listeners : list [Callable [[CacheableAttribute , RoborockBase ], None ]] = []
153170
154171 def __del__ (self ) -> None :
155172 self .release ()
@@ -212,6 +229,30 @@ def on_message_received(self, messages: list[RoborockMessage]) -> None:
212229 if isinstance (result , list ) and len (result ) == 1 :
213230 result = result [0 ]
214231 queue .resolve ((result , None ))
232+ else :
233+ try :
234+ data_protocol = RoborockDataProtocol (int (data_point_number ))
235+ _LOGGER .debug (f"Got device update for { data_protocol .name } : { data_point } " )
236+ if data_protocol in ROBOROCK_DATA_STATUS_PROTOCOL :
237+ _cls : Type [Status ] = ModelStatus .get (
238+ self .device_info .model , S7MaxVStatus
239+ ) # Default to S7 MAXV if we don't have the data
240+ value = self .cache [CacheableAttribute .status ].value
241+ value [data_protocol .name ] = data_point
242+ status = _cls .from_dict (value )
243+ for listener in self ._listeners :
244+ listener (CacheableAttribute .status , status )
245+ elif data_protocol in ROBOROCK_DATA_CONSUMABLE_PROTOCOL :
246+ value = self .cache [CacheableAttribute .consumable ].value
247+ value [data_protocol .name ] = data_point
248+ consumable = Consumable .from_dict (value )
249+ for listener in self ._listeners :
250+ listener (CacheableAttribute .consumable , consumable )
251+ return
252+ except ValueError :
253+ pass
254+ dps = {data_point_number : data_point }
255+ _LOGGER .debug (f"Got unknown data point { dps } " )
215256 elif data .payload and protocol == RoborockMessageProtocol .MAP_RESPONSE :
216257 payload = data .payload [0 :24 ]
217258 [endpoint , _ , request_id , _ ] = struct .unpack ("<8s8sH6s" , payload )
@@ -223,8 +264,6 @@ def on_message_received(self, messages: list[RoborockMessage]) -> None:
223264 if isinstance (decompressed , list ):
224265 decompressed = decompressed [0 ]
225266 queue .resolve ((decompressed , None ))
226- elif data .payload and protocol in RoborockDataProtocol :
227- _LOGGER .debug (f"Got device update for { RoborockDataProtocol (protocol ).name } : { data .payload !r} " )
228267 else :
229268 queue = self ._waiting_queue .get (data .seq )
230269 if queue :
@@ -329,13 +368,15 @@ async def get_status(self) -> Status | None:
329368 _cls : Type [Status ] = ModelStatus .get (
330369 self .device_info .model , S7MaxVStatus
331370 ) # Default to S7 MAXV if we don't have the data
332- return await self .send_command ( RoborockCommand . GET_STATUS , return_type = _cls )
371+ return _cls . from_dict ( await self .cache [ CacheableAttribute . status ]. async_value () )
333372
334373 async def get_dnd_timer (self ) -> DnDTimer | None :
335- return await self .send_command ( RoborockCommand . GET_DND_TIMER , return_type = DnDTimer )
374+ return DnDTimer . from_dict ( await self .cache [ CacheableAttribute . dnd_timer ]. async_value () )
336375
337376 async def get_valley_electricity_timer (self ) -> ValleyElectricityTimer | None :
338- return await self .send_command (RoborockCommand .GET_VALLEY_ELECTRICITY_TIMER , return_type = ValleyElectricityTimer )
377+ return ValleyElectricityTimer .from_dict (
378+ await self .cache [CacheableAttribute .valley_electricity_timer ].async_value ()
379+ )
339380
340381 async def get_clean_summary (self ) -> CleanSummary | None :
341382 clean_summary : dict | list | int = await self .send_command (RoborockCommand .GET_CLEAN_SUMMARY )
@@ -357,16 +398,16 @@ async def get_clean_record(self, record_id: int) -> CleanRecord | None:
357398 return await self .send_command (RoborockCommand .GET_CLEAN_RECORD , [record_id ], return_type = CleanRecord )
358399
359400 async def get_consumable (self ) -> Consumable | None :
360- return await self .send_command ( RoborockCommand . GET_CONSUMABLE , return_type = Consumable )
401+ return Consumable . from_dict ( await self .cache [ CacheableAttribute . consumable ]. async_value () )
361402
362403 async def get_wash_towel_mode (self ) -> WashTowelMode | None :
363- return await self .send_command ( RoborockCommand . GET_WASH_TOWEL_MODE , return_type = WashTowelMode )
404+ return WashTowelMode . from_dict ( await self .cache [ CacheableAttribute . wash_towel_mode ]. async_value () )
364405
365406 async def get_dust_collection_mode (self ) -> DustCollectionMode | None :
366- return await self .send_command ( RoborockCommand . GET_DUST_COLLECTION_MODE , return_type = DustCollectionMode )
407+ return DustCollectionMode . from_dict ( await self .cache [ CacheableAttribute . dust_collection_mode ]. async_value () )
367408
368409 async def get_smart_wash_params (self ) -> SmartWashParams | None :
369- return await self .send_command ( RoborockCommand . GET_SMART_WASH_PARAMS , return_type = SmartWashParams )
410+ return SmartWashParams . from_dict ( await self .cache [ CacheableAttribute . smart_wash_params ]. async_value () )
370411
371412 async def get_dock_summary (self , dock_type : RoborockDockTypeCode ) -> DockSummary | None :
372413 """Gets the status summary from the dock with the methods available for a given dock.
@@ -432,15 +473,18 @@ async def get_room_mapping(self) -> list[RoomMapping] | None:
432473
433474 async def get_child_lock_status (self ) -> ChildLockStatus | None :
434475 """Gets current child lock status."""
435- return await self .send_command ( RoborockCommand . GET_CHILD_LOCK_STATUS , return_type = ChildLockStatus )
476+ return ChildLockStatus . from_dict ( await self .cache [ CacheableAttribute . child_lock_status ]. async_value () )
436477
437478 async def get_flow_led_status (self ) -> FlowLedStatus | None :
438479 """Gets current flow led status."""
439- return await self .send_command ( RoborockCommand . GET_FLOW_LED_STATUS , return_type = FlowLedStatus )
480+ return FlowLedStatus . from_dict ( await self .cache [ CacheableAttribute . flow_led_status ]. async_value () )
440481
441482 async def get_sound_volume (self ) -> int | None :
442483 """Gets current volume level."""
443- return await self .send_command (RoborockCommand .GET_SOUND_VOLUME )
484+ return await self .cache [CacheableAttribute .sound_volume ].async_value ()
485+
486+ def add_listener (self , listener : Callable ):
487+ self ._listeners .append (listener )
444488
445489
446490class RoborockApiClient :
0 commit comments