Update Roborock Map on status change (#140873)

* update map on status change

* Update tests/components/roborock/test_image.py

Co-authored-by: Allen Porter <allen.porter@gmail.com>

* update code to handle state logic within async_update_data

* Update homeassistant/components/roborock/coordinator.py

Co-authored-by: Allen Porter <allen.porter@gmail.com>

* move previous_state and allow update on None

---------

Co-authored-by: Allen Porter <allen.porter@gmail.com>
This commit is contained in:
Luke Lashley 2025-03-18 10:34:02 -04:00 committed by GitHub
parent de1823070f
commit 1cae866da9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 59 additions and 10 deletions

View File

@ -279,6 +279,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
async def _async_update_data(self) -> DeviceProp:
"""Update data via library."""
previous_state = self.roborock_device_info.props.status.state_name
try:
# Update device props and standard api information
await self._update_device_prop()
@ -288,11 +289,14 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
# If the vacuum is currently cleaning and it has been IMAGE_CACHE_INTERVAL
# since the last map update, you can update the map.
if (
self.current_map is not None
and self.roborock_device_info.props.status.in_cleaning
and (dt_util.utcnow() - self.maps[self.current_map].last_updated)
> IMAGE_CACHE_INTERVAL
new_status = self.roborock_device_info.props.status
if self.current_map is not None and (
(
new_status.in_cleaning
and (dt_util.utcnow() - self.maps[self.current_map].last_updated)
> IMAGE_CACHE_INTERVAL
)
or previous_state != new_status.state_name
):
try:
await self.update_map()

View File

@ -11,6 +11,7 @@ from roborock import RoborockException
from vacuum_map_parser_base.map_data import ImageConfig, ImageData
from homeassistant.components.roborock import DOMAIN
from homeassistant.components.roborock.const import V1_LOCAL_NOT_CLEANING_INTERVAL
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
@ -158,9 +159,6 @@ async def test_fail_to_load_image(
) -> None:
"""Test that we gracefully handle failing to load an image."""
with (
patch(
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
) as parse_map,
patch(
"homeassistant.components.roborock.roborock_storage.Path.exists",
return_value=True,
@ -177,8 +175,6 @@ async def test_fail_to_load_image(
await hass.config_entries.async_reload(setup_entry.entry_id)
await hass.async_block_till_done()
assert read_bytes.call_count == 4
# Ensure that we never updated the map manually since we couldn't load it.
assert parse_map.call_count == 0
assert "Unable to read map file" in caplog.text
@ -298,3 +294,52 @@ async def test_index_error_map(
# last_updated timestamp.
assert resp.ok
assert previous_state == hass.states.get("image.roborock_s7_maxv_upstairs").state
async def test_map_status_change(
hass: HomeAssistant,
setup_entry: MockConfigEntry,
hass_client: ClientSessionGenerator,
) -> None:
"""Test floor plan map image is correctly updated on status change."""
assert len(hass.states.async_all("image")) == 4
assert hass.states.get("image.roborock_s7_maxv_upstairs") is not None
client = await hass_client()
resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs")
assert resp.status == HTTPStatus.OK
old_body = await resp.read()
assert old_body[0:4] == b"\x89PNG"
# Call a second time. This interval does not directly trigger a map update, but does
# trigger a status update which detects the state has changed and uddates the map
now = dt_util.utcnow() + V1_LOCAL_NOT_CLEANING_INTERVAL
# Copy the device prop so we don't override it
prop = copy.deepcopy(PROP)
prop.status.state_name = "testing"
new_map_data = copy.deepcopy(MAP_DATA)
new_map_data.image = ImageData(
100, 10, 10, 10, 10, ImageConfig(), Image.new("RGB", (2, 2)), lambda p: p
)
with (
patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.get_prop",
return_value=prop,
),
patch(
"homeassistant.components.roborock.coordinator.dt_util.utcnow",
return_value=now,
),
patch(
"homeassistant.components.roborock.coordinator.RoborockMapDataParser.parse",
return_value=new_map_data,
),
):
async_fire_time_changed(hass, now)
resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs")
assert resp.status == HTTPStatus.OK
assert resp.status == HTTPStatus.OK
body = await resp.read()
assert body is not None
assert body != old_body