diff --git a/homeassistant/components/roborock/binary_sensor.py b/homeassistant/components/roborock/binary_sensor.py index 114656a6d17..b79128e809c 100644 --- a/homeassistant/components/roborock/binary_sensor.py +++ b/homeassistant/components/roborock/binary_sensor.py @@ -159,7 +159,11 @@ async def async_setup_entry( ) for coordinator in config_entry.runtime_data.v1 for description in BINARY_SENSOR_DESCRIPTIONS - if description.value_fn(coordinator.data) is not None + # Note: Currently coordinator.data is always available on startup but won't be in the future + if ( + coordinator.data is not None + and description.value_fn(coordinator.data) is not None + ) ] entities.extend( RoborockBinarySensorEntityA01( @@ -193,9 +197,11 @@ class RoborockBinarySensorEntity(RoborockCoordinatedEntityV1, BinarySensorEntity self.entity_description = description @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return the value reported by the sensor.""" - return bool(self.entity_description.value_fn(self.coordinator.data)) + if (data := self.coordinator.data) is not None: + return bool(self.entity_description.value_fn(data)) + return None class RoborockBinarySensorEntityA01(RoborockCoordinatedEntityA01, BinarySensorEntity): diff --git a/homeassistant/components/roborock/coordinator.py b/homeassistant/components/roborock/coordinator.py index f35ea036609..146ba965365 100644 --- a/homeassistant/components/roborock/coordinator.py +++ b/homeassistant/components/roborock/coordinator.py @@ -83,7 +83,7 @@ class RoborockCoordinators: type RoborockConfigEntry = ConfigEntry[RoborockCoordinators] -class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceState]): +class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceState | None]): """Class to manage fetching data from the API.""" config_entry: RoborockConfigEntry @@ -229,7 +229,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceState]): ) _LOGGER.debug("Updated device properties") - async def _async_update_data(self) -> DeviceState: + async def _async_update_data(self) -> DeviceState | None: """Update data via library.""" await self._verify_api() try: diff --git a/homeassistant/components/roborock/entity.py b/homeassistant/components/roborock/entity.py index 24d5904b97c..ad0a7d4e7e0 100644 --- a/homeassistant/components/roborock/entity.py +++ b/homeassistant/components/roborock/entity.py @@ -3,7 +3,6 @@ from typing import Any from roborock.devices.traits.v1.command import CommandTrait -from roborock.devices.traits.v1.status import StatusTrait from roborock.exceptions import RoborockException from roborock.roborock_typing import RoborockCommand @@ -94,12 +93,6 @@ class RoborockCoordinatedEntityV1( CoordinatorEntity.__init__(self, coordinator=coordinator) self._attr_unique_id = unique_id - @property - def _device_status(self) -> StatusTrait: - """Return the status of the device.""" - data = self.coordinator.data - return data.status - async def send( self, command: RoborockCommand | str, diff --git a/homeassistant/components/roborock/image.py b/homeassistant/components/roborock/image.py index c03c49dabfc..b6f2169456b 100644 --- a/homeassistant/components/roborock/image.py +++ b/homeassistant/components/roborock/image.py @@ -9,7 +9,7 @@ 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 +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -73,6 +73,7 @@ class RoborockMap(RoborockCoordinatedEntityV1, ImageEntity): self.map_flag = map_flag self.cached_map: bytes | None = None self._attr_entity_category = EntityCategory.DIAGNOSTIC + self._attr_image_last_updated = None async def async_added_to_hass(self) -> None: """When entity is added to hass load any previously cached maps from disk.""" @@ -88,17 +89,17 @@ class RoborockMap(RoborockCoordinatedEntityV1, ImageEntity): return map_content return None + @callback 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 (map_content := self._map_content) is None: + 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() async def async_image(self) -> bytes | None: diff --git a/homeassistant/components/roborock/sensor.py b/homeassistant/components/roborock/sensor.py index cbe634dce9b..48f5407f86c 100644 --- a/homeassistant/components/roborock/sensor.py +++ b/homeassistant/components/roborock/sensor.py @@ -427,7 +427,11 @@ async def async_setup_entry( ) for coordinator in coordinators.v1 for description in SENSOR_DESCRIPTIONS - if description.value_fn(coordinator.data) is not None + # Note: Currently coordinator.data is always available on startup but won't be in the future + if ( + coordinator.data is not None + and description.value_fn(coordinator.data) is not None + ) ] entities.extend(RoborockCurrentRoom(coordinator) for coordinator in coordinators.v1) entities.extend( @@ -480,6 +484,8 @@ class RoborockSensorEntity(RoborockCoordinatedEntityV1, SensorEntity): @property def native_value(self) -> StateType | datetime.datetime: """Return the value reported by the sensor.""" + if self.coordinator.data is None: + return None return self.entity_description.value_fn(self.coordinator.data) diff --git a/homeassistant/components/roborock/vacuum.py b/homeassistant/components/roborock/vacuum.py index fb55d81d589..623644379a9 100644 --- a/homeassistant/components/roborock/vacuum.py +++ b/homeassistant/components/roborock/vacuum.py @@ -150,6 +150,7 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity): coordinator.duid_slug, coordinator, ) + self._status_trait = coordinator.properties_api.status self._home_trait = coordinator.properties_api.home self._maps_trait = coordinator.properties_api.maps @@ -176,31 +177,37 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity): @property def fan_speed_list(self) -> list[str]: """Get the list of available fan speeds.""" - return [mode.value for mode in self._device_status.fan_speed_options] + if self.coordinator.data is None: + return [] + return [mode.value for mode in self._status_trait.fan_speed_options] @property def activity(self) -> VacuumActivity | None: """Return the status of the vacuum cleaner.""" - assert self._device_status.state is not None - return STATE_CODE_TO_STATE.get(self._device_status.state) + if self.coordinator.data is None or self._status_trait.state is None: + return None + return STATE_CODE_TO_STATE.get(self._status_trait.state) @property def fan_speed(self) -> str | None: """Return the fan speed of the vacuum cleaner.""" - return self._device_status.fan_speed_name + if self.coordinator.data is None: + return None + return self._status_trait.fan_speed_name async def async_start(self) -> None: """Start the vacuum.""" - if self._device_status.in_returning == 1: - await self.send(RoborockCommand.APP_CHARGE) - elif self._device_status.in_cleaning == 2: - await self.send(RoborockCommand.RESUME_ZONED_CLEAN) - elif self._device_status.in_cleaning == 3: - await self.send(RoborockCommand.RESUME_SEGMENT_CLEAN) - elif self._device_status.in_cleaning == 4: - await self.send(RoborockCommand.APP_RESUME_BUILD_MAP) - else: - await self.send(RoborockCommand.APP_START) + command = RoborockCommand.APP_START + if self.coordinator.data is not None: + if self._status_trait.in_returning == 1: + command = RoborockCommand.APP_CHARGE + elif self._status_trait.in_cleaning == 2: + command = RoborockCommand.RESUME_ZONED_CLEAN + elif self._status_trait.in_cleaning == 3: + command = RoborockCommand.RESUME_SEGMENT_CLEAN + elif self._status_trait.in_cleaning == 4: + command = RoborockCommand.APP_RESUME_BUILD_MAP + await self.send(command) async def async_pause(self) -> None: """Pause the vacuum.""" @@ -224,10 +231,15 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity): async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: """Set vacuum fan speed.""" + if self.coordinator.data is None: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="update_options_failed", + ) await self.send( RoborockCommand.SET_CUSTOM_MODE, [ - {v: k for k, v in self._device_status.fan_speed_mapping.items()}[ + {v: k for k, v in self._status_trait.fan_speed_mapping.items()}[ fan_speed ] ],