diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 523c6a67433..7d23a1cd57d 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -53,6 +53,7 @@ from .coordinator import ( ) from .utils import ( async_create_issue_unsupported_firmware, + async_shutdown_device, get_block_device_sleep_period, get_coap_context, get_device_entry_gen, @@ -339,12 +340,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: shelly_entry_data = get_entry_data(hass)[entry.entry_id] # If device is present, block/rpc coordinator is not setup yet - device = shelly_entry_data.device - if isinstance(device, RpcDevice): - await device.shutdown() - return True - if isinstance(device, BlockDevice): - device.shutdown() + if (device := shelly_entry_data.device) is not None: + await async_shutdown_device(device) return True platforms = RPC_SLEEPING_PLATFORMS diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index e155edf4f4c..b368b38820e 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -319,7 +319,7 @@ class BlockSleepingClimate( f" {repr(err)}" ) from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" @@ -436,7 +436,10 @@ class BlockSleepingClimate( ]["schedule_profile_names"], ] except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + self.hass.async_create_task( + self.coordinator.async_shutdown_device_and_start_reauth(), + eager_start=True, + ) else: self.async_write_ha_state() diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index ca1190de708..24b66e15893 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -24,7 +24,13 @@ from homeassistant.config_entries import ( ConfigFlowResult, OptionsFlow, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.const import ( + CONF_HOST, + CONF_MAC, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig @@ -84,6 +90,7 @@ async def validate_input( ip_address=host, username=data.get(CONF_USERNAME), password=data.get(CONF_PASSWORD), + device_mac=info[CONF_MAC], port=port, ) @@ -153,7 +160,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - await self.async_set_unique_id(self.info["mac"]) + await self.async_set_unique_id(self.info[CONF_MAC]) self._abort_if_unique_id_configured({CONF_HOST: host}) self.host = host self.port = port @@ -286,7 +293,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): if not mac: # We could not get the mac address from the name # so need to check here since we just got the info - await self._async_discovered_mac(self.info["mac"], host) + await self._async_discovered_mac(self.info[CONF_MAC], host) self.host = host self.context.update( diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 50c352bcb25..c52585c3363 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -58,6 +58,7 @@ from .const import ( BLEScannerMode, ) from .utils import ( + async_shutdown_device, get_device_entry_gen, get_http_port, get_rpc_device_wakeup_period, @@ -151,6 +152,14 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]): LOGGER.debug("Reloading entry %s", self.name) await self.hass.config_entries.async_reload(self.entry.entry_id) + async def async_shutdown_device_and_start_reauth(self) -> None: + """Shutdown Shelly device and start reauth flow.""" + # not running disconnect events since we have auth error + # and won't be able to send commands to the device + self.last_update_success = False + await async_shutdown_device(self.device) + self.entry.async_start_reauth(self.hass) + class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]): """Coordinator for a Shelly block based device.""" @@ -300,7 +309,7 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]): except DeviceConnectionError as err: raise UpdateFailed(f"Error fetching data: {repr(err)}") from err except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() @callback def _async_handle_update( @@ -384,7 +393,7 @@ class ShellyRestCoordinator(ShellyCoordinatorBase[BlockDevice]): except DeviceConnectionError as err: raise UpdateFailed(f"Error fetching data: {repr(err)}") from err except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() else: update_device_fw_info(self.hass, self.device, self.entry) @@ -540,7 +549,7 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): except DeviceConnectionError as err: raise UpdateFailed(f"Device disconnected: {repr(err)}") from err except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() async def _async_disconnected(self) -> None: """Handle device disconnected.""" @@ -633,7 +642,8 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): try: await async_stop_scanner(self.device) except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() + return await self.device.shutdown() await self._async_disconnected() @@ -663,7 +673,7 @@ class ShellyRpcPollingCoordinator(ShellyCoordinatorBase[RpcDevice]): except (DeviceConnectionError, RpcCallError) as err: raise UpdateFailed(f"Device disconnected: {repr(err)}") from err except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() def get_block_coordinator_by_device_id( diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index d5202713405..edcee5c2dda 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -344,7 +344,7 @@ class ShellyBlockEntity(CoordinatorEntity[ShellyBlockCoordinator]): f" {repr(err)}" ) from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() class ShellyRpcEntity(CoordinatorEntity[ShellyRpcCoordinator]): @@ -397,7 +397,7 @@ class ShellyRpcEntity(CoordinatorEntity[ShellyRpcCoordinator]): f" {params}, error: {repr(err)}" ) from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() class ShellyBlockAttributeEntity(ShellyBlockEntity, Entity): diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py index 1b1c9c42a11..6fdf05fa9cb 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -126,4 +126,4 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, RestoreNumber): f" {repr(err)}" ) from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() diff --git a/homeassistant/components/shelly/update.py b/homeassistant/components/shelly/update.py index 414bb4d6258..f6a89c5381b 100644 --- a/homeassistant/components/shelly/update.py +++ b/homeassistant/components/shelly/update.py @@ -200,7 +200,7 @@ class RestUpdateEntity(ShellyRestAttributeEntity, UpdateEntity): except DeviceConnectionError as err: raise HomeAssistantError(f"Error starting OTA update: {repr(err)}") from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() else: LOGGER.debug("Result of OTA update call: %s", result) @@ -289,7 +289,7 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity): except RpcCallError as err: raise HomeAssistantError(f"OTA update request error: {repr(err)}") from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() else: self._ota_in_progress = True LOGGER.debug("OTA update call successful") diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index dd0e685fd67..d26e3dc11f3 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -480,3 +480,11 @@ def is_rpc_wifi_stations_disabled( def get_http_port(data: MappingProxyType[str, Any]) -> int: """Get port from config entry data.""" return cast(int, data.get(CONF_PORT, DEFAULT_HTTP_PORT)) + + +async def async_shutdown_device(device: BlockDevice | RpcDevice) -> None: + """Shutdown a Shelly device.""" + if isinstance(device, RpcDevice): + await device.shutdown() + if isinstance(device, BlockDevice): + device.shutdown()