Skip to content

Commit 1d26979

Browse files
committed
Add map entities when home data arrives, not only at setup
Both platforms built their entities once, in async_setup_entry, and skipped any coordinator whose properties_api.home was still None. That is a race, not an invariant. The core Roborock coordinator calls discover_home() during setup, and a device that happens to be busy at that moment raises RoborockDeviceBusy, which the coordinator swallows with an _LOGGER.info. Home data lands a little later via the normal update cycle - but by then async_setup_entry has already returned, so that device silently gets no map image and no rotation select. There is no log line and no retry; the only cure is reloading the config entry by hand, and on a two-robot setup one of them can lose the race on most boots. Keep a set of the (device, map_flag) pairs already added and re-run the scan on every coordinator update, so entities appear as soon as their home data does. As a side effect this also picks up maps created in the Roborock app while Home Assistant is running, which previously needed a reload too. The listeners are registered through config_entry.async_on_unload so they are torn down with the entry.
1 parent 8cf623e commit 1d26979

2 files changed

Lines changed: 77 additions & 26 deletions

File tree

custom_components/roborock_custom_map/image.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,47 @@ async def async_setup_entry(
7474
async_add_entities: AddConfigEntryEntitiesCallback,
7575
) -> None:
7676
"""Set up Roborock image platform."""
77-
async_add_entities(
78-
RoborockMap(
79-
config_entry,
80-
f"{coord.duid_slug}_custom_map_{map_info.name or f'Map {map_info.map_flag}'}",
81-
coord,
82-
coord.properties_api.home,
83-
map_info.map_flag,
84-
map_info.name,
85-
)
86-
for coord in config_entry.runtime_data
87-
if coord.properties_api.home is not None
88-
for map_info in (coord.properties_api.home.home_map_info or {}).values()
89-
)
77+
added: set[tuple[str, int]] = set()
78+
79+
@callback
80+
def _async_add_new_maps() -> None:
81+
"""Add an entity for every map that has appeared since the last check.
82+
83+
Home data is not guaranteed to be populated by the time this platform
84+
is set up: a device that is busy when Home Assistant starts makes the
85+
Roborock coordinator skip `discover_home()`, so `home_map_info` is
86+
still empty here and that device silently ends up with no map entity
87+
until the config entry is reloaded by hand. Re-checking on every
88+
coordinator update picks those maps up as soon as they arrive, and as
89+
a bonus also catches maps created in the Roborock app while Home
90+
Assistant is running.
91+
"""
92+
entities = []
93+
for coord in config_entry.runtime_data:
94+
if (home := coord.properties_api.home) is None:
95+
continue
96+
for map_info in (home.home_map_info or {}).values():
97+
key = (coord.duid_slug, map_info.map_flag)
98+
if key in added:
99+
continue
100+
added.add(key)
101+
entities.append(
102+
RoborockMap(
103+
config_entry,
104+
f"{coord.duid_slug}_custom_map_{map_info.name or f'Map {map_info.map_flag}'}",
105+
coord,
106+
home,
107+
map_info.map_flag,
108+
map_info.name,
109+
)
110+
)
111+
if entities:
112+
async_add_entities(entities)
113+
114+
_async_add_new_maps()
115+
116+
for coord in config_entry.runtime_data:
117+
config_entry.async_on_unload(coord.async_add_listener(_async_add_new_maps))
90118

91119

92120
class RoborockMap(RoborockCoordinatedEntityV1, ImageEntity):

custom_components/roborock_custom_map/select.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from homeassistant.components.select import SelectEntity
88
from homeassistant.config_entries import ConfigEntry
99
from homeassistant.const import EntityCategory
10-
from homeassistant.core import HomeAssistant
10+
from homeassistant.core import HomeAssistant, callback
1111
from homeassistant.helpers.dispatcher import async_dispatcher_send
1212
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
1313
from homeassistant.helpers.restore_state import RestoreEntity
@@ -29,18 +29,41 @@ async def async_setup_entry(
2929
async_add_entities: AddConfigEntryEntitiesCallback,
3030
) -> None:
3131
"""Set up rotation Select entities (one per map)."""
32-
async_add_entities(
33-
RoborockMapRotationSelect(
34-
config_entry=config_entry,
35-
unique_id=f"{coord.duid_slug}_map_rotation_{map_info.map_flag}",
36-
coordinator=coord,
37-
map_flag=map_info.map_flag,
38-
map_name=map_info.name,
39-
)
40-
for coord in config_entry.runtime_data
41-
if coord.properties_api.home is not None
42-
for map_info in (coord.properties_api.home.home_map_info or {}).values()
43-
)
32+
added: set[tuple[str, int]] = set()
33+
34+
@callback
35+
def _async_add_new_maps() -> None:
36+
"""Add a select for every map that has appeared since the last check.
37+
38+
Mirrors the image platform: home data may still be empty when this
39+
runs, in which case the affected device would get no rotation select
40+
until the config entry is reloaded by hand. See image.py for details.
41+
"""
42+
entities = []
43+
for coord in config_entry.runtime_data:
44+
if (home := coord.properties_api.home) is None:
45+
continue
46+
for map_info in (home.home_map_info or {}).values():
47+
key = (coord.duid_slug, map_info.map_flag)
48+
if key in added:
49+
continue
50+
added.add(key)
51+
entities.append(
52+
RoborockMapRotationSelect(
53+
config_entry=config_entry,
54+
unique_id=f"{coord.duid_slug}_map_rotation_{map_info.map_flag}",
55+
coordinator=coord,
56+
map_flag=map_info.map_flag,
57+
map_name=map_info.name,
58+
)
59+
)
60+
if entities:
61+
async_add_entities(entities)
62+
63+
_async_add_new_maps()
64+
65+
for coord in config_entry.runtime_data:
66+
config_entry.async_on_unload(coord.async_add_listener(_async_add_new_maps))
4467

4568

4669
class RoborockMapRotationSelect(RoborockCoordinatedEntityV1, RestoreEntity, SelectEntity):

0 commit comments

Comments
 (0)