1 """The Roborock component."""
3 from __future__
import annotations
6 from collections.abc
import Coroutine
7 from dataclasses
import dataclass
8 from datetime
import timedelta
10 from typing
import Any
12 from roborock
import HomeDataRoom, RoborockException, RoborockInvalidCredentials
13 from roborock.containers
import DeviceData, HomeDataDevice, HomeDataProduct, UserData
14 from roborock.version_1_apis.roborock_mqtt_client_v1
import RoborockMqttClientV1
15 from roborock.version_a01_apis
import RoborockMqttClientA01
16 from roborock.web_api
import RoborockApiClient
23 from .const
import CONF_BASE_URL, CONF_USER_DATA, DOMAIN, PLATFORMS
24 from .coordinator
import RoborockDataUpdateCoordinator, RoborockDataUpdateCoordinatorA01
28 _LOGGER = logging.getLogger(__name__)
30 type RoborockConfigEntry = ConfigEntry[RoborockCoordinators]
35 """Roborock coordinators type."""
37 v1: list[RoborockDataUpdateCoordinator]
38 a01: list[RoborockDataUpdateCoordinatorA01]
42 ) -> list[RoborockDataUpdateCoordinator | RoborockDataUpdateCoordinatorA01]:
43 """Return all coordinators."""
44 return self.v1 + self.a01
48 """Set up roborock from a config entry."""
50 entry.async_on_unload(entry.add_update_listener(update_listener))
52 user_data = UserData.from_dict(entry.data[CONF_USER_DATA])
53 api_client = RoborockApiClient(entry.data[CONF_USERNAME], entry.data[CONF_BASE_URL])
54 _LOGGER.debug(
"Getting home data")
56 home_data = await api_client.get_home_data_v2(user_data)
57 except RoborockInvalidCredentials
as err:
59 "Invalid credentials",
60 translation_domain=DOMAIN,
61 translation_key=
"invalid_credentials",
63 except RoborockException
as err:
65 "Failed to get Roborock home data",
66 translation_domain=DOMAIN,
67 translation_key=
"home_data_fail",
69 _LOGGER.debug(
"Got home data %s", home_data)
70 all_devices: list[HomeDataDevice] = home_data.devices + home_data.received_devices
71 device_map: dict[str, HomeDataDevice] = {
72 device.duid: device
for device
in all_devices
74 product_info: dict[str, HomeDataProduct] = {
75 product.id: product
for product
in home_data.products
78 coordinators = await asyncio.gather(
80 hass, device_map, user_data, product_info, home_data.rooms
82 return_exceptions=
True,
87 for coord
in coordinators
88 if isinstance(coord, RoborockDataUpdateCoordinator)
92 for coord
in coordinators
93 if isinstance(coord, RoborockDataUpdateCoordinatorA01)
95 if len(v1_coords) + len(a01_coords) == 0:
97 "No devices were able to successfully setup",
98 translation_domain=DOMAIN,
99 translation_key=
"no_coordinators",
104 release_tasks = set()
105 for coordinator
in valid_coordinators.values():
106 release_tasks.add(coordinator.release())
107 await asyncio.gather(*release_tasks)
109 entry.async_on_unload(on_unload)
110 entry.runtime_data = valid_coordinators
112 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
119 device_map: dict[str, HomeDataDevice],
121 product_info: dict[str, HomeDataProduct],
122 home_data_rooms: list[HomeDataRoom],
127 RoborockDataUpdateCoordinator | RoborockDataUpdateCoordinatorA01 |
None,
130 """Create a list of setup functions that can later be called asynchronously."""
133 hass, user_data, device, product_info[device.product_id], home_data_rooms
135 for device
in device_map.values()
142 device: HomeDataDevice,
143 product_info: HomeDataProduct,
144 home_data_rooms: list[HomeDataRoom],
145 ) -> RoborockDataUpdateCoordinator | RoborockDataUpdateCoordinatorA01 |
None:
146 """Set up a coordinator for a given device."""
147 if device.pv ==
"1.0":
149 hass, user_data, device, product_info, home_data_rooms
151 if device.pv ==
"A01":
154 "Not adding device %s because its protocol version %s or category %s is not supported",
157 product_info.category.name,
165 device: HomeDataDevice,
166 product_info: HomeDataProduct,
167 home_data_rooms: list[HomeDataRoom],
168 ) -> RoborockDataUpdateCoordinator |
None:
169 """Set up a device Coordinator."""
170 mqtt_client = await hass.async_add_executor_job(
171 RoborockMqttClientV1, user_data, DeviceData(device, product_info.model)
174 networking = await mqtt_client.get_networking()
175 if networking
is None:
178 raise RoborockException(
"Networking request returned None.")
179 except RoborockException
as err:
181 "Not setting up %s because we could not get the network information of the device. "
182 "Please confirm it is online and the Roborock servers can communicate with it",
186 await mqtt_client.async_release()
189 hass, device, networking, product_info, mqtt_client, home_data_rooms
192 await coordinator.verify_api()
193 coordinator.api.is_available =
True
195 await coordinator.get_maps()
196 except RoborockException
as err:
197 _LOGGER.warning(
"Failed to get map data")
200 await coordinator.async_config_entry_first_refresh()
201 except ConfigEntryNotReady
as ex:
202 await coordinator.release()
203 if isinstance(coordinator.api, RoborockMqttClientV1):
205 "Not setting up %s because the we failed to get data for the first time using the online client. "
206 "Please ensure your Home Assistant instance can communicate with this device. "
207 "You may need to open firewall instances on your Home Assistant network and on your Vacuum's network",
212 if coordinator.last_exception:
213 _LOGGER.debug(coordinator.last_exception)
214 raise coordinator.last_exception
from ex
215 elif coordinator.last_exception:
218 extra_error = f
"Please create an issue with the following error included: {coordinator.last_exception}"
220 "Not setting up %s because the coordinator failed to get data for the first time using the "
225 raise coordinator.last_exception
from ex
232 device: HomeDataDevice,
233 product_info: HomeDataProduct,
234 ) -> RoborockDataUpdateCoordinatorA01 |
None:
235 """Set up a A01 protocol device."""
236 mqtt_client = RoborockMqttClientA01(
237 user_data, DeviceData(device, product_info.name), product_info.category
240 await coord.async_config_entry_first_refresh()
245 """Handle removal of an entry."""
246 return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
250 """Handle options update."""
252 await hass.config_entries.async_reload(entry.entry_id)
list[RoborockDataUpdateCoordinator|RoborockDataUpdateCoordinatorA01] values(self)
None on_unload(HomeAssistant hass, GatewayId gateway_id, Callable fnct)
bool async_setup_entry(HomeAssistant hass, RoborockConfigEntry entry)
None update_listener(HomeAssistant hass, RoborockConfigEntry entry)
list[ Coroutine[ Any, Any, RoborockDataUpdateCoordinator|RoborockDataUpdateCoordinatorA01|None,]] build_setup_functions(HomeAssistant hass, dict[str, HomeDataDevice] device_map, UserData user_data, dict[str, HomeDataProduct] product_info, list[HomeDataRoom] home_data_rooms)
RoborockDataUpdateCoordinator|None setup_device_v1(HomeAssistant hass, UserData user_data, HomeDataDevice device, HomeDataProduct product_info, list[HomeDataRoom] home_data_rooms)
RoborockDataUpdateCoordinatorA01|None setup_device_a01(HomeAssistant hass, UserData user_data, HomeDataDevice device, HomeDataProduct product_info)
bool async_unload_entry(HomeAssistant hass, RoborockConfigEntry entry)
RoborockDataUpdateCoordinator|RoborockDataUpdateCoordinatorA01|None setup_device(HomeAssistant hass, UserData user_data, HomeDataDevice device, HomeDataProduct product_info, list[HomeDataRoom] home_data_rooms)