From 913ca74ac2a0b664cf3c437f9f01f6b59348d468 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 16 Oct 2025 00:44:12 +0000 Subject: [PATCH 1/8] Raise an issue when the local api is unavailable --- .../components/roborock/coordinator.py | 20 +++++++++++ .../components/roborock/strings.json | 7 ++++ tests/components/roborock/test_coordinator.py | 35 +++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/homeassistant/components/roborock/coordinator.py b/homeassistant/components/roborock/coordinator.py index e36208dfee1104..611fe52072cf8c 100644 --- a/homeassistant/components/roborock/coordinator.py +++ b/homeassistant/components/roborock/coordinator.py @@ -38,6 +38,11 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.issue_registry import ( + IssueSeverity, + async_create_issue, + async_delete_issue, +) from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt as dt_util, slugify @@ -274,6 +279,9 @@ async def _verify_api(self) -> None: try: await self.api.async_connect() await self.api.ping() + async_delete_issue( + self.hass, DOMAIN, f"cloud_api_used_{self.duid_slug}" + ) except RoborockException: _LOGGER.warning( "Using the cloud API for device %s. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance", @@ -284,6 +292,18 @@ async def _verify_api(self) -> None: self.api = self.cloud_api self.update_interval = V1_CLOUD_NOT_CLEANING_INTERVAL self._is_cloud_api = True + async_create_issue( + self.hass, + DOMAIN, + f"cloud_api_used_{self.duid_slug}", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="cloud_api_used", + translation_placeholders={ + "device_name": self.roborock_device_info.device.name + }, + ) + # Right now this should never be called if the cloud api is the primary api, # but in the future if it is, a new else should be added. diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index ae8cb682c417a9..3568ac2af9111e 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -39,6 +39,13 @@ "wrong_account": "Wrong account: Please authenticate with the right account." } }, + "issues": { + "cloud_api_used": { + "title": "Cloud API Used", + "description": "The Roborock integration is using the cloud API for {device_name}. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance." + } + }, + "options": { "step": { "drawables": { diff --git a/tests/components/roborock/test_coordinator.py b/tests/components/roborock/test_coordinator.py index 315ab14bdb5042..30344b93622870 100644 --- a/tests/components/roborock/test_coordinator.py +++ b/tests/components/roborock/test_coordinator.py @@ -19,6 +19,7 @@ from homeassistant.components.roborock.coordinator import RoborockDataUpdateCoordinator from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir from homeassistant.util import dt as dt_util from .mock_data import PROP @@ -163,3 +164,37 @@ async def test_no_maps( ): await hass.config_entries.async_setup(mock_roborock_entry.entry_id) assert load_map.call_count == 0 + + +async def test_cloud_api_repair( + hass: HomeAssistant, + mock_roborock_entry: MockConfigEntry, + bypass_api_fixture_v1_only, +) -> None: + """Test that a repair is created when we use the cloud api.""" + # Force the system to use the cloud api. + with patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClientV1.ping", + side_effect=RoborockException(), + ): + await hass.config_entries.async_setup(mock_roborock_entry.entry_id) + await hass.async_block_till_done() + + issue_registry = ir.async_get(hass) + assert len(issue_registry.issues) == 2 + issue = list(issue_registry.issues.values())[0] + assert issue.translation_key == "cloud_api_used" + assert issue.translation_placeholders == {"device_name": "Roborock S7 MaxV"} + issue = list(issue_registry.issues.values())[1] + assert issue.translation_key == "cloud_api_used" + assert issue.translation_placeholders == {"device_name": "Roborock S7 2"} + await hass.config_entries.async_unload(mock_roborock_entry.entry_id) + # Now change to using the local api + with patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClientV1.ping" + ): + # Set it back up + await hass.config_entries.async_setup(mock_roborock_entry.entry_id) + await hass.async_block_till_done() + + assert len(issue_registry.issues) == 0 From 382fdffeaa151c826b4b099b97f4a1f7fa92de53 Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Thu, 16 Oct 2025 09:55:08 -0400 Subject: [PATCH 2/8] Update homeassistant/components/roborock/strings.json Co-authored-by: Josef Zweck --- homeassistant/components/roborock/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index 3568ac2af9111e..84c1748b6fd63c 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -42,7 +42,7 @@ "issues": { "cloud_api_used": { "title": "Cloud API Used", - "description": "The Roborock integration is using the cloud API for {device_name}. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance." + "description": "The Roborock integration is unable to connect directly to {device_name} and falling back to the cloud API. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance." } }, From 5cd17360fb08ed9d8d729b21d06e1f3df96c36f2 Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Thu, 16 Oct 2025 09:56:19 -0400 Subject: [PATCH 3/8] Update tests/components/roborock/test_coordinator.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/components/roborock/test_coordinator.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/components/roborock/test_coordinator.py b/tests/components/roborock/test_coordinator.py index 30344b93622870..517190e402eaf6 100644 --- a/tests/components/roborock/test_coordinator.py +++ b/tests/components/roborock/test_coordinator.py @@ -182,12 +182,10 @@ async def test_cloud_api_repair( issue_registry = ir.async_get(hass) assert len(issue_registry.issues) == 2 - issue = list(issue_registry.issues.values())[0] - assert issue.translation_key == "cloud_api_used" - assert issue.translation_placeholders == {"device_name": "Roborock S7 MaxV"} - issue = list(issue_registry.issues.values())[1] - assert issue.translation_key == "cloud_api_used" - assert issue.translation_placeholders == {"device_name": "Roborock S7 2"} + # Check that both expected device names are present, regardless of order + assert all(issue.translation_key == "cloud_api_used" for issue in issue_registry.issues.values()) + names = {issue.translation_placeholders["device_name"] for issue in issue_registry.issues.values()} + assert names == {"Roborock S7 MaxV", "Roborock S7 2"} await hass.config_entries.async_unload(mock_roborock_entry.entry_id) # Now change to using the local api with patch( From e94d30e0242058d1e4a8563c5c8b3093f210755b Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Thu, 16 Oct 2025 10:00:51 -0400 Subject: [PATCH 4/8] Update homeassistant/components/roborock/strings.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- homeassistant/components/roborock/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index 84c1748b6fd63c..0b1b580a43b228 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -41,7 +41,7 @@ }, "issues": { "cloud_api_used": { - "title": "Cloud API Used", + "title": "Cloud API used", "description": "The Roborock integration is unable to connect directly to {device_name} and falling back to the cloud API. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance." } }, From 85aca2531d7f43870627abae24b9e8ae168e1296 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 18 Oct 2025 00:55:34 +0000 Subject: [PATCH 5/8] fix linting --- tests/components/roborock/test_coordinator.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/components/roborock/test_coordinator.py b/tests/components/roborock/test_coordinator.py index 517190e402eaf6..ed6d9df1960091 100644 --- a/tests/components/roborock/test_coordinator.py +++ b/tests/components/roborock/test_coordinator.py @@ -183,8 +183,14 @@ async def test_cloud_api_repair( issue_registry = ir.async_get(hass) assert len(issue_registry.issues) == 2 # Check that both expected device names are present, regardless of order - assert all(issue.translation_key == "cloud_api_used" for issue in issue_registry.issues.values()) - names = {issue.translation_placeholders["device_name"] for issue in issue_registry.issues.values()} + assert all( + issue.translation_key == "cloud_api_used" + for issue in issue_registry.issues.values() + ) + names = { + issue.translation_placeholders["device_name"] + for issue in issue_registry.issues.values() + } assert names == {"Roborock S7 MaxV", "Roborock S7 2"} await hass.config_entries.async_unload(mock_roborock_entry.entry_id) # Now change to using the local api From 8c1ef2aeb34025c15fc0f7f14b42493ad34d163b Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Sun, 19 Oct 2025 22:05:47 -0400 Subject: [PATCH 6/8] Update homeassistant/components/roborock/strings.json Co-authored-by: Allen Porter --- homeassistant/components/roborock/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index 0b1b580a43b228..b6276952aba762 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -42,7 +42,7 @@ "issues": { "cloud_api_used": { "title": "Cloud API used", - "description": "The Roborock integration is unable to connect directly to {device_name} and falling back to the cloud API. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance." + "description": "The Roborock integration is unable to connect directly to {device_name} and falling back to the cloud API. This is not recommended as it can lead to rate limiting. Please make your vacuum accessible on the local network by your Home Assistant instance." } }, From 03b32dd9a3d0cab55a697aba471e354109160ccc Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 26 Oct 2025 00:48:24 +0000 Subject: [PATCH 7/8] Add learn more URL --- homeassistant/components/roborock/coordinator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/roborock/coordinator.py b/homeassistant/components/roborock/coordinator.py index b75f54ebc4decd..d93cf779ad9392 100644 --- a/homeassistant/components/roborock/coordinator.py +++ b/homeassistant/components/roborock/coordinator.py @@ -302,6 +302,7 @@ async def _verify_api(self) -> None: translation_placeholders={ "device_name": self.roborock_device_info.device.name }, + learn_more_url="https://www.home-assistant.io/integrations/roborock/#the-integration-tells-me-it-cannot-reach-my-vacuum-and-is-using-the-cloud-api-and-that-this-is-not-supported-or-i-am-having-any-networking-issues", ) # Right now this should never be called if the cloud api is the primary api, From d02bdd1de94d004fd6210ad0c8dc02c25328d51f Mon Sep 17 00:00:00 2001 From: Luke Date: Sun, 26 Oct 2025 00:49:04 +0000 Subject: [PATCH 8/8] linting --- tests/components/roborock/test_coordinator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/components/roborock/test_coordinator.py b/tests/components/roborock/test_coordinator.py index b4419c4bae9e24..77b87752dc6b71 100644 --- a/tests/components/roborock/test_coordinator.py +++ b/tests/components/roborock/test_coordinator.py @@ -208,6 +208,8 @@ async def test_cloud_api_repair( await hass.async_block_till_done() assert len(issue_registry.issues) == 0 + + async def test_two_maps_in_cleaning( hass: HomeAssistant, mock_roborock_entry: MockConfigEntry,