Simplify roborock coordinator (#134700)

* Update roborock coordinator to require maps on startup

* Fix indent in merge
This commit is contained in:
Allen Porter 2025-01-07 07:09:32 -08:00 committed by GitHub
parent 393551d696
commit c684b06734
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 43 deletions

View File

@ -205,14 +205,6 @@ async def setup_device_v1(
coordinator = RoborockDataUpdateCoordinator( coordinator = RoborockDataUpdateCoordinator(
hass, device, networking, product_info, mqtt_client, home_data_rooms hass, device, networking, product_info, mqtt_client, home_data_rooms
) )
# Verify we can communicate locally - if we can't, switch to cloud api
await coordinator.verify_api()
coordinator.api.is_available = True
try:
await coordinator.get_maps()
except RoborockException as err:
_LOGGER.warning("Failed to get map data")
_LOGGER.debug(err)
try: try:
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
except ConfigEntryNotReady as ex: except ConfigEntryNotReady as ex:

View File

@ -73,7 +73,27 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
self.maps: dict[int, RoborockMapInfo] = {} self.maps: dict[int, RoborockMapInfo] = {}
self._home_data_rooms = {str(room.id): room.name for room in home_data_rooms} self._home_data_rooms = {str(room.id): room.name for room in home_data_rooms}
async def verify_api(self) -> None: async def _async_setup(self) -> None:
"""Set up the coordinator."""
# Verify we can communicate locally - if we can't, switch to cloud api
await self._verify_api()
self.api.is_available = True
try:
maps = await self.api.get_multi_maps_list()
except RoborockException as err:
raise UpdateFailed("Failed to get map data: {err}") from err
# Rooms names populated later with calls to `set_current_map_rooms` for each map
self.maps = {
roborock_map.mapFlag: RoborockMapInfo(
flag=roborock_map.mapFlag,
name=roborock_map.name or f"Map {roborock_map.mapFlag}",
rooms={},
)
for roborock_map in (maps.map_info if (maps and maps.map_info) else ())
}
async def _verify_api(self) -> None:
"""Verify that the api is reachable. If it is not, switch clients.""" """Verify that the api is reachable. If it is not, switch clients."""
if isinstance(self.api, RoborockLocalClientV1): if isinstance(self.api, RoborockLocalClientV1):
try: try:
@ -96,12 +116,8 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
async def _update_device_prop(self) -> None: async def _update_device_prop(self) -> None:
"""Update device properties.""" """Update device properties."""
device_prop = await self.api.get_prop() if (device_prop := await self.api.get_prop()) is not None:
if device_prop:
if self.roborock_device_info.props:
self.roborock_device_info.props.update(device_prop) self.roborock_device_info.props.update(device_prop)
else:
self.roborock_device_info.props = device_prop
async def _async_update_data(self) -> DeviceProp: async def _async_update_data(self) -> DeviceProp:
"""Update data via library.""" """Update data via library."""
@ -111,7 +127,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
# Set the new map id from the updated device props # Set the new map id from the updated device props
self._set_current_map() self._set_current_map()
# Get the rooms for that map id. # Get the rooms for that map id.
await self.get_rooms() await self.set_current_map_rooms()
except RoborockException as ex: except RoborockException as ex:
raise UpdateFailed(ex) from ex raise UpdateFailed(ex) from ex
return self.roborock_device_info.props return self.roborock_device_info.props
@ -127,29 +143,18 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
self.roborock_device_info.props.status.map_status - 3 self.roborock_device_info.props.status.map_status - 3
) // 4 ) // 4
async def get_maps(self) -> None: async def set_current_map_rooms(self) -> None:
"""Add a map to the coordinators mapping.""" """Fetch all of the rooms for the current map and set on RoborockMapInfo."""
maps = await self.api.get_multi_maps_list()
if maps and maps.map_info:
for roborock_map in maps.map_info:
self.maps[roborock_map.mapFlag] = RoborockMapInfo(
flag=roborock_map.mapFlag,
name=roborock_map.name or f"Map {roborock_map.mapFlag}",
rooms={},
)
async def get_rooms(self) -> None:
"""Get all of the rooms for the current map."""
# The api is only able to access rooms for the currently selected map # The api is only able to access rooms for the currently selected map
# So it is important this is only called when you have the map you care # So it is important this is only called when you have the map you care
# about selected. # about selected.
if self.current_map in self.maps: if self.current_map is None or self.current_map not in self.maps:
iot_rooms = await self.api.get_room_mapping() return
if iot_rooms is not None: room_mapping = await self.api.get_room_mapping()
for room in iot_rooms: self.maps[self.current_map].rooms = {
self.maps[self.current_map].rooms[room.segment_id] = ( room.segment_id: self._home_data_rooms.get(room.iot_id, "Unknown")
self._home_data_rooms.get(room.iot_id, "Unknown") for room in room_mapping or ()
) }
@cached_property @cached_property
def duid(self) -> str: def duid(self) -> str:

View File

@ -121,7 +121,10 @@ class RoborockMap(RoborockCoordinatedEntityV1, ImageEntity):
"""Update the image if it is not cached.""" """Update the image if it is not cached."""
if self.is_map_valid(): if self.is_map_valid():
response = await asyncio.gather( response = await asyncio.gather(
*(self.cloud_api.get_map_v1(), self.coordinator.get_rooms()), *(
self.cloud_api.get_map_v1(),
self.coordinator.set_current_map_rooms(),
),
return_exceptions=True, return_exceptions=True,
) )
if not isinstance(response[0], bytes): if not isinstance(response[0], bytes):
@ -174,7 +177,8 @@ async def create_coordinator_maps(
await asyncio.sleep(MAP_SLEEP) await asyncio.sleep(MAP_SLEEP)
# Get the map data # Get the map data
map_update = await asyncio.gather( map_update = await asyncio.gather(
*[coord.cloud_api.get_map_v1(), coord.get_rooms()], return_exceptions=True *[coord.cloud_api.get_map_v1(), coord.set_current_map_rooms()],
return_exceptions=True,
) )
# If we fail to get the map, we should set it to empty byte, # If we fail to get the map, we should set it to empty byte,
# still create it, and set it as unavailable. # still create it, and set it as unavailable.

View File

@ -133,20 +133,18 @@ async def test_local_client_fails_props(
assert mock_roborock_entry.state is ConfigEntryState.SETUP_RETRY assert mock_roborock_entry.state is ConfigEntryState.SETUP_RETRY
async def test_fails_maps_continue( async def test_fail_maps(
hass: HomeAssistant, hass: HomeAssistant,
mock_roborock_entry: MockConfigEntry, mock_roborock_entry: MockConfigEntry,
bypass_api_fixture_v1_only, bypass_api_fixture_v1_only,
) -> None: ) -> None:
"""Test that if we fail to get the maps, we still setup.""" """Test that the integration fails to load if we fail to get the maps."""
with patch( with patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.get_multi_maps_list", "homeassistant.components.roborock.coordinator.RoborockLocalClientV1.get_multi_maps_list",
side_effect=RoborockException(), side_effect=RoborockException(),
): ):
await async_setup_component(hass, DOMAIN, {}) await async_setup_component(hass, DOMAIN, {})
assert mock_roborock_entry.state is ConfigEntryState.LOADED assert mock_roborock_entry.state is ConfigEntryState.SETUP_RETRY
# No map data means no images
assert len(hass.states.async_all("image")) == 0
async def test_reauth_started( async def test_reauth_started(