roborock.devices.traits.v1.home

Trait that represents a full view of the home layout.

This trait combines information about maps and rooms to provide a comprehensive view of the home layout, including room names and their corresponding segment on the map. It also makes it straight forward to fetch the map image and data.

This trait depends on the MapsTrait and RoomsTrait to gather the necessary information. It provides properties to access the current map, the list of rooms with names, and the map image and data.

Callers may first call discover_home() to populate the home layout cache by iterating through all available maps on the device. This will cache the map information and room names for all maps to minimize map switching and improve performance. After the initial discovery, callers can call refresh() to update the current map's information and room names as needed.

  1"""Trait that represents a full view of the home layout.
  2
  3This trait combines information about maps and rooms to provide a comprehensive
  4view of the home layout, including room names and their corresponding segment
  5on the map. It also makes it straight forward to fetch the map image and data.
  6
  7This trait depends on the MapsTrait and RoomsTrait to gather the necessary
  8information. It provides properties to access the current map, the list of
  9rooms with names, and the map image and data.
 10
 11Callers may first call `discover_home()` to populate the home layout cache by
 12iterating through all available maps on the device. This will cache the map
 13information and room names for all maps to minimize map switching and improve
 14performance. After the initial discovery, callers can call `refresh()` to update
 15the current map's information and room names as needed.
 16"""
 17
 18import asyncio
 19import base64
 20import logging
 21
 22from roborock.data import CombinedMapInfo, MultiMapsListMapInfo, NamedRoomMapping, RoborockBase
 23from roborock.data.v1.v1_code_mappings import RoborockStateCode
 24from roborock.devices.cache import DeviceCache
 25from roborock.devices.traits.v1 import common
 26from roborock.exceptions import RoborockDeviceBusy, RoborockException, RoborockInvalidStatus
 27from roborock.roborock_typing import RoborockCommand
 28
 29from .map_content import MapContent, MapContentTrait
 30from .maps import MapsTrait
 31from .rooms import RoomsTrait
 32from .status import StatusTrait
 33
 34_LOGGER = logging.getLogger(__name__)
 35
 36MAP_SLEEP = 3
 37
 38
 39class HomeTrait(RoborockBase, common.V1TraitMixin):
 40    """Trait that represents a full view of the home layout."""
 41
 42    command = RoborockCommand.GET_MAP_V1  # This is not used
 43    converter = common.DefaultConverter(RoborockBase)  # Not used
 44
 45    def __init__(
 46        self,
 47        status_trait: StatusTrait,
 48        maps_trait: MapsTrait,
 49        map_content: MapContentTrait,
 50        rooms_trait: RoomsTrait,
 51        device_cache: DeviceCache,
 52    ) -> None:
 53        """Initialize the HomeTrait.
 54
 55        We keep track of the MapsTrait and RoomsTrait to provide a comprehensive
 56        view of the home layout. This also depends on the StatusTrait to determine
 57        the current map. See comments in MapsTrait for details on that dependency.
 58
 59        The cache is used to store discovered home data to minimize map switching
 60        and improve performance. The cache should be persisted by the caller to
 61        ensure data is retained across restarts.
 62
 63        After initial discovery, only information for the current map is refreshed
 64        to keep data up to date without excessive map switching. However, as
 65        users switch rooms, the current map's data will be updated to ensure
 66        accuracy.
 67        """
 68        super().__init__()
 69        self._status_trait = status_trait
 70        self._maps_trait = maps_trait
 71        self._map_content = map_content
 72        self._rooms_trait = rooms_trait
 73        self._device_cache = device_cache
 74        self._discovery_completed = False
 75        self._home_map_info: dict[int, CombinedMapInfo] | None = None
 76        self._home_map_content: dict[int, MapContent] | None = None
 77
 78    async def discover_home(self) -> None:
 79        """Iterate through all maps to discover rooms and cache them.
 80
 81        This will be a no-op if the home cache is already populated.
 82
 83        This cannot be called while the device is cleaning, as that would interrupt the
 84        cleaning process. This will raise `RoborockDeviceBusy` if the device is
 85        currently cleaning.
 86
 87        After discovery, the home cache will be populated and can be accessed via the `home_map_info` property.
 88        """
 89        device_cache_data = await self._device_cache.get()
 90        if device_cache_data and device_cache_data.home_map_info:
 91            _LOGGER.debug("Home cache already populated, skipping discovery")
 92            self._home_map_info = device_cache_data.home_map_info
 93            self._discovery_completed = True
 94            try:
 95                self._home_map_content = {
 96                    k: self._map_content.converter.parse_map_content(base64.b64decode(v))
 97                    for k, v in (device_cache_data.home_map_content_base64 or {}).items()
 98                }
 99            except (ValueError, RoborockException) as ex:
100                _LOGGER.warning("Failed to parse cached home map content, will re-discover: %s", ex)
101                self._home_map_content = {}
102            else:
103                return
104
105        if self._status_trait.state == RoborockStateCode.cleaning:
106            raise RoborockDeviceBusy("Cannot perform home discovery while the device is cleaning")
107
108        await self._maps_trait.refresh()
109        if self._maps_trait.current_map_info is None:
110            _LOGGER.debug("Cannot perform home discovery without current map info")
111            self._discovery_completed = True
112            await self._update_home_cache({}, {})
113            return
114
115        home_map_info, home_map_content = await self._build_home_map_info()
116        _LOGGER.debug("Home discovery complete, caching data for %d maps", len(home_map_info))
117        self._discovery_completed = True
118        await self._update_home_cache(home_map_info, home_map_content)
119
120    async def _refresh_map_info(self, map_info: MultiMapsListMapInfo) -> CombinedMapInfo:
121        """Collect room data for a specific map and return CombinedMapInfo."""
122        await self._rooms_trait.refresh()
123
124        # We have room names from multiple sources:
125        # - The map_info.rooms which we just received from the MultiMapsList
126        # - RoomsTrait rooms come from the GET_ROOM_MAPPING command for the current device (only)
127        # - RoomsTrait rooms that are pulled from the cloud API
128        # We always prefer the RoomsTrait room names since they are always newer and
129        # just refreshed above.
130        rooms_map: dict[int, NamedRoomMapping] = {
131            **map_info.rooms_map,
132            **{room.segment_id: room for room in self._rooms_trait.rooms or ()},
133        }
134        return CombinedMapInfo(
135            map_flag=map_info.map_flag,
136            name=map_info.name,
137            rooms=list(rooms_map.values()),
138        )
139
140    async def _refresh_map_content(self) -> MapContent:
141        """Refresh the map content trait to get the latest map data."""
142        await self._map_content.refresh()
143        return MapContent(
144            image_content=self._map_content.image_content,
145            map_data=self._map_content.map_data,
146            raw_api_response=self._map_content.raw_api_response,
147        )
148
149    async def _build_home_map_info(self) -> tuple[dict[int, CombinedMapInfo], dict[int, MapContent]]:
150        """Perform the actual discovery and caching of home map info and content."""
151        home_map_info: dict[int, CombinedMapInfo] = {}
152        home_map_content: dict[int, MapContent] = {}
153
154        # Sort map_info to process the current map last, reducing map switching.
155        # False (non-original maps) sorts before True (original map). We ensure
156        # we load the original map last.
157        sorted_map_infos = sorted(
158            self._maps_trait.map_info or [],
159            key=lambda mi: mi.map_flag == self._maps_trait.current_map,
160            reverse=False,
161        )
162        _LOGGER.debug("Building home cache for maps: %s", [mi.map_flag for mi in sorted_map_infos])
163        for map_info in sorted_map_infos:
164            # We need to load each map to get its room data
165            if len(sorted_map_infos) > 1:
166                _LOGGER.debug("Loading map %s", map_info.map_flag)
167                try:
168                    await self._maps_trait.set_current_map(map_info.map_flag)
169                except RoborockInvalidStatus as ex:
170                    # Device is in a state that forbids map switching. Translate to
171                    # "busy" so callers can fall back to refreshing the current map only.
172                    raise RoborockDeviceBusy("Cannot switch maps right now (device action locked)") from ex
173                await asyncio.sleep(MAP_SLEEP)
174
175            map_content = await self._refresh_map_content()
176            home_map_content[map_info.map_flag] = map_content
177
178            combined_map_info = await self._refresh_map_info(map_info)
179            home_map_info[map_info.map_flag] = combined_map_info
180        return home_map_info, home_map_content
181
182    async def refresh(self) -> None:
183        """Refresh current map's underlying map and room data, updating cache as needed.
184
185        This will only refresh the current map's data and will not populate non
186        active maps or re-discover the home. It is expected that this will keep
187        information up to date for the current map as users switch to that map.
188        """
189        if not self._discovery_completed:
190            # Running initial discovery also populates all of the same information
191            # as below so we can just call that method. If the device is busy
192            # then we'll fall through below to refresh the current map only.
193            try:
194                await self.discover_home()
195                return
196            except RoborockDeviceBusy:
197                _LOGGER.debug("Cannot refresh home data while device is busy cleaning")
198
199        # Refresh the list of map names/info
200        await self._maps_trait.refresh()
201        if (current_map_info := self._maps_trait.current_map_info) is None or (
202            map_flag := self._maps_trait.current_map
203        ) is None:
204            _LOGGER.debug("Cannot refresh home data without current map info")
205            return
206
207        # Refresh the map content to ensure we have the latest image and object positions
208        new_map_content = await self._refresh_map_content()
209        # Refresh the current map's room data
210        combined_map_info = await self._refresh_map_info(current_map_info)
211        await self._update_current_map(
212            map_flag, combined_map_info, new_map_content, update_cache=self._discovery_completed
213        )
214
215    @property
216    def home_map_info(self) -> dict[int, CombinedMapInfo] | None:
217        """Returns the map information for all cached maps."""
218        return self._home_map_info
219
220    @property
221    def current_map_data(self) -> CombinedMapInfo | None:
222        """Returns the map data for the current map."""
223        current_map_flag = self._maps_trait.current_map
224        if current_map_flag is None or self._home_map_info is None:
225            return None
226        return self._home_map_info.get(current_map_flag)
227
228    @property
229    def current_rooms(self) -> list[NamedRoomMapping]:
230        """Returns the room names for the current map."""
231        if self.current_map_data is None:
232            return []
233        return self.current_map_data.rooms
234
235    @property
236    def home_map_content(self) -> dict[int, MapContent] | None:
237        """Returns the map content for all cached maps."""
238        return self._home_map_content
239
240    async def _update_home_cache(
241        self, home_map_info: dict[int, CombinedMapInfo], home_map_content: dict[int, MapContent]
242    ) -> None:
243        """Update the entire home cache with new map info and content."""
244        device_cache_data = await self._device_cache.get()
245        device_cache_data.home_map_info = home_map_info
246        device_cache_data.home_map_content_base64 = {
247            k: base64.b64encode(v.raw_api_response).decode("utf-8")
248            for k, v in home_map_content.items()
249            if v.raw_api_response
250        }
251        await self._device_cache.set(device_cache_data)
252        self._home_map_info = home_map_info
253        self._home_map_content = home_map_content
254
255    async def _update_current_map(
256        self,
257        map_flag: int,
258        map_info: CombinedMapInfo,
259        map_content: MapContent,
260        update_cache: bool,
261    ) -> None:
262        """Update the cache for the current map only."""
263        # Update the persistent cache if requested e.g. home discovery has
264        # completed and we want to keep it fresh. Otherwise just update the
265        # in memory map below.
266        if update_cache:
267            device_cache_data = await self._device_cache.get()
268            if device_cache_data.home_map_info is None:
269                device_cache_data.home_map_info = {}
270            device_cache_data.home_map_info[map_flag] = map_info
271            if map_content.raw_api_response:
272                if device_cache_data.home_map_content_base64 is None:
273                    device_cache_data.home_map_content_base64 = {}
274                device_cache_data.home_map_content_base64[map_flag] = base64.b64encode(
275                    map_content.raw_api_response
276                ).decode("utf-8")
277            await self._device_cache.set(device_cache_data)
278
279        if self._home_map_info is None:
280            self._home_map_info = {}
281        self._home_map_info[map_flag] = map_info
282
283        if self._home_map_content is None:
284            self._home_map_content = {}
285        self._home_map_content[map_flag] = map_content
MAP_SLEEP = 3
 40class HomeTrait(RoborockBase, common.V1TraitMixin):
 41    """Trait that represents a full view of the home layout."""
 42
 43    command = RoborockCommand.GET_MAP_V1  # This is not used
 44    converter = common.DefaultConverter(RoborockBase)  # Not used
 45
 46    def __init__(
 47        self,
 48        status_trait: StatusTrait,
 49        maps_trait: MapsTrait,
 50        map_content: MapContentTrait,
 51        rooms_trait: RoomsTrait,
 52        device_cache: DeviceCache,
 53    ) -> None:
 54        """Initialize the HomeTrait.
 55
 56        We keep track of the MapsTrait and RoomsTrait to provide a comprehensive
 57        view of the home layout. This also depends on the StatusTrait to determine
 58        the current map. See comments in MapsTrait for details on that dependency.
 59
 60        The cache is used to store discovered home data to minimize map switching
 61        and improve performance. The cache should be persisted by the caller to
 62        ensure data is retained across restarts.
 63
 64        After initial discovery, only information for the current map is refreshed
 65        to keep data up to date without excessive map switching. However, as
 66        users switch rooms, the current map's data will be updated to ensure
 67        accuracy.
 68        """
 69        super().__init__()
 70        self._status_trait = status_trait
 71        self._maps_trait = maps_trait
 72        self._map_content = map_content
 73        self._rooms_trait = rooms_trait
 74        self._device_cache = device_cache
 75        self._discovery_completed = False
 76        self._home_map_info: dict[int, CombinedMapInfo] | None = None
 77        self._home_map_content: dict[int, MapContent] | None = None
 78
 79    async def discover_home(self) -> None:
 80        """Iterate through all maps to discover rooms and cache them.
 81
 82        This will be a no-op if the home cache is already populated.
 83
 84        This cannot be called while the device is cleaning, as that would interrupt the
 85        cleaning process. This will raise `RoborockDeviceBusy` if the device is
 86        currently cleaning.
 87
 88        After discovery, the home cache will be populated and can be accessed via the `home_map_info` property.
 89        """
 90        device_cache_data = await self._device_cache.get()
 91        if device_cache_data and device_cache_data.home_map_info:
 92            _LOGGER.debug("Home cache already populated, skipping discovery")
 93            self._home_map_info = device_cache_data.home_map_info
 94            self._discovery_completed = True
 95            try:
 96                self._home_map_content = {
 97                    k: self._map_content.converter.parse_map_content(base64.b64decode(v))
 98                    for k, v in (device_cache_data.home_map_content_base64 or {}).items()
 99                }
100            except (ValueError, RoborockException) as ex:
101                _LOGGER.warning("Failed to parse cached home map content, will re-discover: %s", ex)
102                self._home_map_content = {}
103            else:
104                return
105
106        if self._status_trait.state == RoborockStateCode.cleaning:
107            raise RoborockDeviceBusy("Cannot perform home discovery while the device is cleaning")
108
109        await self._maps_trait.refresh()
110        if self._maps_trait.current_map_info is None:
111            _LOGGER.debug("Cannot perform home discovery without current map info")
112            self._discovery_completed = True
113            await self._update_home_cache({}, {})
114            return
115
116        home_map_info, home_map_content = await self._build_home_map_info()
117        _LOGGER.debug("Home discovery complete, caching data for %d maps", len(home_map_info))
118        self._discovery_completed = True
119        await self._update_home_cache(home_map_info, home_map_content)
120
121    async def _refresh_map_info(self, map_info: MultiMapsListMapInfo) -> CombinedMapInfo:
122        """Collect room data for a specific map and return CombinedMapInfo."""
123        await self._rooms_trait.refresh()
124
125        # We have room names from multiple sources:
126        # - The map_info.rooms which we just received from the MultiMapsList
127        # - RoomsTrait rooms come from the GET_ROOM_MAPPING command for the current device (only)
128        # - RoomsTrait rooms that are pulled from the cloud API
129        # We always prefer the RoomsTrait room names since they are always newer and
130        # just refreshed above.
131        rooms_map: dict[int, NamedRoomMapping] = {
132            **map_info.rooms_map,
133            **{room.segment_id: room for room in self._rooms_trait.rooms or ()},
134        }
135        return CombinedMapInfo(
136            map_flag=map_info.map_flag,
137            name=map_info.name,
138            rooms=list(rooms_map.values()),
139        )
140
141    async def _refresh_map_content(self) -> MapContent:
142        """Refresh the map content trait to get the latest map data."""
143        await self._map_content.refresh()
144        return MapContent(
145            image_content=self._map_content.image_content,
146            map_data=self._map_content.map_data,
147            raw_api_response=self._map_content.raw_api_response,
148        )
149
150    async def _build_home_map_info(self) -> tuple[dict[int, CombinedMapInfo], dict[int, MapContent]]:
151        """Perform the actual discovery and caching of home map info and content."""
152        home_map_info: dict[int, CombinedMapInfo] = {}
153        home_map_content: dict[int, MapContent] = {}
154
155        # Sort map_info to process the current map last, reducing map switching.
156        # False (non-original maps) sorts before True (original map). We ensure
157        # we load the original map last.
158        sorted_map_infos = sorted(
159            self._maps_trait.map_info or [],
160            key=lambda mi: mi.map_flag == self._maps_trait.current_map,
161            reverse=False,
162        )
163        _LOGGER.debug("Building home cache for maps: %s", [mi.map_flag for mi in sorted_map_infos])
164        for map_info in sorted_map_infos:
165            # We need to load each map to get its room data
166            if len(sorted_map_infos) > 1:
167                _LOGGER.debug("Loading map %s", map_info.map_flag)
168                try:
169                    await self._maps_trait.set_current_map(map_info.map_flag)
170                except RoborockInvalidStatus as ex:
171                    # Device is in a state that forbids map switching. Translate to
172                    # "busy" so callers can fall back to refreshing the current map only.
173                    raise RoborockDeviceBusy("Cannot switch maps right now (device action locked)") from ex
174                await asyncio.sleep(MAP_SLEEP)
175
176            map_content = await self._refresh_map_content()
177            home_map_content[map_info.map_flag] = map_content
178
179            combined_map_info = await self._refresh_map_info(map_info)
180            home_map_info[map_info.map_flag] = combined_map_info
181        return home_map_info, home_map_content
182
183    async def refresh(self) -> None:
184        """Refresh current map's underlying map and room data, updating cache as needed.
185
186        This will only refresh the current map's data and will not populate non
187        active maps or re-discover the home. It is expected that this will keep
188        information up to date for the current map as users switch to that map.
189        """
190        if not self._discovery_completed:
191            # Running initial discovery also populates all of the same information
192            # as below so we can just call that method. If the device is busy
193            # then we'll fall through below to refresh the current map only.
194            try:
195                await self.discover_home()
196                return
197            except RoborockDeviceBusy:
198                _LOGGER.debug("Cannot refresh home data while device is busy cleaning")
199
200        # Refresh the list of map names/info
201        await self._maps_trait.refresh()
202        if (current_map_info := self._maps_trait.current_map_info) is None or (
203            map_flag := self._maps_trait.current_map
204        ) is None:
205            _LOGGER.debug("Cannot refresh home data without current map info")
206            return
207
208        # Refresh the map content to ensure we have the latest image and object positions
209        new_map_content = await self._refresh_map_content()
210        # Refresh the current map's room data
211        combined_map_info = await self._refresh_map_info(current_map_info)
212        await self._update_current_map(
213            map_flag, combined_map_info, new_map_content, update_cache=self._discovery_completed
214        )
215
216    @property
217    def home_map_info(self) -> dict[int, CombinedMapInfo] | None:
218        """Returns the map information for all cached maps."""
219        return self._home_map_info
220
221    @property
222    def current_map_data(self) -> CombinedMapInfo | None:
223        """Returns the map data for the current map."""
224        current_map_flag = self._maps_trait.current_map
225        if current_map_flag is None or self._home_map_info is None:
226            return None
227        return self._home_map_info.get(current_map_flag)
228
229    @property
230    def current_rooms(self) -> list[NamedRoomMapping]:
231        """Returns the room names for the current map."""
232        if self.current_map_data is None:
233            return []
234        return self.current_map_data.rooms
235
236    @property
237    def home_map_content(self) -> dict[int, MapContent] | None:
238        """Returns the map content for all cached maps."""
239        return self._home_map_content
240
241    async def _update_home_cache(
242        self, home_map_info: dict[int, CombinedMapInfo], home_map_content: dict[int, MapContent]
243    ) -> None:
244        """Update the entire home cache with new map info and content."""
245        device_cache_data = await self._device_cache.get()
246        device_cache_data.home_map_info = home_map_info
247        device_cache_data.home_map_content_base64 = {
248            k: base64.b64encode(v.raw_api_response).decode("utf-8")
249            for k, v in home_map_content.items()
250            if v.raw_api_response
251        }
252        await self._device_cache.set(device_cache_data)
253        self._home_map_info = home_map_info
254        self._home_map_content = home_map_content
255
256    async def _update_current_map(
257        self,
258        map_flag: int,
259        map_info: CombinedMapInfo,
260        map_content: MapContent,
261        update_cache: bool,
262    ) -> None:
263        """Update the cache for the current map only."""
264        # Update the persistent cache if requested e.g. home discovery has
265        # completed and we want to keep it fresh. Otherwise just update the
266        # in memory map below.
267        if update_cache:
268            device_cache_data = await self._device_cache.get()
269            if device_cache_data.home_map_info is None:
270                device_cache_data.home_map_info = {}
271            device_cache_data.home_map_info[map_flag] = map_info
272            if map_content.raw_api_response:
273                if device_cache_data.home_map_content_base64 is None:
274                    device_cache_data.home_map_content_base64 = {}
275                device_cache_data.home_map_content_base64[map_flag] = base64.b64encode(
276                    map_content.raw_api_response
277                ).decode("utf-8")
278            await self._device_cache.set(device_cache_data)
279
280        if self._home_map_info is None:
281            self._home_map_info = {}
282        self._home_map_info[map_flag] = map_info
283
284        if self._home_map_content is None:
285            self._home_map_content = {}
286        self._home_map_content[map_flag] = map_content

Trait that represents a full view of the home layout.

HomeTrait( status_trait: roborock.devices.traits.v1.status.StatusTrait, maps_trait: <function mqtt_rpc_channel.<locals>.wrapper>, map_content: <function map_rpc_channel.<locals>.wrapper>, rooms_trait: roborock.devices.traits.v1.rooms.RoomsTrait, device_cache: roborock.devices.cache.DeviceCache)
46    def __init__(
47        self,
48        status_trait: StatusTrait,
49        maps_trait: MapsTrait,
50        map_content: MapContentTrait,
51        rooms_trait: RoomsTrait,
52        device_cache: DeviceCache,
53    ) -> None:
54        """Initialize the HomeTrait.
55
56        We keep track of the MapsTrait and RoomsTrait to provide a comprehensive
57        view of the home layout. This also depends on the StatusTrait to determine
58        the current map. See comments in MapsTrait for details on that dependency.
59
60        The cache is used to store discovered home data to minimize map switching
61        and improve performance. The cache should be persisted by the caller to
62        ensure data is retained across restarts.
63
64        After initial discovery, only information for the current map is refreshed
65        to keep data up to date without excessive map switching. However, as
66        users switch rooms, the current map's data will be updated to ensure
67        accuracy.
68        """
69        super().__init__()
70        self._status_trait = status_trait
71        self._maps_trait = maps_trait
72        self._map_content = map_content
73        self._rooms_trait = rooms_trait
74        self._device_cache = device_cache
75        self._discovery_completed = False
76        self._home_map_info: dict[int, CombinedMapInfo] | None = None
77        self._home_map_content: dict[int, MapContent] | None = None

Initialize the HomeTrait.

We keep track of the MapsTrait and RoomsTrait to provide a comprehensive view of the home layout. This also depends on the StatusTrait to determine the current map. See comments in MapsTrait for details on that dependency.

The cache is used to store discovered home data to minimize map switching and improve performance. The cache should be persisted by the caller to ensure data is retained across restarts.

After initial discovery, only information for the current map is refreshed to keep data up to date without excessive map switching. However, as users switch rooms, the current map's data will be updated to ensure accuracy.

command = <RoborockCommand.GET_MAP_V1: 'get_map_v1'>

The RoborockCommand used to fetch the trait data from the device (internal only).

converter = DefaultConverter

The converter used to parse the response from the device (internal only).

async def discover_home(self) -> None:
 79    async def discover_home(self) -> None:
 80        """Iterate through all maps to discover rooms and cache them.
 81
 82        This will be a no-op if the home cache is already populated.
 83
 84        This cannot be called while the device is cleaning, as that would interrupt the
 85        cleaning process. This will raise `RoborockDeviceBusy` if the device is
 86        currently cleaning.
 87
 88        After discovery, the home cache will be populated and can be accessed via the `home_map_info` property.
 89        """
 90        device_cache_data = await self._device_cache.get()
 91        if device_cache_data and device_cache_data.home_map_info:
 92            _LOGGER.debug("Home cache already populated, skipping discovery")
 93            self._home_map_info = device_cache_data.home_map_info
 94            self._discovery_completed = True
 95            try:
 96                self._home_map_content = {
 97                    k: self._map_content.converter.parse_map_content(base64.b64decode(v))
 98                    for k, v in (device_cache_data.home_map_content_base64 or {}).items()
 99                }
100            except (ValueError, RoborockException) as ex:
101                _LOGGER.warning("Failed to parse cached home map content, will re-discover: %s", ex)
102                self._home_map_content = {}
103            else:
104                return
105
106        if self._status_trait.state == RoborockStateCode.cleaning:
107            raise RoborockDeviceBusy("Cannot perform home discovery while the device is cleaning")
108
109        await self._maps_trait.refresh()
110        if self._maps_trait.current_map_info is None:
111            _LOGGER.debug("Cannot perform home discovery without current map info")
112            self._discovery_completed = True
113            await self._update_home_cache({}, {})
114            return
115
116        home_map_info, home_map_content = await self._build_home_map_info()
117        _LOGGER.debug("Home discovery complete, caching data for %d maps", len(home_map_info))
118        self._discovery_completed = True
119        await self._update_home_cache(home_map_info, home_map_content)

Iterate through all maps to discover rooms and cache them.

This will be a no-op if the home cache is already populated.

This cannot be called while the device is cleaning, as that would interrupt the cleaning process. This will raise RoborockDeviceBusy if the device is currently cleaning.

After discovery, the home cache will be populated and can be accessed via the home_map_info property.

async def refresh(self) -> None:
183    async def refresh(self) -> None:
184        """Refresh current map's underlying map and room data, updating cache as needed.
185
186        This will only refresh the current map's data and will not populate non
187        active maps or re-discover the home. It is expected that this will keep
188        information up to date for the current map as users switch to that map.
189        """
190        if not self._discovery_completed:
191            # Running initial discovery also populates all of the same information
192            # as below so we can just call that method. If the device is busy
193            # then we'll fall through below to refresh the current map only.
194            try:
195                await self.discover_home()
196                return
197            except RoborockDeviceBusy:
198                _LOGGER.debug("Cannot refresh home data while device is busy cleaning")
199
200        # Refresh the list of map names/info
201        await self._maps_trait.refresh()
202        if (current_map_info := self._maps_trait.current_map_info) is None or (
203            map_flag := self._maps_trait.current_map
204        ) is None:
205            _LOGGER.debug("Cannot refresh home data without current map info")
206            return
207
208        # Refresh the map content to ensure we have the latest image and object positions
209        new_map_content = await self._refresh_map_content()
210        # Refresh the current map's room data
211        combined_map_info = await self._refresh_map_info(current_map_info)
212        await self._update_current_map(
213            map_flag, combined_map_info, new_map_content, update_cache=self._discovery_completed
214        )

Refresh current map's underlying map and room data, updating cache as needed.

This will only refresh the current map's data and will not populate non active maps or re-discover the home. It is expected that this will keep information up to date for the current map as users switch to that map.

home_map_info: dict[int, roborock.data.containers.CombinedMapInfo] | None
216    @property
217    def home_map_info(self) -> dict[int, CombinedMapInfo] | None:
218        """Returns the map information for all cached maps."""
219        return self._home_map_info

Returns the map information for all cached maps.

current_map_data: roborock.data.containers.CombinedMapInfo | None
221    @property
222    def current_map_data(self) -> CombinedMapInfo | None:
223        """Returns the map data for the current map."""
224        current_map_flag = self._maps_trait.current_map
225        if current_map_flag is None or self._home_map_info is None:
226            return None
227        return self._home_map_info.get(current_map_flag)

Returns the map data for the current map.

current_rooms: list[roborock.data.containers.NamedRoomMapping]
229    @property
230    def current_rooms(self) -> list[NamedRoomMapping]:
231        """Returns the room names for the current map."""
232        if self.current_map_data is None:
233            return []
234        return self.current_map_data.rooms

Returns the room names for the current map.

home_map_content: dict[int, roborock.devices.traits.v1.map_content.MapContent] | None
236    @property
237    def home_map_content(self) -> dict[int, MapContent] | None:
238        """Returns the map content for all cached maps."""
239        return self._home_map_content

Returns the map content for all cached maps.