"""Support for Roborock image.""" from datetime import datetime import logging from typing import override from roborock.devices.traits.v1.home import HomeTrait from roborock.devices.traits.v1.map_content import MapContent from homeassistant.components.image import ImageEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .coordinator import ( RoborockConfigEntry, RoborockCoordinatorType, RoborockDataUpdateCoordinator, ) from .entity import RoborockCoordinatedEntityV1 _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 0 async def async_setup_entry( hass: HomeAssistant, config_entry: RoborockConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up Roborock image platform.""" coordinators = config_entry.runtime_data @callback def async_add_coordinator_entities( coordinator: RoborockCoordinatorType, ) -> None: """Add entities for a specific coordinator.""" if not isinstance(coordinator, RoborockDataUpdateCoordinator): return entities = [ RoborockMap( config_entry, coordinator, coordinator.properties_api.home, map_info.map_flag, map_info.name, ) for map_info in ( coordinator.properties_api.home.home_map_info or {} ).values() ] async_add_entities(entities) for coordinator in coordinators.values(): async_add_coordinator_entities(coordinator) config_entry.async_on_unload( async_dispatcher_connect( hass, f"roborock_coordinator_added_{config_entry.entry_id}", async_add_coordinator_entities, ) ) class RoborockMap(RoborockCoordinatedEntityV1, ImageEntity): """A class to let you visualize the map.""" _attr_has_entity_name = True image_last_updated: datetime _attr_name: str def __init__( self, config_entry: ConfigEntry, coordinator: RoborockDataUpdateCoordinator, home_trait: HomeTrait, map_flag: int, map_name: str, ) -> None: """Initialize a Roborock map.""" map_name = map_name or f"Map {map_flag}" # Note: Map names are not a valid unique id since they can be changed # in the roborock app. This should be migrated to use map flag for # the unique id. unique_id = f"{coordinator.duid_slug}_map_{map_name}" RoborockCoordinatedEntityV1.__init__(self, unique_id, coordinator) ImageEntity.__init__(self, coordinator.hass) self.config_entry = config_entry self._attr_name = map_name self._home_trait = home_trait self.map_flag = map_flag self.cached_map: bytes | None = None self._attr_entity_category = EntityCategory.DIAGNOSTIC self._attr_image_last_updated = None @override async def async_added_to_hass(self) -> None: """When entity is added to hass load any previously cached maps from disk.""" await super().async_added_to_hass() self._attr_image_last_updated = self.coordinator.last_home_update self.async_write_ha_state() @property def _map_content(self) -> MapContent | None: if self._home_trait.home_map_content and ( map_content := self._home_trait.home_map_content.get(self.map_flag) ): return map_content return None @callback @override def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator. If the coordinator has updated the map, we can update the image. """ if self.coordinator.data is None or (map_content := self._map_content) is None: return if self.cached_map != map_content.image_content: self.cached_map = map_content.image_content self._attr_image_last_updated = self.coordinator.last_home_update super()._handle_coordinator_update() @override async def async_image(self) -> bytes | None: """Get the cached image.""" if (map_content := self._map_content) is None: raise HomeAssistantError("Map flag not found in coordinator maps") return map_content.image_content