diff --git a/homeassistant/components/homeassistant_sky_connect/__init__.py b/homeassistant/components/homeassistant_sky_connect/__init__.py index 08d54bdef12..4315862e523 100644 --- a/homeassistant/components/homeassistant_sky_connect/__init__.py +++ b/homeassistant/components/homeassistant_sky_connect/__init__.py @@ -15,7 +15,7 @@ from homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon get_addon_manager, get_zigbee_socket, ) -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigEntryDisabler from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady @@ -75,7 +75,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not usb.async_is_plugged_in(hass, matcher): # The USB dongle is not plugged in - raise ConfigEntryNotReady + hass.async_create_task( + hass.config_entries.async_set_disabled_by( + entry.entry_id, ConfigEntryDisabler.INTEGRATION + ) + ) + return False addon_info = await _multi_pan_addon_info(hass, entry) diff --git a/homeassistant/components/homeassistant_sky_connect/config_flow.py b/homeassistant/components/homeassistant_sky_connect/config_flow.py index 1e4fd8701cd..076a093e5b0 100644 --- a/homeassistant/components/homeassistant_sky_connect/config_flow.py +++ b/homeassistant/components/homeassistant_sky_connect/config_flow.py @@ -5,7 +5,7 @@ from typing import Any from homeassistant.components import usb from homeassistant.components.homeassistant_hardware import silabs_multiprotocol_addon -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigEntryDisabler, ConfigFlow from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult @@ -35,7 +35,12 @@ class HomeAssistantSkyConnectConfigFlow(ConfigFlow, domain=DOMAIN): manufacturer = discovery_info.manufacturer description = discovery_info.description unique_id = f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}" - if await self.async_set_unique_id(unique_id): + if existing_entry := await self.async_set_unique_id(unique_id): + # Re-enable existing config entry which was disabled by USB unplug + if existing_entry.disabled_by == ConfigEntryDisabler.INTEGRATION: + await self.hass.config_entries.async_set_disabled_by( + existing_entry.entry_id, None + ) self._abort_if_unique_id_configured(updates={"device": device}) return self.async_create_entry( title="Home Assistant Sky Connect", diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index f2e2be97e42..2907b6a966f 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -163,6 +163,7 @@ class ConfigEntryChange(StrEnum): class ConfigEntryDisabler(StrEnum): """What disabled a config entry.""" + INTEGRATION = "integration" USER = "user" diff --git a/tests/components/homeassistant_sky_connect/test_init.py b/tests/components/homeassistant_sky_connect/test_init.py index ebf1c74d9e0..45da1f532a2 100644 --- a/tests/components/homeassistant_sky_connect/test_init.py +++ b/tests/components/homeassistant_sky_connect/test_init.py @@ -5,11 +5,12 @@ from unittest.mock import MagicMock, Mock, patch import pytest -from homeassistant.components import zha +from homeassistant.components import usb, zha from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.components.homeassistant_sky_connect.const import DOMAIN -from homeassistant.config_entries import ConfigEntryState +from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry @@ -244,14 +245,22 @@ async def test_setup_zha_multipan_other_device( assert config_entry.title == CONFIG_ENTRY_DATA["description"] -async def test_setup_entry_wait_usb(hass: HomeAssistant) -> None: +async def test_setup_entry_wait_usb( + mock_zha_config_flow_setup, hass: HomeAssistant +) -> None: """Test setup of a config entry when the dongle is not plugged in.""" # Setup the config entry + vid = CONFIG_ENTRY_DATA["vid"] + pid = CONFIG_ENTRY_DATA["device"] + serial_number = CONFIG_ENTRY_DATA["serial_number"] + manufacturer = CONFIG_ENTRY_DATA["manufacturer"] + description = CONFIG_ENTRY_DATA["description"] config_entry = MockConfigEntry( data=CONFIG_ENTRY_DATA, domain=DOMAIN, options={}, title="Home Assistant Sky Connect", + unique_id=f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}", ) config_entry.add_to_hass(hass) with patch( @@ -261,7 +270,31 @@ async def test_setup_entry_wait_usb(hass: HomeAssistant) -> None: assert not await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert len(mock_is_plugged_in.mock_calls) == 1 - assert config_entry.state == ConfigEntryState.SETUP_RETRY + assert config_entry.disabled_by == ConfigEntryDisabler.INTEGRATION + assert config_entry.state == ConfigEntryState.NOT_LOADED + + # USB dongle plugged in + usb_data = usb.UsbServiceInfo( + device=CONFIG_ENTRY_DATA["device"], + vid=CONFIG_ENTRY_DATA["vid"], + pid=CONFIG_ENTRY_DATA["device"], + serial_number=CONFIG_ENTRY_DATA["serial_number"], + manufacturer=CONFIG_ENTRY_DATA["manufacturer"], + description=CONFIG_ENTRY_DATA["description"], + ) + with patch( + "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", + return_value=True, + ) as mock_is_plugged_in, patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=True + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "usb"}, data=usb_data + ) + assert result["type"] == FlowResultType.ABORT + + assert config_entry.disabled_by is None + assert config_entry.state == ConfigEntryState.LOADED async def test_setup_entry_addon_info_fails(