diff --git a/custom_components/roborock_custom_map/image.py b/custom_components/roborock_custom_map/image.py index a43b917..8d937eb 100644 --- a/custom_components/roborock_custom_map/image.py +++ b/custom_components/roborock_custom_map/image.py @@ -74,19 +74,47 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up Roborock image platform.""" - async_add_entities( - RoborockMap( - config_entry, - f"{coord.duid_slug}_custom_map_{map_info.name or f'Map {map_info.map_flag}'}", - coord, - coord.properties_api.home, - map_info.map_flag, - map_info.name, - ) - for coord in config_entry.runtime_data - if coord.properties_api.home is not None - for map_info in (coord.properties_api.home.home_map_info or {}).values() - ) + added: set[tuple[str, int]] = set() + + @callback + def _async_add_new_maps() -> None: + """Add an entity for every map that has appeared since the last check. + + Home data is not guaranteed to be populated by the time this platform + is set up: a device that is busy when Home Assistant starts makes the + Roborock coordinator skip `discover_home()`, so `home_map_info` is + still empty here and that device silently ends up with no map entity + until the config entry is reloaded by hand. Re-checking on every + coordinator update picks those maps up as soon as they arrive, and as + a bonus also catches maps created in the Roborock app while Home + Assistant is running. + """ + entities = [] + for coord in config_entry.runtime_data: + if (home := coord.properties_api.home) is None: + continue + for map_info in (home.home_map_info or {}).values(): + key = (coord.duid_slug, map_info.map_flag) + if key in added: + continue + added.add(key) + entities.append( + RoborockMap( + config_entry, + f"{coord.duid_slug}_custom_map_{map_info.name or f'Map {map_info.map_flag}'}", + coord, + home, + map_info.map_flag, + map_info.name, + ) + ) + if entities: + async_add_entities(entities) + + _async_add_new_maps() + + for coord in config_entry.runtime_data: + config_entry.async_on_unload(coord.async_add_listener(_async_add_new_maps)) class RoborockMap(RoborockCoordinatedEntityV1, ImageEntity): diff --git a/custom_components/roborock_custom_map/select.py b/custom_components/roborock_custom_map/select.py index a28d3ba..7878f88 100644 --- a/custom_components/roborock_custom_map/select.py +++ b/custom_components/roborock_custom_map/select.py @@ -7,7 +7,7 @@ from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity @@ -29,18 +29,41 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up rotation Select entities (one per map).""" - async_add_entities( - RoborockMapRotationSelect( - config_entry=config_entry, - unique_id=f"{coord.duid_slug}_map_rotation_{map_info.map_flag}", - coordinator=coord, - map_flag=map_info.map_flag, - map_name=map_info.name, - ) - for coord in config_entry.runtime_data - if coord.properties_api.home is not None - for map_info in (coord.properties_api.home.home_map_info or {}).values() - ) + added: set[tuple[str, int]] = set() + + @callback + def _async_add_new_maps() -> None: + """Add a select for every map that has appeared since the last check. + + Mirrors the image platform: home data may still be empty when this + runs, in which case the affected device would get no rotation select + until the config entry is reloaded by hand. See image.py for details. + """ + entities = [] + for coord in config_entry.runtime_data: + if (home := coord.properties_api.home) is None: + continue + for map_info in (home.home_map_info or {}).values(): + key = (coord.duid_slug, map_info.map_flag) + if key in added: + continue + added.add(key) + entities.append( + RoborockMapRotationSelect( + config_entry=config_entry, + unique_id=f"{coord.duid_slug}_map_rotation_{map_info.map_flag}", + coordinator=coord, + map_flag=map_info.map_flag, + map_name=map_info.name, + ) + ) + if entities: + async_add_entities(entities) + + _async_add_new_maps() + + for coord in config_entry.runtime_data: + config_entry.async_on_unload(coord.async_add_listener(_async_add_new_maps)) class RoborockMapRotationSelect(RoborockCoordinatedEntityV1, RestoreEntity, SelectEntity):