99from typing import Any
1010
1111from roborock import (
12- HomeDataRoom ,
1312 RoborockException ,
1413 RoborockInvalidCredentials ,
1514 RoborockInvalidUserAgreement ,
1615 RoborockNoUserAgreement ,
1716)
18- from roborock .data import DeviceData , HomeDataDevice , HomeDataProduct , UserData
19- from roborock .version_1_apis .roborock_mqtt_client_v1 import RoborockMqttClientV1
20- from roborock .version_a01_apis import RoborockMqttClientA01
21- from roborock .web_api import RoborockApiClient
17+ from roborock .data import UserData
18+ from roborock .devices .device import RoborockDevice
19+ from roborock .devices .device_manager import UserParams , create_device_manager
2220
2321from homeassistant .const import CONF_USERNAME , EVENT_HOMEASSISTANT_STOP
2422from homeassistant .core import HomeAssistant
3230 RoborockCoordinators ,
3331 RoborockDataUpdateCoordinator ,
3432 RoborockDataUpdateCoordinatorA01 ,
33+ RoborockWashingMachineUpdateCoordinator ,
34+ RoborockWetDryVacUpdateCoordinator ,
3535)
36- from .roborock_storage import async_remove_map_storage
36+ from .roborock_storage import CacheStore , async_remove_map_storage
3737
3838SCAN_INTERVAL = timedelta (seconds = 30 )
3939
@@ -44,14 +44,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
4444 """Set up roborock from a config entry."""
4545
4646 user_data = UserData .from_dict (entry .data [CONF_USER_DATA ])
47- api_client = RoborockApiClient (
48- entry .data [CONF_USERNAME ],
49- entry . data [ CONF_BASE_URL ] ,
50- session = async_get_clientsession ( hass ) ,
47+ user_params = UserParams (
48+ username = entry .data [CONF_USERNAME ],
49+ user_data = user_data ,
50+ base_url = entry . data [ CONF_BASE_URL ] ,
5151 )
52- _LOGGER . debug ( "Getting home data" )
52+ cache = CacheStore ( hass , entry . entry_id )
5353 try :
54- home_data = await api_client .get_home_data_v3 (user_data )
54+ device_manager = await create_device_manager (
55+ user_params ,
56+ cache = cache ,
57+ session = async_get_clientsession (hass ),
58+ )
5559 except RoborockInvalidCredentials as err :
5660 raise ConfigEntryAuthFailed (
5761 "Invalid credentials" ,
@@ -75,29 +79,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
7579 translation_domain = DOMAIN ,
7680 translation_key = "home_data_fail" ,
7781 ) from err
82+ devices = await device_manager .get_devices ()
83+ _LOGGER .debug ("Device manager found %d devices" , len (devices ))
84+ for device in devices :
85+ entry .async_on_unload (device .close )
7886
79- _LOGGER .debug ("Got home data %s" , home_data )
80- all_devices : list [HomeDataDevice ] = home_data .devices + home_data .received_devices
81- device_map : dict [str , HomeDataDevice ] = {
82- device .duid : device for device in all_devices
83- }
84- product_info : dict [str , HomeDataProduct ] = {
85- product .id : product for product in home_data .products
86- }
87- # Get a Coordinator if the device is available or if we have connected to the device before
8887 coordinators = await asyncio .gather (
89- * build_setup_functions (
90- hass ,
91- entry ,
92- device_map ,
93- user_data ,
94- product_info ,
95- home_data .rooms ,
96- api_client ,
97- ),
88+ * build_setup_functions (hass , entry , devices , user_data ),
9889 return_exceptions = True ,
9990 )
100- # Valid coordinators are those where we had networking cached or we could get networking
10191 v1_coords = [
10292 coord
10393 for coord in coordinators
@@ -115,17 +105,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
115105 translation_key = "no_coordinators" ,
116106 )
117107 valid_coordinators = RoborockCoordinators (v1_coords , a01_coords )
118- await asyncio .gather (
119- * (coord .refresh_coordinator_map () for coord in valid_coordinators .v1 )
120- )
121108
122109 async def on_stop (_ : Any ) -> None :
123110 _LOGGER .debug ("Shutting down roborock" )
124111 await asyncio .gather (
125112 * (
126113 coordinator .async_shutdown ()
127114 for coordinator in valid_coordinators .values ()
128- )
115+ ),
116+ cache .flush (),
129117 )
130118
131119 entry .async_on_unload (
@@ -138,6 +126,17 @@ async def on_stop(_: Any) -> None:
138126
139127 await hass .config_entries .async_forward_entry_setups (entry , PLATFORMS )
140128
129+ _remove_stale_devices (hass , entry , devices )
130+
131+ return True
132+
133+
134+ def _remove_stale_devices (
135+ hass : HomeAssistant ,
136+ entry : RoborockConfigEntry ,
137+ devices : list [RoborockDevice ],
138+ ) -> None :
139+ device_map : dict [str , RoborockDevice ] = {device .duid : device for device in devices }
141140 device_registry = dr .async_get (hass )
142141 device_entries = dr .async_entries_for_config_entry (
143142 device_registry , config_entry_id = entry .entry_id
@@ -159,8 +158,6 @@ async def on_stop(_: Any) -> None:
159158 remove_config_entry_id = entry .entry_id ,
160159 )
161160
162- return True
163-
164161
165162async def async_migrate_entry (hass : HomeAssistant , entry : RoborockConfigEntry ) -> bool :
166163 """Migrate old configuration entries to the new format."""
@@ -190,11 +187,8 @@ async def async_migrate_entry(hass: HomeAssistant, entry: RoborockConfigEntry) -
190187def build_setup_functions (
191188 hass : HomeAssistant ,
192189 entry : RoborockConfigEntry ,
193- device_map : dict [ str , HomeDataDevice ],
190+ devices : list [ RoborockDevice ],
194191 user_data : UserData ,
195- product_info : dict [str , HomeDataProduct ],
196- home_data_rooms : list [HomeDataRoom ],
197- api_client : RoborockApiClient ,
198192) -> list [
199193 Coroutine [
200194 Any ,
@@ -203,134 +197,45 @@ def build_setup_functions(
203197 ]
204198]:
205199 """Create a list of setup functions that can later be called asynchronously."""
206- return [
207- setup_device (
208- hass ,
209- entry ,
210- user_data ,
211- device ,
212- product_info [device .product_id ],
213- home_data_rooms ,
214- api_client ,
215- )
216- for device in device_map .values ()
217- ]
218-
200+ coordinators : list [
201+ RoborockDataUpdateCoordinator | RoborockDataUpdateCoordinatorA01
202+ ] = []
203+ for device in devices :
204+ _LOGGER .debug ("Creating device %s: %s" , device .name , device )
205+ if device .v1_properties is not None :
206+ coordinators .append (
207+ RoborockDataUpdateCoordinator (hass , entry , device , device .v1_properties )
208+ )
209+ elif device .dyad is not None :
210+ coordinators .append (
211+ RoborockWetDryVacUpdateCoordinator (hass , entry , device , device .dyad )
212+ )
213+ elif device .zeo is not None :
214+ coordinators .append (
215+ RoborockWashingMachineUpdateCoordinator (hass , entry , device , device .zeo )
216+ )
217+ else :
218+ _LOGGER .warning (
219+ "Not adding device %s because its protocol version %s or category %s is not supported" ,
220+ device .duid ,
221+ device .device_info .pv ,
222+ device .product .category .name ,
223+ )
219224
220- async def setup_device (
221- hass : HomeAssistant ,
222- entry : RoborockConfigEntry ,
223- user_data : UserData ,
224- device : HomeDataDevice ,
225- product_info : HomeDataProduct ,
226- home_data_rooms : list [HomeDataRoom ],
227- api_client : RoborockApiClient ,
228- ) -> RoborockDataUpdateCoordinator | RoborockDataUpdateCoordinatorA01 | None :
229- """Set up a coordinator for a given device."""
230- if device .pv == "1.0" :
231- return await setup_device_v1 (
232- hass , entry , user_data , device , product_info , home_data_rooms , api_client
233- )
234- if device .pv == "A01" :
235- return await setup_device_a01 (hass , entry , user_data , device , product_info )
236- _LOGGER .warning (
237- "Not adding device %s because its protocol version %s or category %s is not supported" ,
238- device .duid ,
239- device .pv ,
240- product_info .category .name ,
241- )
242- return None
225+ return [setup_coordinator (coordinator ) for coordinator in coordinators ]
243226
244227
245- async def setup_device_v1 (
246- hass : HomeAssistant ,
247- entry : RoborockConfigEntry ,
248- user_data : UserData ,
249- device : HomeDataDevice ,
250- product_info : HomeDataProduct ,
251- home_data_rooms : list [HomeDataRoom ],
252- api_client : RoborockApiClient ,
253- ) -> RoborockDataUpdateCoordinator | None :
254- """Set up a device Coordinator."""
255- mqtt_client = await hass .async_add_executor_job (
256- RoborockMqttClientV1 , user_data , DeviceData (device , product_info .model )
257- )
258- try :
259- await mqtt_client .async_connect ()
260- networking = await mqtt_client .get_networking ()
261- if networking is None :
262- # If the api does not return an error but does return None for
263- # get_networking - then we need to go through cache checking.
264- raise RoborockException ("Networking request returned None." ) # noqa: TRY301
265- except RoborockException as err :
266- _LOGGER .warning (
267- "Not setting up %s because we could not get the network information of the device. "
268- "Please confirm it is online and the Roborock servers can communicate with it" ,
269- device .name ,
270- )
271- _LOGGER .debug (err )
272- await mqtt_client .async_release ()
273- raise
274- coordinator = RoborockDataUpdateCoordinator (
275- hass ,
276- entry ,
277- device ,
278- networking ,
279- product_info ,
280- mqtt_client ,
281- home_data_rooms ,
282- api_client ,
283- user_data ,
284- )
228+ async def setup_coordinator (
229+ coordinator : RoborockDataUpdateCoordinator | RoborockDataUpdateCoordinatorA01 ,
230+ ) -> RoborockDataUpdateCoordinator | RoborockDataUpdateCoordinatorA01 | None :
231+ """Set up a single coordinator."""
285232 try :
286233 await coordinator .async_config_entry_first_refresh ()
287- except ConfigEntryNotReady as ex :
234+ except ConfigEntryNotReady :
288235 await coordinator .async_shutdown ()
289- if isinstance (coordinator .api , RoborockMqttClientV1 ):
290- _LOGGER .warning (
291- "Not setting up %s because the we failed to get data for the first time using the online client. "
292- "Please ensure your Home Assistant instance can communicate with this device. "
293- "You may need to open firewall instances on your Home Assistant network and on your Vacuum's network" ,
294- device .name ,
295- )
296- # Most of the time if we fail to connect using the mqtt client, the problem is due to firewall,
297- # but in case if it isn't, the error can be included in debug logs for the user to grab.
298- if coordinator .last_exception :
299- _LOGGER .debug (coordinator .last_exception )
300- raise coordinator .last_exception from ex
301- elif coordinator .last_exception :
302- # If this is reached, we have verified that we can communicate with the Vacuum locally,
303- # so if there is an error here - it is not a communication issue but some other problem
304- extra_error = f"Please create an issue with the following error included: { coordinator .last_exception } "
305- _LOGGER .warning (
306- "Not setting up %s because the coordinator failed to get data for the first time using the "
307- "offline client %s" ,
308- device .name ,
309- extra_error ,
310- )
311- raise coordinator .last_exception from ex
312- return coordinator
313-
314-
315- async def setup_device_a01 (
316- hass : HomeAssistant ,
317- entry : RoborockConfigEntry ,
318- user_data : UserData ,
319- device : HomeDataDevice ,
320- product_info : HomeDataProduct ,
321- ) -> RoborockDataUpdateCoordinatorA01 | None :
322- """Set up a A01 protocol device."""
323- mqtt_client = await hass .async_add_executor_job (
324- RoborockMqttClientA01 ,
325- user_data ,
326- DeviceData (device , product_info .model ),
327- product_info .category ,
328- )
329- coord = RoborockDataUpdateCoordinatorA01 (
330- hass , entry , device , product_info , mqtt_client
331- )
332- await coord .async_config_entry_first_refresh ()
333- return coord
236+ raise
237+ else :
238+ return coordinator
334239
335240
336241async def async_unload_entry (hass : HomeAssistant , entry : RoborockConfigEntry ) -> bool :
@@ -341,3 +246,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
341246async def async_remove_entry (hass : HomeAssistant , entry : RoborockConfigEntry ) -> None :
342247 """Handle removal of an entry."""
343248 await async_remove_map_storage (hass , entry .entry_id )
249+ store = CacheStore (hass , entry .entry_id )
250+ await store .async_remove ()
0 commit comments