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
1 change: 1 addition & 0 deletions homeassistant/components/myuplink/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"config_flow": true,
"dependencies": ["application_credentials"],
"documentation": "https://www.home-assistant.io/integrations/myuplink",
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "silver",
"requirements": ["myuplink==0.7.0"]
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/portainer/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/portainer",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["pyportainer==1.0.3"]
"requirements": ["pyportainer==1.0.4"]
}
41 changes: 35 additions & 6 deletions homeassistant/components/roborock/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,14 +418,36 @@ async def refresh_coordinator_map(self) -> None:
# If we don't have a cur map(shouldn't happen) just
# return as we can't do anything.
return
map_flags = sorted(self.maps, key=lambda data: data == cur_map, reverse=True)
if self.data.status.in_cleaning:
# If the vacuum is cleaning, we cannot change maps
# as it will interrupt the cleaning.
_LOGGER.info(
"Vacuum is cleaning, not switching to other maps to fetch rooms"
)
# Since this is hitting the cloud api, we want to be careful and will just
# stop here rather than retrying in the future.
map_flags = [cur_map]
else:
map_flags = sorted(
self.maps, key=lambda data: data == cur_map, reverse=True
)
for map_flag in map_flags:
if map_flag != cur_map:
# Only change the map and sleep if we have multiple maps.
await self.cloud_api.load_multi_map(map_flag)
self.current_map = map_flag
try:
await self.cloud_api.load_multi_map(map_flag)
except RoborockException as ex:
_LOGGER.debug(
"Failed to change to map %s when refreshing maps: %s",
map_flag,
ex,
)
continue
else:
self.current_map = map_flag
# We cannot get the map until the roborock servers fully process the
# map change.
# map change. If the above command fails, we should still sleep, just
# in case it executes delayed.
await asyncio.sleep(MAP_SLEEP)
tasks = [self.set_current_map_rooms()]
# The image is set within async_setup, so if it exists, we have it here.
Expand All @@ -436,11 +458,18 @@ async def refresh_coordinator_map(self) -> None:
# If either of these fail, we don't care, and we want to continue.
await asyncio.gather(*tasks, return_exceptions=True)

if len(self.maps) > 1:
if len(self.maps) > 1 and not self.data.status.in_cleaning:
# Set the map back to the map the user previously had selected so that it
# does not change the end user's app.
# Only needs to happen when we changed maps above.
await self.cloud_api.load_multi_map(cur_map)
try:
await self.cloud_api.load_multi_map(cur_map)
except RoborockException as ex:
_LOGGER.warning(
"Failed to change back to map %s when refreshing maps: %s",
cur_map,
ex,
)
self.current_map = cur_map


Expand Down
9 changes: 8 additions & 1 deletion homeassistant/components/thethingsnetwork/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ async def async_step_user(
),
user_input,
)
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
return self.async_show_form(
step_id="user",
data_schema=schema,
errors=errors,
description_placeholders={
"instructions_url": "https://www.thethingsindustries.com/docs/integrations/adding-applications/"
},
)

async def async_step_reauth(
self, entry_data: Mapping[str, Any]
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/thethingsnetwork/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"step": {
"user": {
"title": "Connect to The Things Network v3",
"description": "Enter the API hostname, application ID and API key to use with Home Assistant.\n\n[Read the instructions](https://www.thethingsindustries.com/docs/integrations/adding-applications/) on how to register your application and create an API key.",
"description": "Enter the API hostname, application ID and API key to use with Home Assistant.\n\n[Read the instructions]({instructions_url}) on how to register your application and create an API key.",
"data": {
"host": "[%key:common::config_flow::data::host%]",
"app_id": "Application ID",
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

116 changes: 114 additions & 2 deletions tests/components/roborock/test_coordinator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test Roborock Coordinator specific logic."""

import asyncio
import copy
from datetime import timedelta
from unittest.mock import patch
Expand All @@ -11,13 +12,15 @@

from homeassistant.components.roborock.const import (
CONF_SHOW_BACKGROUND,
DOMAIN,
GET_MAPS_SERVICE_NAME,
V1_CLOUD_IN_CLEANING_INTERVAL,
V1_CLOUD_NOT_CLEANING_INTERVAL,
V1_LOCAL_IN_CLEANING_INTERVAL,
V1_LOCAL_NOT_CLEANING_INTERVAL,
)
from homeassistant.components.roborock.coordinator import RoborockDataUpdateCoordinator
from homeassistant.const import Platform
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util

Expand All @@ -29,7 +32,7 @@
@pytest.fixture
def platforms() -> list[Platform]:
"""Fixture to set platforms used in the test."""
return [Platform.SENSOR]
return [Platform.SENSOR, Platform.VACUUM]


@pytest.mark.parametrize(
Expand Down Expand Up @@ -166,3 +169,112 @@ 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_two_maps_in_cleaning(
hass: HomeAssistant,
mock_roborock_entry: MockConfigEntry,
bypass_api_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that we gracefully handle having two maps but we are in cleaning."""
prop = copy.deepcopy(PROP)
prop.status.in_cleaning = True
with (
patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClientV1.get_prop",
return_value=prop,
),
patch(
"homeassistant.components.roborock.RoborockMqttClientV1.load_multi_map"
) as load_map,
):
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
# We should not try to load any maps as we should just get the information for our
# current map and move on.
assert load_map.call_count == 0
assert (
"Vacuum is cleaning, not switching to other maps to fetch rooms" in caplog.text
)


async def test_failed_load_multi_map(
hass: HomeAssistant,
mock_roborock_entry: MockConfigEntry,
bypass_api_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that we gracefully handle one map failing to load."""
with (
patch(
"homeassistant.components.roborock.RoborockMqttClientV1.load_multi_map",
side_effect=[RoborockException(), None, None, None],
) as load_map,
):
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
assert "Failed to change to map 1 when refreshing maps" in caplog.text
# We continue to try and load the next map so we we should have multiple load maps.
# 2 for both devices, even though one for one of the devices failed.
assert load_map.call_count == 4
# Just to be safe since we load the maps asynchronously, lets make sure that only
# one map out of the four didn't get called.
responses = await asyncio.gather(
*(
hass.services.async_call(
DOMAIN,
GET_MAPS_SERVICE_NAME,
{ATTR_ENTITY_ID: dev},
blocking=True,
return_response=True,
)
for dev in ("vacuum.roborock_s7_maxv", "vacuum.roborock_s7_2")
)
)
num_no_rooms = sum(
1
for res in responses
for data in res.values()
for m in data["maps"]
if not m["rooms"]
)
assert num_no_rooms == 1


async def test_failed_reset_map(
hass: HomeAssistant,
mock_roborock_entry: MockConfigEntry,
bypass_api_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that we gracefully handle not being able to revert back to the original map."""
with (
patch(
"homeassistant.components.roborock.RoborockMqttClientV1.load_multi_map",
side_effect=[None, None, None, RoborockException()],
) as load_map,
):
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
assert "Failed to change back to map 0 when refreshing maps" in caplog.text
# 2 for both devices, even though one for one of the devices failed.
assert load_map.call_count == 4
responses = await asyncio.gather(
*(
hass.services.async_call(
DOMAIN,
GET_MAPS_SERVICE_NAME,
{ATTR_ENTITY_ID: dev},
blocking=True,
return_response=True,
)
for dev in ("vacuum.roborock_s7_maxv", "vacuum.roborock_s7_2")
)
)
num_no_rooms = sum(
1
for res in responses
for data in res.values()
for m in data["maps"]
if not m["rooms"]
)
# No maps should be missing information, as we just couldn't go back to the original.
assert num_no_rooms == 0
Loading