roborock.devices.traits.v1.maps

Trait for managing maps and room mappings on Roborock devices.

New datatypes are introduced here to manage the additional information associated with maps and rooms, such as map names and room names. These override the base container datatypes to add additional fields.

 1"""Trait for managing maps and room mappings on Roborock devices.
 2
 3New datatypes are introduced here to manage the additional information associated
 4with maps and rooms, such as map names and room names. These override the
 5base container datatypes to add additional fields.
 6"""
 7
 8import logging
 9from typing import Self
10
11from roborock.data import MultiMapsList, MultiMapsListMapInfo
12from roborock.devices.traits.v1 import common
13from roborock.roborock_typing import RoborockCommand
14
15from .status import StatusTrait
16
17_LOGGER = logging.getLogger(__name__)
18
19
20@common.mqtt_rpc_channel
21class MapsTrait(MultiMapsList, common.V1TraitMixin):
22    """Trait for managing the maps of Roborock devices.
23
24    A device may have multiple maps, each identified by a unique map_flag.
25    Each map can have multiple rooms associated with it, in a `RoomMapping`.
26
27    The MapsTrait depends on the StatusTrait to determine the currently active
28    map. It is the responsibility of the caller to ensure that the StatusTrait
29    is up to date before using this trait. However, there is a possibility of
30    races if another client changes the current map between the time the
31    StatusTrait is refreshed and when the MapsTrait is used. This is mitigated
32    by the fact that the map list is unlikely to change frequently, and the
33    current map is only changed when the user explicitly switches maps.
34    """
35
36    command = RoborockCommand.GET_MULTI_MAPS_LIST
37
38    def __init__(self, status_trait: StatusTrait) -> None:
39        """Initialize the MapsTrait.
40
41        We keep track of the StatusTrait to ensure we have the latest
42        status information when dealing with maps.
43        """
44        super().__init__()
45        self._status_trait = status_trait
46
47    @property
48    def current_map(self) -> int | None:
49        """Returns the currently active map (map_flag), if available."""
50        return self._status_trait.current_map
51
52    @property
53    def current_map_info(self) -> MultiMapsListMapInfo | None:
54        """Returns the currently active map info, if available."""
55        if (current_map := self.current_map) is None or self.map_info is None:
56            return None
57        for map_info in self.map_info:
58            if map_info.map_flag == current_map:
59                return map_info
60        return None
61
62    async def set_current_map(self, map_flag: int) -> None:
63        """Update the current map of the device by it's map_flag id."""
64        await self.rpc_channel.send_command(RoborockCommand.LOAD_MULTI_MAP, params=[map_flag])
65        # Refresh our status to make sure it reflects the new map
66        await self._status_trait.refresh()
67
68    def _parse_response(self, response: common.V1ResponseData) -> Self:
69        """Parse the response from the device into a MapsTrait instance.
70
71        This overrides the base implementation to handle the specific
72        response format for the multi maps list. This is needed because we have
73        a custom constructor that requires the StatusTrait.
74        """
75        if not isinstance(response, list):
76            raise ValueError(f"Unexpected MapsTrait response format: {response!r}")
77        response = response[0]
78        if not isinstance(response, dict):
79            raise ValueError(f"Unexpected MapsTrait response format: {response!r}")
80        return MultiMapsList.from_dict(response)