From 291df6dafe44090cb13ebae4672d2c1de4ba6e9c Mon Sep 17 00:00:00 2001
From: Shay Levy <levyshay1@gmail.com>
Date: Sun, 14 Apr 2024 18:07:26 +0300
Subject: [PATCH] Bump aioshelly to 9.0.0 (#114025)

* Update Shelly to use initialize from aioshelly

* Save indentation in _async_device_connect

* Use firmware_supported property from aioshelly

* Review comments

* Bump aioshelly

* Fix lint errors

* Test RPC initialized update
---
 homeassistant/components/shelly/__init__.py   | 116 +++++-------------
 .../components/shelly/config_flow.py          |  11 +-
 .../components/shelly/coordinator.py          |  89 ++++++++++----
 homeassistant/components/shelly/entity.py     |   2 +-
 homeassistant/components/shelly/light.py      |   2 +-
 homeassistant/components/shelly/manifest.json |   2 +-
 homeassistant/components/shelly/strings.json  |   1 -
 homeassistant/components/shelly/switch.py     |   8 +-
 requirements_all.txt                          |   2 +-
 requirements_test_all.txt                     |   2 +-
 tests/components/shelly/conftest.py           |  18 +++
 tests/components/shelly/test_binary_sensor.py |  13 +-
 tests/components/shelly/test_climate.py       |  18 +--
 tests/components/shelly/test_config_flow.py   |  50 +-------
 tests/components/shelly/test_coordinator.py   | 115 ++++++++++-------
 tests/components/shelly/test_init.py          |  69 ++++++++---
 tests/components/shelly/test_number.py        |  44 +++++--
 tests/components/shelly/test_sensor.py        |  33 +++--
 tests/components/shelly/test_update.py        |  11 +-
 19 files changed, 349 insertions(+), 257 deletions(-)

diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py
index 7d23a1cd57d..cfeab531687 100644
--- a/homeassistant/components/shelly/__init__.py
+++ b/homeassistant/components/shelly/__init__.py
@@ -3,23 +3,22 @@
 from __future__ import annotations
 
 import contextlib
-from typing import Any, Final
+from typing import Final
 
-from aioshelly.block_device import BlockDevice, BlockUpdateType
+from aioshelly.block_device import BlockDevice
 from aioshelly.common import ConnectionOptions
 from aioshelly.const import DEFAULT_COAP_PORT, RPC_GENERATIONS
 from aioshelly.exceptions import (
     DeviceConnectionError,
-    FirmwareUnsupported,
     InvalidAuthError,
     MacAddressMismatchError,
 )
-from aioshelly.rpc_device import RpcDevice, RpcUpdateType
+from aioshelly.rpc_device import RpcDevice
 import voluptuous as vol
 
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
-from homeassistant.core import HomeAssistant, callback
+from homeassistant.core import HomeAssistant
 from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
 from homeassistant.helpers import issue_registry as ir
 from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -53,12 +52,9 @@ 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,
     get_http_port,
-    get_rpc_device_wakeup_period,
     get_ws_context,
 )
 
@@ -154,7 +150,6 @@ async def _async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
         async_get_clientsession(hass),
         coap_context,
         options,
-        False,
     )
 
     dev_reg = dr_async_get(hass)
@@ -186,57 +181,38 @@ async def _async_setup_block_entry(hass: HomeAssistant, entry: ConfigEntry) -> b
         data[CONF_SLEEP_PERIOD] = sleep_period = BLOCK_EXPECTED_SLEEP_PERIOD
         hass.config_entries.async_update_entry(entry, data=data)
 
-    async def _async_block_device_setup() -> None:
-        """Set up a block based device that is online."""
-        shelly_entry_data.block = ShellyBlockCoordinator(hass, entry, device)
-        shelly_entry_data.block.async_setup()
-
-        platforms = BLOCK_SLEEPING_PLATFORMS
-
-        if not entry.data.get(CONF_SLEEP_PERIOD):
-            shelly_entry_data.rest = ShellyRestCoordinator(hass, device, entry)
-            platforms = BLOCK_PLATFORMS
-
-        await hass.config_entries.async_forward_entry_setups(entry, platforms)
-
-    @callback
-    def _async_device_online(_: Any, update_type: BlockUpdateType) -> None:
-        LOGGER.debug("Device %s is online, resuming setup", entry.title)
-        shelly_entry_data.device = None
-
-        if sleep_period is None:
-            data = {**entry.data}
-            data[CONF_SLEEP_PERIOD] = get_block_device_sleep_period(device.settings)
-            data["model"] = device.settings["device"]["type"]
-            hass.config_entries.async_update_entry(entry, data=data)
-
-        hass.async_create_task(_async_block_device_setup(), eager_start=True)
-
     if sleep_period == 0:
         # Not a sleeping device, finish setup
         LOGGER.debug("Setting up online block device %s", entry.title)
         try:
             await device.initialize()
+            if not device.firmware_supported:
+                async_create_issue_unsupported_firmware(hass, entry)
+                raise ConfigEntryNotReady
         except (DeviceConnectionError, MacAddressMismatchError) as err:
             raise ConfigEntryNotReady(repr(err)) from err
         except InvalidAuthError as err:
             raise ConfigEntryAuthFailed(repr(err)) from err
-        except FirmwareUnsupported as err:
-            async_create_issue_unsupported_firmware(hass, entry)
-            raise ConfigEntryNotReady from err
 
-        await _async_block_device_setup()
+        shelly_entry_data.block = ShellyBlockCoordinator(hass, entry, device)
+        shelly_entry_data.block.async_setup()
+        shelly_entry_data.rest = ShellyRestCoordinator(hass, device, entry)
+        await hass.config_entries.async_forward_entry_setups(entry, BLOCK_PLATFORMS)
     elif sleep_period is None or device_entry is None:
         # Need to get sleep info or first time sleeping device setup, wait for device
-        shelly_entry_data.device = device
         LOGGER.debug(
             "Setup for device %s will resume when device is online", entry.title
         )
-        device.subscribe_updates(_async_device_online)
+        shelly_entry_data.block = ShellyBlockCoordinator(hass, entry, device)
+        shelly_entry_data.block.async_setup(BLOCK_SLEEPING_PLATFORMS)
     else:
         # Restore sensors for sleeping device
         LOGGER.debug("Setting up offline block device %s", entry.title)
-        await _async_block_device_setup()
+        shelly_entry_data.block = ShellyBlockCoordinator(hass, entry, device)
+        shelly_entry_data.block.async_setup()
+        await hass.config_entries.async_forward_entry_setups(
+            entry, BLOCK_SLEEPING_PLATFORMS
+        )
 
     ir.async_delete_issue(
         hass, DOMAIN, FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=entry.unique_id)
@@ -260,7 +236,6 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> boo
         async_get_clientsession(hass),
         ws_context,
         options,
-        False,
     )
 
     dev_reg = dr_async_get(hass)
@@ -276,58 +251,38 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> boo
     sleep_period = entry.data.get(CONF_SLEEP_PERIOD)
     shelly_entry_data = get_entry_data(hass)[entry.entry_id]
 
-    async def _async_rpc_device_setup() -> None:
-        """Set up a RPC based device that is online."""
-        shelly_entry_data.rpc = ShellyRpcCoordinator(hass, entry, device)
-        shelly_entry_data.rpc.async_setup()
-
-        platforms = RPC_SLEEPING_PLATFORMS
-
-        if not entry.data.get(CONF_SLEEP_PERIOD):
-            shelly_entry_data.rpc_poll = ShellyRpcPollingCoordinator(
-                hass, entry, device
-            )
-            platforms = RPC_PLATFORMS
-
-        await hass.config_entries.async_forward_entry_setups(entry, platforms)
-
-    @callback
-    def _async_device_online(_: Any, update_type: RpcUpdateType) -> None:
-        LOGGER.debug("Device %s is online, resuming setup", entry.title)
-        shelly_entry_data.device = None
-
-        if sleep_period is None:
-            data = {**entry.data}
-            data[CONF_SLEEP_PERIOD] = get_rpc_device_wakeup_period(device.status)
-            hass.config_entries.async_update_entry(entry, data=data)
-
-        hass.async_create_task(_async_rpc_device_setup(), eager_start=True)
-
     if sleep_period == 0:
         # Not a sleeping device, finish setup
         LOGGER.debug("Setting up online RPC device %s", entry.title)
         try:
             await device.initialize()
-        except FirmwareUnsupported as err:
-            async_create_issue_unsupported_firmware(hass, entry)
-            raise ConfigEntryNotReady from err
+            if not device.firmware_supported:
+                async_create_issue_unsupported_firmware(hass, entry)
+                raise ConfigEntryNotReady
         except (DeviceConnectionError, MacAddressMismatchError) as err:
             raise ConfigEntryNotReady(repr(err)) from err
         except InvalidAuthError as err:
             raise ConfigEntryAuthFailed(repr(err)) from err
 
-        await _async_rpc_device_setup()
+        shelly_entry_data.rpc = ShellyRpcCoordinator(hass, entry, device)
+        shelly_entry_data.rpc.async_setup()
+        shelly_entry_data.rpc_poll = ShellyRpcPollingCoordinator(hass, entry, device)
+        await hass.config_entries.async_forward_entry_setups(entry, RPC_PLATFORMS)
     elif sleep_period is None or device_entry is None:
         # Need to get sleep info or first time sleeping device setup, wait for device
-        shelly_entry_data.device = device
         LOGGER.debug(
             "Setup for device %s will resume when device is online", entry.title
         )
-        device.subscribe_updates(_async_device_online)
+        shelly_entry_data.rpc = ShellyRpcCoordinator(hass, entry, device)
+        shelly_entry_data.rpc.async_setup(RPC_SLEEPING_PLATFORMS)
     else:
         # Restore sensors for sleeping device
-        LOGGER.debug("Setting up offline block device %s", entry.title)
-        await _async_rpc_device_setup()
+        LOGGER.debug("Setting up offline RPC device %s", entry.title)
+        shelly_entry_data.rpc = ShellyRpcCoordinator(hass, entry, device)
+        shelly_entry_data.rpc.async_setup()
+        await hass.config_entries.async_forward_entry_setups(
+            entry, RPC_SLEEPING_PLATFORMS
+        )
 
     ir.async_delete_issue(
         hass, DOMAIN, FIRMWARE_UNSUPPORTED_ISSUE_ID.format(unique=entry.unique_id)
@@ -339,11 +294,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Unload a config entry."""
     shelly_entry_data = get_entry_data(hass)[entry.entry_id]
 
-    # If device is present, block/rpc coordinator is not setup yet
-    if (device := shelly_entry_data.device) is not None:
-        await async_shutdown_device(device)
-        return True
-
     platforms = RPC_SLEEPING_PLATFORMS
     if not entry.data.get(CONF_SLEEP_PERIOD):
         platforms = RPC_PLATFORMS
diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py
index 24b66e15893..46cea4e49a4 100644
--- a/homeassistant/components/shelly/config_flow.py
+++ b/homeassistant/components/shelly/config_flow.py
@@ -11,7 +11,6 @@ from aioshelly.const import BLOCK_GENERATIONS, DEFAULT_HTTP_PORT, RPC_GENERATION
 from aioshelly.exceptions import (
     CustomPortNotSupported,
     DeviceConnectionError,
-    FirmwareUnsupported,
     InvalidAuthError,
 )
 from aioshelly.rpc_device import RpcDevice
@@ -103,6 +102,7 @@ async def validate_input(
             ws_context,
             options,
         )
+        await rpc_device.initialize()
         await rpc_device.shutdown()
 
         sleep_period = get_rpc_device_wakeup_period(rpc_device.status)
@@ -121,6 +121,7 @@ async def validate_input(
         coap_context,
         options,
     )
+    await block_device.initialize()
     block_device.shutdown()
     return {
         "title": block_device.name,
@@ -154,8 +155,6 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
                 self.info = await self._async_get_info(host, port)
             except DeviceConnectionError:
                 errors["base"] = "cannot_connect"
-            except FirmwareUnsupported:
-                return self.async_abort(reason="unsupported_firmware")
             except Exception:  # pylint: disable=broad-except
                 LOGGER.exception("Unexpected exception")
                 errors["base"] = "unknown"
@@ -287,8 +286,6 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
             self.info = await self._async_get_info(host, DEFAULT_HTTP_PORT)
         except DeviceConnectionError:
             return self.async_abort(reason="cannot_connect")
-        except FirmwareUnsupported:
-            return self.async_abort(reason="unsupported_firmware")
 
         if not mac:
             # We could not get the mac address from the name
@@ -366,14 +363,14 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
         if user_input is not None:
             try:
                 info = await self._async_get_info(host, port)
-            except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported):
+            except (DeviceConnectionError, InvalidAuthError):
                 return self.async_abort(reason="reauth_unsuccessful")
 
             if get_device_entry_gen(self.entry) != 1:
                 user_input[CONF_USERNAME] = "admin"
             try:
                 await validate_input(self.hass, host, port, info, user_input)
-            except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported):
+            except (DeviceConnectionError, InvalidAuthError):
                 return self.async_abort(reason="reauth_unsuccessful")
 
             return self.async_update_reload_and_abort(
diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py
index 18f96dd9c2e..bd6686198ed 100644
--- a/homeassistant/components/shelly/coordinator.py
+++ b/homeassistant/components/shelly/coordinator.py
@@ -15,7 +15,12 @@ from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCal
 from aioshelly.rpc_device import RpcDevice, RpcUpdateType
 
 from homeassistant.config_entries import ConfigEntry, ConfigEntryState
-from homeassistant.const import ATTR_DEVICE_ID, CONF_HOST, EVENT_HOMEASSISTANT_STOP
+from homeassistant.const import (
+    ATTR_DEVICE_ID,
+    CONF_HOST,
+    EVENT_HOMEASSISTANT_STOP,
+    Platform,
+)
 from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
 from homeassistant.helpers import issue_registry as ir
 from homeassistant.helpers.debounce import Debouncer
@@ -58,7 +63,9 @@ from .const import (
     BLEScannerMode,
 )
 from .utils import (
+    async_create_issue_unsupported_firmware,
     async_shutdown_device,
+    get_block_device_sleep_period,
     get_device_entry_gen,
     get_http_port,
     get_rpc_device_wakeup_period,
@@ -73,7 +80,6 @@ class ShellyEntryData:
     """Class for sharing data within a given config entry."""
 
     block: ShellyBlockCoordinator | None = None
-    device: BlockDevice | RpcDevice | None = None
     rest: ShellyRestCoordinator | None = None
     rpc: ShellyRpcCoordinator | None = None
     rpc_poll: ShellyRpcPollingCoordinator | None = None
@@ -98,6 +104,7 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]):
         self.entry = entry
         self.device = device
         self.device_id: str | None = None
+        self._pending_platforms: list[Platform] | None = None
         device_name = device.name if device.initialized else entry.title
         interval_td = timedelta(seconds=update_interval)
         super().__init__(hass, LOGGER, name=device_name, update_interval=interval_td)
@@ -131,8 +138,9 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]):
         """Sleep period of the device."""
         return self.entry.data.get(CONF_SLEEP_PERIOD, 0)
 
-    def async_setup(self) -> None:
+    def async_setup(self, pending_platforms: list[Platform] | None = None) -> None:
         """Set up the coordinator."""
+        self._pending_platforms = pending_platforms
         dev_reg = dr_async_get(self.hass)
         device_entry = dev_reg.async_get_or_create(
             config_entry_id=self.entry.entry_id,
@@ -146,6 +154,45 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]):
         )
         self.device_id = device_entry.id
 
+    async def _async_device_connect(self) -> None:
+        """Connect to a Shelly Block device."""
+        LOGGER.debug("Connecting to Shelly Device - %s", self.name)
+        try:
+            await self.device.initialize()
+            update_device_fw_info(self.hass, self.device, self.entry)
+        except DeviceConnectionError as err:
+            raise UpdateFailed(f"Device disconnected: {repr(err)}") from err
+        except InvalidAuthError:
+            self.entry.async_start_reauth(self.hass)
+            return
+
+        if not self.device.firmware_supported:
+            async_create_issue_unsupported_firmware(self.hass, self.entry)
+            return
+
+        if not self._pending_platforms:
+            return
+
+        LOGGER.debug("Device %s is online, resuming setup", self.entry.title)
+        platforms = self._pending_platforms
+        self._pending_platforms = None
+
+        data = {**self.entry.data}
+
+        # Update sleep_period
+        old_sleep_period = data[CONF_SLEEP_PERIOD]
+        if isinstance(self.device, RpcDevice):
+            new_sleep_period = get_rpc_device_wakeup_period(self.device.status)
+        elif isinstance(self.device, BlockDevice):
+            new_sleep_period = get_block_device_sleep_period(self.device.settings)
+
+        if new_sleep_period != old_sleep_period:
+            data[CONF_SLEEP_PERIOD] = new_sleep_period
+            self.hass.config_entries.async_update_entry(self.entry, data=data)
+
+        # Resume platform setup
+        await self.hass.config_entries.async_forward_entry_setups(self.entry, platforms)
+
     async def _async_reload_entry(self) -> None:
         """Reload entry."""
         self._debounced_reload.async_cancel()
@@ -179,7 +226,7 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
 
         self._last_cfg_changed: int | None = None
         self._last_mode: str | None = None
-        self._last_effect: int | None = None
+        self._last_effect: str | None = None
         self._last_input_events_count: dict = {}
         self._last_target_temp: float | None = None
         self._push_update_failures: int = 0
@@ -211,15 +258,14 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
         if not self.device.initialized:
             return
 
-        assert self.device.blocks
-
         # For buttons which are battery powered - set initial value for last_event_count
         if self.model in SHBTN_MODELS and self._last_input_events_count.get(1) is None:
             for block in self.device.blocks:
                 if block.type != "device":
                     continue
 
-                if len(block.wakeupEvent) == 1 and block.wakeupEvent[0] == "button":
+                wakeup_event = cast(list, block.wakeupEvent)
+                if len(wakeup_event) == 1 and wakeup_event[0] == "button":
                     self._last_input_events_count[1] = -1
 
                 break
@@ -228,7 +274,7 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
         cfg_changed = 0
         for block in self.device.blocks:
             if block.type == "device" and block.cfgChanged is not None:
-                cfg_changed = block.cfgChanged
+                cfg_changed = cast(int, block.cfgChanged)
 
             # Shelly TRV sends information about changing the configuration for no
             # reason, reloading the config entry is not needed for it.
@@ -314,14 +360,16 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
         self, device_: BlockDevice, update_type: BlockUpdateType
     ) -> None:
         """Handle device update."""
-        if update_type == BlockUpdateType.COAP_PERIODIC:
+        if update_type is BlockUpdateType.ONLINE:
+            self.hass.async_create_task(self._async_device_connect(), eager_start=True)
+        elif update_type is BlockUpdateType.COAP_PERIODIC:
             self._push_update_failures = 0
             ir.async_delete_issue(
                 self.hass,
                 DOMAIN,
                 PUSH_UPDATE_ISSUE_ID.format(unique=self.mac),
             )
-        elif update_type == BlockUpdateType.COAP_REPLY:
+        elif update_type is BlockUpdateType.COAP_REPLY:
             self._push_update_failures += 1
             if self._push_update_failures == MAX_PUSH_UPDATE_FAILURES:
                 LOGGER.debug(
@@ -346,9 +394,9 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]):
         )
         self.async_set_updated_data(None)
 
-    def async_setup(self) -> None:
+    def async_setup(self, pending_platforms: list[Platform] | None = None) -> None:
         """Set up the coordinator."""
-        super().async_setup()
+        super().async_setup(pending_platforms)
         self.device.subscribe_updates(self._async_handle_update)
 
     def shutdown(self) -> None:
@@ -538,14 +586,7 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
         if self.device.connected:
             return
 
-        LOGGER.debug("Reconnecting to Shelly RPC Device - %s", self.name)
-        try:
-            await self.device.initialize()
-            update_device_fw_info(self.hass, self.device, self.entry)
-        except DeviceConnectionError as err:
-            raise UpdateFailed(f"Device disconnected: {repr(err)}") from err
-        except InvalidAuthError:
-            await self.async_shutdown_device_and_start_reauth()
+        await self._async_device_connect()
 
     async def _async_disconnected(self) -> None:
         """Handle device disconnected."""
@@ -612,7 +653,9 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
         self, device_: RpcDevice, update_type: RpcUpdateType
     ) -> None:
         """Handle device update."""
-        if update_type is RpcUpdateType.INITIALIZED:
+        if update_type is RpcUpdateType.ONLINE:
+            self.hass.async_create_task(self._async_device_connect(), eager_start=True)
+        elif update_type is RpcUpdateType.INITIALIZED:
             self.hass.async_create_task(self._async_connected(), eager_start=True)
             self.async_set_updated_data(None)
         elif update_type is RpcUpdateType.DISCONNECTED:
@@ -624,9 +667,9 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
         elif update_type is RpcUpdateType.EVENT and (event := self.device.event):
             self._async_device_event_handler(event)
 
-    def async_setup(self) -> None:
+    def async_setup(self, pending_platforms: list[Platform] | None = None) -> None:
         """Set up the coordinator."""
-        super().async_setup()
+        super().async_setup(pending_platforms)
         self.device.subscribe_updates(self._async_handle_update)
         if self.device.initialized:
             # If we are already initialized, we are connected
diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py
index accca5f1a64..150244e2e47 100644
--- a/homeassistant/components/shelly/entity.py
+++ b/homeassistant/components/shelly/entity.py
@@ -74,7 +74,7 @@ def async_setup_block_attribute_entities(
 
     for block in coordinator.device.blocks:
         for sensor_id in block.sensor_ids:
-            description = sensors.get((block.type, sensor_id))
+            description = sensors.get((cast(str, block.type), sensor_id))
             if description is None:
                 continue
 
diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py
index d0590fc7c20..0650e2d15e5 100644
--- a/homeassistant/components/shelly/light.py
+++ b/homeassistant/components/shelly/light.py
@@ -78,7 +78,7 @@ def async_setup_block_entry(
     for block in coordinator.device.blocks:
         if block.type == "light":
             blocks.append(block)
-        elif block.type == "relay":
+        elif block.type == "relay" and block.channel is not None:
             if not is_block_channel_type_light(
                 coordinator.device.settings, int(block.channel)
             ):
diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json
index 06159cb543b..08971713ced 100644
--- a/homeassistant/components/shelly/manifest.json
+++ b/homeassistant/components/shelly/manifest.json
@@ -9,7 +9,7 @@
   "iot_class": "local_push",
   "loggers": ["aioshelly"],
   "quality_scale": "platinum",
-  "requirements": ["aioshelly==8.2.0"],
+  "requirements": ["aioshelly==9.0.0"],
   "zeroconf": [
     {
       "type": "_http._tcp.local.",
diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json
index d4a8b117f4c..cee27e9ca07 100644
--- a/homeassistant/components/shelly/strings.json
+++ b/homeassistant/components/shelly/strings.json
@@ -38,7 +38,6 @@
     },
     "abort": {
       "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
-      "unsupported_firmware": "The device is using an unsupported firmware version.",
       "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
       "reauth_unsuccessful": "Re-authentication was unsuccessful, please remove the integration and set it up again."
     }
diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py
index 48ff337d22a..14fec43c58b 100644
--- a/homeassistant/components/shelly/switch.py
+++ b/homeassistant/components/shelly/switch.py
@@ -104,8 +104,12 @@ def async_setup_block_entry(
     relay_blocks = []
     assert coordinator.device.blocks
     for block in coordinator.device.blocks:
-        if block.type != "relay" or is_block_channel_type_light(
-            coordinator.device.settings, int(block.channel)
+        if (
+            block.type != "relay"
+            or block.channel is not None
+            and is_block_channel_type_light(
+                coordinator.device.settings, int(block.channel)
+            )
         ):
             continue
 
diff --git a/requirements_all.txt b/requirements_all.txt
index 54d136c0b83..14f5749a02a 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -368,7 +368,7 @@ aioruuvigateway==0.1.0
 aiosenz==1.0.0
 
 # homeassistant.components.shelly
-aioshelly==8.2.0
+aioshelly==9.0.0
 
 # homeassistant.components.skybell
 aioskybell==22.7.0
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 32bfdcd0968..a58259c096c 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -341,7 +341,7 @@ aioruuvigateway==0.1.0
 aiosenz==1.0.0
 
 # homeassistant.components.shelly
-aioshelly==8.2.0
+aioshelly==9.0.0
 
 # homeassistant.components.skybell
 aioskybell==22.7.0
diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py
index 3cd27101f76..18813ff7eba 100644
--- a/tests/components/shelly/conftest.py
+++ b/tests/components/shelly/conftest.py
@@ -319,6 +319,11 @@ async def mock_block_device():
                 {}, BlockUpdateType.COAP_REPLY
             )
 
+        def online():
+            block_device_mock.return_value.subscribe_updates.call_args[0][0](
+                {}, BlockUpdateType.ONLINE
+            )
+
         device = Mock(
             spec=BlockDevice,
             blocks=MOCK_BLOCKS,
@@ -337,6 +342,7 @@ async def mock_block_device():
         block_device_mock.return_value.mock_update_reply = Mock(
             side_effect=update_reply
         )
+        block_device_mock.return_value.mock_online = Mock(side_effect=online)
 
         yield block_device_mock.return_value
 
@@ -376,16 +382,28 @@ async def mock_rpc_device():
                 {}, RpcUpdateType.EVENT
             )
 
+        def online():
+            rpc_device_mock.return_value.subscribe_updates.call_args[0][0](
+                {}, RpcUpdateType.ONLINE
+            )
+
         def disconnected():
             rpc_device_mock.return_value.subscribe_updates.call_args[0][0](
                 {}, RpcUpdateType.DISCONNECTED
             )
 
+        def initialized():
+            rpc_device_mock.return_value.subscribe_updates.call_args[0][0](
+                {}, RpcUpdateType.INITIALIZED
+            )
+
         device = _mock_rpc_device()
         rpc_device_mock.return_value = device
         rpc_device_mock.return_value.mock_disconnected = Mock(side_effect=disconnected)
         rpc_device_mock.return_value.mock_update = Mock(side_effect=update)
         rpc_device_mock.return_value.mock_event = Mock(side_effect=event)
+        rpc_device_mock.return_value.mock_online = Mock(side_effect=online)
+        rpc_device_mock.return_value.mock_initialized = Mock(side_effect=initialized)
 
         yield rpc_device_mock.return_value
 
diff --git a/tests/components/shelly/test_binary_sensor.py b/tests/components/shelly/test_binary_sensor.py
index 00a430cd4b1..624eb82f060 100644
--- a/tests/components/shelly/test_binary_sensor.py
+++ b/tests/components/shelly/test_binary_sensor.py
@@ -144,7 +144,7 @@ async def test_block_sleeping_binary_sensor(
     assert hass.states.get(entity_id) is None
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == STATE_OFF
@@ -180,7 +180,7 @@ async def test_block_restored_sleeping_binary_sensor(
 
     # Make device online
     monkeypatch.setattr(mock_block_device, "initialized", True)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == STATE_OFF
@@ -206,7 +206,7 @@ async def test_block_restored_sleeping_binary_sensor_no_last_state(
 
     # Make device online
     monkeypatch.setattr(mock_block_device, "initialized", True)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == STATE_OFF
@@ -263,6 +263,7 @@ async def test_rpc_sleeping_binary_sensor(
 ) -> None:
     """Test RPC online sleeping binary sensor."""
     entity_id = f"{BINARY_SENSOR_DOMAIN}.test_name_cloud"
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
     config_entry = await init_integration(hass, 2, sleep_period=1000)
 
     # Sensor should be created when device is online
@@ -273,7 +274,7 @@ async def test_rpc_sleeping_binary_sensor(
     )
 
     # Make device online
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == STATE_OFF
@@ -344,6 +345,10 @@ async def test_rpc_restored_sleeping_binary_sensor_no_last_state(
 
     # Make device online
     monkeypatch.setattr(mock_rpc_device, "initialized", True)
+    mock_rpc_device.mock_online()
+    await hass.async_block_till_done()
+
+    # Mock update
     mock_rpc_device.mock_update()
     await hass.async_block_till_done()
 
diff --git a/tests/components/shelly/test_climate.py b/tests/components/shelly/test_climate.py
index 0bdab979a0e..9fee3468f11 100644
--- a/tests/components/shelly/test_climate.py
+++ b/tests/components/shelly/test_climate.py
@@ -64,7 +64,7 @@ async def test_climate_hvac_mode(
     await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     # Test initial hvac mode - off
@@ -125,7 +125,7 @@ async def test_climate_set_temperature(
     await init_integration(hass, 1, sleep_period=1000)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     state = hass.states.get(ENTITY_ID)
@@ -192,7 +192,7 @@ async def test_climate_set_preset_mode(
     await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     state = hass.states.get(ENTITY_ID)
@@ -278,7 +278,7 @@ async def test_block_restored_climate(
 
     # Make device online
     monkeypatch.setattr(mock_block_device, "initialized", True)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == HVACMode.OFF
@@ -349,7 +349,7 @@ async def test_block_restored_climate_us_customery(
     monkeypatch.setattr(mock_block_device, "initialized", True)
     monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "targetTemp", 4.0)
     monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "temp", 18.2)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == HVACMode.OFF
@@ -451,7 +451,7 @@ async def test_block_set_mode_connection_error(
     await init_integration(hass, 1, sleep_period=1000)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     with pytest.raises(HomeAssistantError):
@@ -476,7 +476,7 @@ async def test_block_set_mode_auth_error(
     entry = await init_integration(hass, 1, sleep_period=1000)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert entry.state is ConfigEntryState.LOADED
@@ -534,7 +534,7 @@ async def test_block_restored_climate_auth_error(
     type(mock_block_device).settings = PropertyMock(
         return_value={}, side_effect=InvalidAuthError
     )
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert entry.state is ConfigEntryState.LOADED
@@ -561,7 +561,7 @@ async def test_device_not_calibrated(
     await init_integration(hass, 1, sleep_period=1000, model=MODEL_VALVE)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     mock_status = MOCK_STATUS_COAP.copy()
diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py
index f2b0736f867..c73b93f9fdb 100644
--- a/tests/components/shelly/test_config_flow.py
+++ b/tests/components/shelly/test_config_flow.py
@@ -10,7 +10,6 @@ from aioshelly.const import DEFAULT_HTTP_PORT, MODEL_1, MODEL_PLUS_2PM
 from aioshelly.exceptions import (
     CustomPortNotSupported,
     DeviceConnectionError,
-    FirmwareUnsupported,
     InvalidAuthError,
 )
 import pytest
@@ -433,25 +432,6 @@ async def test_user_setup_ignored_device(
     assert len(mock_setup_entry.mock_calls) == 1
 
 
-async def test_form_firmware_unsupported(hass: HomeAssistant) -> None:
-    """Test we abort if device firmware is unsupported."""
-    result = await hass.config_entries.flow.async_init(
-        DOMAIN, context={"source": config_entries.SOURCE_USER}
-    )
-
-    with patch(
-        "homeassistant.components.shelly.config_flow.get_info",
-        side_effect=FirmwareUnsupported,
-    ):
-        result2 = await hass.config_entries.flow.async_configure(
-            result["flow_id"],
-            {"host": "1.1.1.1"},
-        )
-
-        assert result2["type"] is FlowResultType.ABORT
-        assert result2["reason"] == "unsupported_firmware"
-
-
 @pytest.mark.parametrize(
     ("exc", "base_error"),
     [
@@ -757,22 +737,6 @@ async def test_zeroconf_with_wifi_ap_ip(hass: HomeAssistant) -> None:
     assert entry.data["host"] == "2.2.2.2"
 
 
-async def test_zeroconf_firmware_unsupported(hass: HomeAssistant) -> None:
-    """Test we abort if device firmware is unsupported."""
-    with patch(
-        "homeassistant.components.shelly.config_flow.get_info",
-        side_effect=FirmwareUnsupported,
-    ):
-        result = await hass.config_entries.flow.async_init(
-            DOMAIN,
-            data=DISCOVERY_INFO,
-            context={"source": config_entries.SOURCE_ZEROCONF},
-        )
-
-        assert result["type"] is FlowResultType.ABORT
-        assert result["reason"] == "unsupported_firmware"
-
-
 async def test_zeroconf_cannot_connect(hass: HomeAssistant) -> None:
     """Test we get the form."""
     with patch(
@@ -927,11 +891,7 @@ async def test_reauth_unsuccessful(
         assert result["reason"] == "reauth_unsuccessful"
 
 
-@pytest.mark.parametrize(
-    "error",
-    [DeviceConnectionError, FirmwareUnsupported],
-)
-async def test_reauth_get_info_error(hass: HomeAssistant, error: Exception) -> None:
+async def test_reauth_get_info_error(hass: HomeAssistant) -> None:
     """Test reauthentication flow failed with error in get_info()."""
     entry = MockConfigEntry(
         domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0", "gen": 2}
@@ -940,7 +900,7 @@ async def test_reauth_get_info_error(hass: HomeAssistant, error: Exception) -> N
 
     with patch(
         "homeassistant.components.shelly.config_flow.get_info",
-        side_effect=error,
+        side_effect=DeviceConnectionError,
     ):
         result = await hass.config_entries.flow.async_init(
             DOMAIN,
@@ -1154,6 +1114,7 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh(
     caplog: pytest.LogCaptureFixture,
 ) -> None:
     """Test zeroconf discovery does not triggers refresh for sleeping device."""
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
     entry = MockConfigEntry(
         domain="shelly",
         unique_id="AABBCCDDEEFF",
@@ -1163,10 +1124,11 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh(
     await hass.config_entries.async_setup(entry.entry_id)
     await hass.async_block_till_done()
 
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     assert "online, resuming setup" in caplog.text
+    assert len(mock_rpc_device.initialize.mock_calls) == 1
 
     with patch(
         "homeassistant.components.shelly.config_flow.get_info",
@@ -1186,7 +1148,7 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh(
         hass, dt_util.utcnow() + timedelta(seconds=ENTRY_RELOAD_COOLDOWN)
     )
     await hass.async_block_till_done()
-    assert len(mock_rpc_device.initialize.mock_calls) == 0
+    assert len(mock_rpc_device.initialize.mock_calls) == 1
     assert "device did not update" not in caplog.text
 
 
diff --git a/tests/components/shelly/test_coordinator.py b/tests/components/shelly/test_coordinator.py
index b155176dccd..9f251d1e008 100644
--- a/tests/components/shelly/test_coordinator.py
+++ b/tests/components/shelly/test_coordinator.py
@@ -1,14 +1,10 @@
 """Tests for Shelly coordinator."""
 
 from datetime import timedelta
-from unittest.mock import AsyncMock, Mock, patch
+from unittest.mock import AsyncMock, Mock, call, patch
 
 from aioshelly.const import MODEL_BULB, MODEL_BUTTON1
-from aioshelly.exceptions import (
-    DeviceConnectionError,
-    FirmwareUnsupported,
-    InvalidAuthError,
-)
+from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
 from freezegun.api import FrozenDateTimeFactory
 import pytest
 
@@ -29,13 +25,13 @@ from homeassistant.components.shelly.const import (
 from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
 from homeassistant.const import ATTR_DEVICE_ID, STATE_ON, STATE_UNAVAILABLE
 from homeassistant.core import Event, HomeAssistant
+from homeassistant.helpers import issue_registry as ir
 from homeassistant.helpers.device_registry import (
     CONNECTION_NETWORK_MAC,
     async_entries_for_config_entry,
     async_get as async_get_dev_reg,
     format_mac,
 )
-import homeassistant.helpers.issue_registry as ir
 
 from . import (
     MOCK_MAC,
@@ -216,28 +212,25 @@ async def test_block_rest_update_auth_error(
     assert flow["context"].get("entry_id") == entry.entry_id
 
 
-async def test_block_firmware_unsupported(
+async def test_block_sleeping_device_firmware_unsupported(
     hass: HomeAssistant,
-    freezer: FrozenDateTimeFactory,
     mock_block_device: Mock,
     monkeypatch: pytest.MonkeyPatch,
+    issue_registry: ir.IssueRegistry,
 ) -> None:
-    """Test block device polling authentication error."""
-    monkeypatch.setattr(
-        mock_block_device,
-        "update",
-        AsyncMock(side_effect=FirmwareUnsupported),
-    )
-    entry = await init_integration(hass, 1)
-
-    assert entry.state is ConfigEntryState.LOADED
+    """Test block sleeping device firmware not supported."""
+    monkeypatch.setattr(mock_block_device, "firmware_supported", False)
+    entry = await init_integration(hass, 1, sleep_period=3600)
 
-    # Move time to generate polling
-    freezer.tick(timedelta(seconds=UPDATE_PERIOD_MULTIPLIER * 15))
-    async_fire_time_changed(hass)
+    # Make device online
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert entry.state is ConfigEntryState.LOADED
+    assert (
+        DOMAIN,
+        "firmware_unsupported_123456789ABC",
+    ) in issue_registry.issues
 
 
 async def test_block_polling_connection_error(
@@ -290,20 +283,28 @@ async def test_block_rest_update_connection_error(
 
 
 async def test_block_sleeping_device_no_periodic_updates(
-    hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_block_device: Mock
+    hass: HomeAssistant,
+    freezer: FrozenDateTimeFactory,
+    mock_block_device: Mock,
+    monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     """Test block sleeping device no periodic updates."""
     entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
-    await init_integration(hass, 1, sleep_period=1000)
+    monkeypatch.setitem(
+        mock_block_device.settings,
+        "sleep_mode",
+        {"period": 60, "unit": "m"},
+    )
+    await init_integration(hass, 1, sleep_period=3600)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert get_entity_state(hass, entity_id) == "22.1"
 
     # Move time to generate polling
-    freezer.tick(timedelta(seconds=UPDATE_PERIOD_MULTIPLIER * 1000))
+    freezer.tick(timedelta(seconds=UPDATE_PERIOD_MULTIPLIER * 3600))
     async_fire_time_changed(hass)
     await hass.async_block_till_done()
 
@@ -352,7 +353,7 @@ async def test_block_button_click_event(
     entry = await init_integration(hass, 1, model=MODEL_BUTTON1, sleep_period=1000)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     dev_reg = async_get_dev_reg(hass)
@@ -529,6 +530,7 @@ async def test_rpc_update_entry_sleep_period(
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     """Test RPC update entry sleep period."""
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 600)
     entry = await init_integration(hass, 2, sleep_period=600)
     register_entity(
         hass,
@@ -539,7 +541,7 @@ async def test_rpc_update_entry_sleep_period(
     )
 
     # Make device online
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     assert entry.data["sleep_period"] == 600
@@ -554,10 +556,14 @@ async def test_rpc_update_entry_sleep_period(
 
 
 async def test_rpc_sleeping_device_no_periodic_updates(
-    hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_rpc_device: Mock
+    hass: HomeAssistant,
+    freezer: FrozenDateTimeFactory,
+    mock_rpc_device: Mock,
+    monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     """Test RPC sleeping device no periodic updates."""
     entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
     entry = await init_integration(hass, 2, sleep_period=1000)
     register_entity(
         hass,
@@ -568,7 +574,7 @@ async def test_rpc_sleeping_device_no_periodic_updates(
     )
 
     # Make device online
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     assert get_entity_state(hass, entity_id) == "22.9"
@@ -581,25 +587,25 @@ async def test_rpc_sleeping_device_no_periodic_updates(
     assert get_entity_state(hass, entity_id) is STATE_UNAVAILABLE
 
 
-async def test_rpc_firmware_unsupported(
-    hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_rpc_device: Mock
+async def test_rpc_sleeping_device_firmware_unsupported(
+    hass: HomeAssistant,
+    mock_rpc_device: Mock,
+    monkeypatch: pytest.MonkeyPatch,
+    issue_registry: ir.IssueRegistry,
 ) -> None:
-    """Test RPC update entry unsupported firmware."""
-    entry = await init_integration(hass, 2)
-    register_entity(
-        hass,
-        SENSOR_DOMAIN,
-        "test_name_temperature",
-        "temperature:0-temperature_0",
-        entry,
-    )
+    """Test RPC sleeping device firmware not supported."""
+    monkeypatch.setattr(mock_rpc_device, "firmware_supported", False)
+    entry = await init_integration(hass, 2, sleep_period=3600)
 
-    # Move time to generate sleep period update
-    freezer.tick(timedelta(seconds=600 * SLEEP_PERIOD_MULTIPLIER))
-    async_fire_time_changed(hass)
+    # Make device online
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     assert entry.state is ConfigEntryState.LOADED
+    assert (
+        DOMAIN,
+        "firmware_unsupported_123456789ABC",
+    ) in issue_registry.issues
 
 
 async def test_rpc_reconnect_auth_error(
@@ -753,11 +759,12 @@ async def test_rpc_update_entry_fw_ver(
     hass: HomeAssistant, mock_rpc_device: Mock, monkeypatch: pytest.MonkeyPatch
 ) -> None:
     """Test RPC update entry firmware version."""
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 600)
     entry = await init_integration(hass, 2, sleep_period=600)
     dev_reg = async_get_dev_reg(hass)
 
     # Make device online
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     assert entry.unique_id
@@ -779,3 +786,23 @@ async def test_rpc_update_entry_fw_ver(
     )
     assert device
     assert device.sw_version == "99.0.0"
+
+
+async def test_rpc_runs_connected_events_when_initialized(
+    hass: HomeAssistant,
+    mock_rpc_device: Mock,
+    monkeypatch: pytest.MonkeyPatch,
+) -> None:
+    """Test RPC runs connected events when initialized."""
+    monkeypatch.setattr(mock_rpc_device, "initialized", False)
+    await init_integration(hass, 2)
+
+    assert call.script_list() not in mock_rpc_device.mock_calls
+
+    # Mock initialized event
+    monkeypatch.setattr(mock_rpc_device, "initialized", True)
+    mock_rpc_device.mock_initialized()
+    await hass.async_block_till_done()
+
+    # BLE script list is called during connected events
+    assert call.script_list() in mock_rpc_device.mock_calls
diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py
index de658cd0d16..61ec8ce6779 100644
--- a/tests/components/shelly/test_init.py
+++ b/tests/components/shelly/test_init.py
@@ -8,7 +8,6 @@ from aioshelly.common import ConnectionOptions
 from aioshelly.const import MODEL_PLUS_2PM
 from aioshelly.exceptions import (
     DeviceConnectionError,
-    FirmwareUnsupported,
     InvalidAuthError,
     MacAddressMismatchError,
 )
@@ -27,6 +26,7 @@ from homeassistant.components.shelly.const import (
 from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
 from homeassistant.const import CONF_HOST, CONF_PORT, STATE_ON, STATE_UNAVAILABLE
 from homeassistant.core import HomeAssistant
+from homeassistant.helpers import issue_registry as ir
 from homeassistant.helpers.device_registry import (
     CONNECTION_NETWORK_MAC,
     DeviceRegistry,
@@ -145,27 +145,46 @@ async def test_setup_entry_not_shelly(
 
 
 @pytest.mark.parametrize("gen", [1, 2, 3])
-@pytest.mark.parametrize("side_effect", [DeviceConnectionError, FirmwareUnsupported])
 async def test_device_connection_error(
     hass: HomeAssistant,
     gen: int,
-    side_effect: Exception,
     mock_block_device: Mock,
     mock_rpc_device: Mock,
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     """Test device connection error."""
     monkeypatch.setattr(
-        mock_block_device, "initialize", AsyncMock(side_effect=side_effect)
+        mock_block_device, "initialize", AsyncMock(side_effect=DeviceConnectionError)
     )
     monkeypatch.setattr(
-        mock_rpc_device, "initialize", AsyncMock(side_effect=side_effect)
+        mock_rpc_device, "initialize", AsyncMock(side_effect=DeviceConnectionError)
     )
 
     entry = await init_integration(hass, gen)
     assert entry.state is ConfigEntryState.SETUP_RETRY
 
 
+@pytest.mark.parametrize("gen", [1, 2, 3])
+async def test_device_unsupported_firmware(
+    hass: HomeAssistant,
+    gen: int,
+    mock_block_device: Mock,
+    mock_rpc_device: Mock,
+    monkeypatch: pytest.MonkeyPatch,
+    issue_registry: ir.IssueRegistry,
+) -> None:
+    """Test device init with unsupported firmware."""
+    monkeypatch.setattr(mock_block_device, "firmware_supported", False)
+    monkeypatch.setattr(mock_rpc_device, "firmware_supported", False)
+
+    entry = await init_integration(hass, gen)
+    assert entry.state is ConfigEntryState.SETUP_RETRY
+    assert (
+        DOMAIN,
+        "firmware_unsupported_123456789ABC",
+    ) in issue_registry.issues
+
+
 @pytest.mark.parametrize("gen", [1, 2, 3])
 async def test_mac_mismatch_error(
     hass: HomeAssistant,
@@ -217,12 +236,13 @@ async def test_device_auth_error(
     assert flow["context"].get("entry_id") == entry.entry_id
 
 
-@pytest.mark.parametrize(("entry_sleep", "device_sleep"), [(None, 0), (1000, 1000)])
+@pytest.mark.parametrize(("entry_sleep", "device_sleep"), [(None, 0), (3600, 3600)])
 async def test_sleeping_block_device_online(
     hass: HomeAssistant,
     entry_sleep: int | None,
     device_sleep: int,
     mock_block_device: Mock,
+    monkeypatch: pytest.MonkeyPatch,
     device_reg: DeviceRegistry,
     caplog: pytest.LogCaptureFixture,
 ) -> None:
@@ -234,10 +254,17 @@ async def test_sleeping_block_device_online(
         connections={(CONNECTION_NETWORK_MAC, format_mac(MOCK_MAC))},
     )
 
+    monkeypatch.setitem(
+        mock_block_device.settings,
+        "sleep_mode",
+        {"period": int(device_sleep / 60), "unit": "m"},
+    )
     entry = await init_integration(hass, 1, sleep_period=entry_sleep)
     assert "will resume when device is online" in caplog.text
 
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
+    await hass.async_block_till_done()
+
     assert "online, resuming setup" in caplog.text
     assert entry.data["sleep_period"] == device_sleep
 
@@ -248,13 +275,17 @@ async def test_sleeping_rpc_device_online(
     entry_sleep: int | None,
     device_sleep: int,
     mock_rpc_device: Mock,
+    monkeypatch: pytest.MonkeyPatch,
     caplog: pytest.LogCaptureFixture,
 ) -> None:
     """Test sleeping RPC device online."""
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", device_sleep)
     entry = await init_integration(hass, 2, sleep_period=entry_sleep)
     assert "will resume when device is online" in caplog.text
 
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
+    await hass.async_block_till_done()
+
     assert "online, resuming setup" in caplog.text
     assert entry.data["sleep_period"] == device_sleep
 
@@ -270,7 +301,9 @@ async def test_sleeping_rpc_device_online_new_firmware(
     assert "will resume when device is online" in caplog.text
 
     mutate_rpc_device_status(monkeypatch, mock_rpc_device, "sys", "wakeup_period", 1500)
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
+    await hass.async_block_till_done()
+
     assert "online, resuming setup" in caplog.text
     assert entry.data["sleep_period"] == 1500
 
@@ -413,9 +446,12 @@ async def test_entry_missing_port(hass: HomeAssistant) -> None:
     }
     entry = MockConfigEntry(domain=DOMAIN, data=data, unique_id=MOCK_MAC)
     entry.add_to_hass(hass)
-    with patch(
-        "homeassistant.components.shelly.RpcDevice.create", return_value=Mock()
-    ) as rpc_device_mock:
+    with (
+        patch("homeassistant.components.shelly.RpcDevice.initialize"),
+        patch(
+            "homeassistant.components.shelly.RpcDevice.create", return_value=Mock()
+        ) as rpc_device_mock,
+    ):
         await hass.config_entries.async_setup(entry.entry_id)
         await hass.async_block_till_done()
 
@@ -435,9 +471,12 @@ async def test_rpc_entry_custom_port(hass: HomeAssistant) -> None:
     }
     entry = MockConfigEntry(domain=DOMAIN, data=data, unique_id=MOCK_MAC)
     entry.add_to_hass(hass)
-    with patch(
-        "homeassistant.components.shelly.RpcDevice.create", return_value=Mock()
-    ) as rpc_device_mock:
+    with (
+        patch("homeassistant.components.shelly.RpcDevice.initialize"),
+        patch(
+            "homeassistant.components.shelly.RpcDevice.create", return_value=Mock()
+        ) as rpc_device_mock,
+    ):
         await hass.config_entries.async_setup(entry.entry_id)
         await hass.async_block_till_done()
 
diff --git a/tests/components/shelly/test_number.py b/tests/components/shelly/test_number.py
index c138ef71b7d..99ad5709d29 100644
--- a/tests/components/shelly/test_number.py
+++ b/tests/components/shelly/test_number.py
@@ -33,12 +33,17 @@ async def test_block_number_update(
 ) -> None:
     """Test block device number update."""
     entity_id = "number.test_name_valve_position"
-    await init_integration(hass, 1, sleep_period=1000)
+    monkeypatch.setitem(
+        mock_block_device.settings,
+        "sleep_mode",
+        {"period": 60, "unit": "m"},
+    )
+    await init_integration(hass, 1, sleep_period=3600)
 
     assert hass.states.get(entity_id) is None
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "50"
@@ -93,7 +98,7 @@ async def test_block_restored_number(
 
     # Make device online
     monkeypatch.setattr(mock_block_device, "initialized", True)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "50"
@@ -130,20 +135,27 @@ async def test_block_restored_number_no_last_state(
 
     # Make device online
     monkeypatch.setattr(mock_block_device, "initialized", True)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "50"
 
 
 async def test_block_number_set_value(
-    hass: HomeAssistant, mock_block_device: Mock
+    hass: HomeAssistant,
+    mock_block_device: Mock,
+    monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     """Test block device number set value."""
-    await init_integration(hass, 1, sleep_period=1000)
+    monkeypatch.setitem(
+        mock_block_device.settings,
+        "sleep_mode",
+        {"period": 60, "unit": "m"},
+    )
+    await init_integration(hass, 1, sleep_period=3600)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     mock_block_device.reset_mock()
@@ -162,15 +174,20 @@ async def test_block_set_value_connection_error(
     hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch
 ) -> None:
     """Test block device set value connection error."""
+    monkeypatch.setitem(
+        mock_block_device.settings,
+        "sleep_mode",
+        {"period": 60, "unit": "m"},
+    )
     monkeypatch.setattr(
         mock_block_device,
         "http_request",
         AsyncMock(side_effect=DeviceConnectionError),
     )
-    await init_integration(hass, 1, sleep_period=1000)
+    await init_integration(hass, 1, sleep_period=3600)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     with pytest.raises(HomeAssistantError):
@@ -186,15 +203,20 @@ async def test_block_set_value_auth_error(
     hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch
 ) -> None:
     """Test block device set value authentication error."""
+    monkeypatch.setitem(
+        mock_block_device.settings,
+        "sleep_mode",
+        {"period": 60, "unit": "m"},
+    )
     monkeypatch.setattr(
         mock_block_device,
         "http_request",
         AsyncMock(side_effect=InvalidAuthError),
     )
-    entry = await init_integration(hass, 1, sleep_period=1000)
+    entry = await init_integration(hass, 1, sleep_period=3600)
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert entry.state is ConfigEntryState.LOADED
diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py
index 0a15b78994b..6151cac10ab 100644
--- a/tests/components/shelly/test_sensor.py
+++ b/tests/components/shelly/test_sensor.py
@@ -164,7 +164,7 @@ async def test_block_sleeping_sensor(
     assert hass.states.get(entity_id) is None
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "22.1"
@@ -206,7 +206,7 @@ async def test_block_restored_sleeping_sensor(
 
     # Make device online
     monkeypatch.setattr(mock_block_device, "initialized", True)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "22.1"
@@ -232,7 +232,7 @@ async def test_block_restored_sleeping_sensor_no_last_state(
 
     # Make device online
     monkeypatch.setattr(mock_block_device, "initialized", True)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "22.1"
@@ -305,7 +305,7 @@ async def test_block_not_matched_restored_sleeping_sensor(
         mock_block_device.blocks[SENSOR_BLOCK_ID], "description", "other_desc"
     )
     monkeypatch.setattr(mock_block_device, "initialized", True)
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "20.4"
@@ -448,6 +448,7 @@ async def test_rpc_sleeping_sensor(
 ) -> None:
     """Test RPC online sleeping sensor."""
     entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
     entry = await init_integration(hass, 2, sleep_period=1000)
 
     # Sensor should be created when device is online
@@ -462,7 +463,7 @@ async def test_rpc_sleeping_sensor(
     )
 
     # Make device online
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "22.9"
@@ -501,6 +502,10 @@ async def test_rpc_restored_sleeping_sensor(
 
     # Make device online
     monkeypatch.setattr(mock_rpc_device, "initialized", True)
+    mock_rpc_device.mock_online()
+    await hass.async_block_till_done()
+
+    # Mock update
     mock_rpc_device.mock_update()
     await hass.async_block_till_done()
 
@@ -533,6 +538,10 @@ async def test_rpc_restored_sleeping_sensor_no_last_state(
 
     # Make device online
     monkeypatch.setattr(mock_rpc_device, "initialized", True)
+    mock_rpc_device.mock_online()
+    await hass.async_block_till_done()
+
+    # Mock update
     mock_rpc_device.mock_update()
     await hass.async_block_till_done()
 
@@ -583,19 +592,21 @@ async def test_rpc_sleeping_update_entity_service(
     hass: HomeAssistant,
     mock_rpc_device: Mock,
     entity_registry: EntityRegistry,
+    monkeypatch: pytest.MonkeyPatch,
     caplog: pytest.LogCaptureFixture,
 ) -> None:
     """Test RPC sleeping device when the update_entity service is used."""
     await async_setup_component(hass, "homeassistant", {})
 
     entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
     await init_integration(hass, 2, sleep_period=1000)
 
     # Entity should be created when device is online
     assert hass.states.get(entity_id) is None
 
     # Make device online
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     state = hass.states.get(entity_id)
@@ -627,19 +638,25 @@ async def test_block_sleeping_update_entity_service(
     hass: HomeAssistant,
     mock_block_device: Mock,
     entity_registry: EntityRegistry,
+    monkeypatch: pytest.MonkeyPatch,
     caplog: pytest.LogCaptureFixture,
 ) -> None:
     """Test block sleeping device when the update_entity service is used."""
     await async_setup_component(hass, "homeassistant", {})
 
     entity_id = f"{SENSOR_DOMAIN}.test_name_temperature"
-    await init_integration(hass, 1, sleep_period=1000)
+    monkeypatch.setitem(
+        mock_block_device.settings,
+        "sleep_mode",
+        {"period": 60, "unit": "m"},
+    )
+    await init_integration(hass, 1, sleep_period=3600)
 
     # Sensor should be created when device is online
     assert hass.states.get(entity_id) is None
 
     # Make device online
-    mock_block_device.mock_update()
+    mock_block_device.mock_online()
     await hass.async_block_till_done()
 
     assert hass.states.get(entity_id).state == "22.1"
diff --git a/tests/components/shelly/test_update.py b/tests/components/shelly/test_update.py
index 73320b2c65f..93b0f55c415 100644
--- a/tests/components/shelly/test_update.py
+++ b/tests/components/shelly/test_update.py
@@ -335,6 +335,7 @@ async def test_rpc_sleeping_update(
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     """Test RPC sleeping device update entity."""
+    monkeypatch.setitem(mock_rpc_device.status["sys"], "wakeup_period", 1000)
     monkeypatch.setitem(mock_rpc_device.shelly, "ver", "1")
     monkeypatch.setitem(
         mock_rpc_device.status["sys"],
@@ -350,7 +351,7 @@ async def test_rpc_sleeping_update(
     assert hass.states.get(entity_id) is None
 
     # Make device online
-    mock_rpc_device.mock_update()
+    mock_rpc_device.mock_online()
     await hass.async_block_till_done()
 
     state = hass.states.get(entity_id)
@@ -411,6 +412,10 @@ async def test_rpc_restored_sleeping_update(
 
     # Make device online
     monkeypatch.setattr(mock_rpc_device, "initialized", True)
+    mock_rpc_device.mock_online()
+    await hass.async_block_till_done()
+
+    # Mock update
     mock_rpc_device.mock_update()
     await hass.async_block_till_done()
 
@@ -456,6 +461,10 @@ async def test_rpc_restored_sleeping_update_no_last_state(
 
     # Make device online
     monkeypatch.setattr(mock_rpc_device, "initialized", True)
+    mock_rpc_device.mock_online()
+    await hass.async_block_till_done()
+
+    # Mock update
     mock_rpc_device.mock_update()
     await hass.async_block_till_done()
 
-- 
GitLab