From 2e5e2c50dd0354888a5799ce62d2947f01e6ee1b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Jan 2025 17:41:21 -1000 Subject: [PATCH] Ensure Shelly cleanups Bluetooth scanner data upon removal (#135472) * Add bluetooth API to remove scanners that are no longer used - Cleanup the advertisment history right away when a scanner is removed In the future we will do some additional cleanup * coverage * finish tests * Ensure Shelly cleanups Bluetooth scanner data upon removal needs https://github.com/home-assistant/core/pull/135408 --- homeassistant/components/shelly/__init__.py | 9 +++++++++ .../components/shelly/coordinator.py | 4 +++- tests/components/shelly/test_init.py | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index e0d9d17d55d..5ca58ec7d01 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -15,6 +15,7 @@ from aioshelly.exceptions import ( from aioshelly.rpc_device import RpcDevice import voluptuous as vol +from homeassistant.components.bluetooth import async_remove_scanner from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady @@ -331,3 +332,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ShellyConfigEntry) -> b return await hass.config_entries.async_unload_platforms( entry, runtime_data.platforms ) + + +async def async_remove_entry(hass: HomeAssistant, entry: ShellyConfigEntry) -> None: + """Remove a config entry.""" + if get_device_entry_gen(entry) in RPC_GENERATIONS and ( + mac_address := entry.unique_id + ): + async_remove_scanner(hass, mac_address) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index f58e42a78d8..e6129b5559a 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -20,6 +20,7 @@ from aioshelly.exceptions import ( from aioshelly.rpc_device import RpcDevice, RpcUpdateType from propcache import cached_property +from homeassistant.components.bluetooth import async_remove_scanner from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import ( ATTR_DEVICE_ID, @@ -30,7 +31,7 @@ from homeassistant.const import ( from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, issue_registry as ir from homeassistant.helpers.debounce import Debouncer -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .bluetooth import async_connect_scanner @@ -697,6 +698,7 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): ) if ble_scanner_mode == BLEScannerMode.DISABLED and self.connected: await async_stop_scanner(self.device) + async_remove_scanner(self.hass, format_mac(self.mac).upper()) return if await async_ensure_ble_enabled(self.device): # BLE enable required a reboot, don't bother connecting diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py index b5516485501..270e2163635 100644 --- a/tests/components/shelly/test_init.py +++ b/tests/components/shelly/test_init.py @@ -545,3 +545,22 @@ async def test_sleeping_block_device_wrong_sleep_period( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert entry.data[CONF_SLEEP_PERIOD] == BLOCK_EXPECTED_SLEEP_PERIOD + + +async def test_bluetooth_cleanup_on_remove_entry( + hass: HomeAssistant, + mock_rpc_device: Mock, +) -> None: + """Test bluetooth is cleaned up on entry removal.""" + entry = await init_integration(hass, 2) + + assert entry.state is ConfigEntryState.LOADED + + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + with patch("homeassistant.components.shelly.async_remove_scanner") as remove_mock: + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + + remove_mock.assert_called_once_with(hass, entry.unique_id.upper())