Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions homeassistant/components/roborock/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -284,6 +292,19 @@ 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
},
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,
# but in the future if it is, a new else should be added.

Expand Down
7 changes: 7 additions & 0 deletions homeassistant/components/roborock/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 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."
}
},

"options": {
"step": {
"drawables": {
Expand Down
39 changes: 39 additions & 0 deletions tests/components/roborock/test_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from homeassistant.components.roborock.coordinator import RoborockDataUpdateCoordinator
from homeassistant.const import ATTR_ENTITY_ID, 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
Expand Down Expand Up @@ -171,6 +172,44 @@ async def test_no_maps(
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(),
):
Comment on lines +182 to +185

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RoborockLocalClientV1.ping is awaited in the coordinator; patching it without an AsyncMock can cause await errors. Use an AsyncMock for the side effect and import AsyncMock: with patch('homeassistant.components.roborock.coordinator.RoborockLocalClientV1.ping', new=AsyncMock(side_effect=RoborockException()))

Copilot uses AI. Check for mistakes.
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
# 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(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.ping"
):
Comment on lines +203 to +205

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This patch replaces an awaited coroutine with a regular MagicMock, which will raise 'object MagicMock can't be used in 'await' expression'. Patch with an AsyncMock that returns None: with patch('homeassistant.components.roborock.coordinator.RoborockLocalClientV1.ping', new=AsyncMock(return_value=None))

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these are necessary, but if a core dev wants me to I will make these changes to AsyncMock

# 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


async def test_two_maps_in_cleaning(
hass: HomeAssistant,
mock_roborock_entry: MockConfigEntry,
Expand Down
Loading