From 4e5bf5ac22b3e38478fc589d65528a0a7c49b59e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Jan 2025 17:41:49 -1000 Subject: [PATCH] Ensure ESPHome cleanups Bluetooth scanner data upon removal (#135470) * 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 ESPHome cleanups Bluetooth scanner data upon removal needs https://github.com/home-assistant/core/pull/135408 --- homeassistant/components/esphome/__init__.py | 3 +++ homeassistant/components/esphome/manager.py | 4 +++- tests/components/esphome/test_bluetooth.py | 21 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 13e9496a9fd..5934c9a6f68 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -5,6 +5,7 @@ from __future__ import annotations from aioesphomeapi import APIClient from homeassistant.components import ffmpeg, zeroconf +from homeassistant.components.bluetooth import async_remove_scanner from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -86,4 +87,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) -> async def async_remove_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) -> None: """Remove an esphome config entry.""" + if mac_address := entry.unique_id: + async_remove_scanner(hass, mac_address.upper()) await DomainData.get(hass).get_or_create_store(hass, entry).async_remove() diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index dfd318c0c74..7fcd859142a 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -24,7 +24,7 @@ from aioesphomeapi import ( from awesomeversion import AwesomeVersion import voluptuous as vol -from homeassistant.components import tag, zeroconf +from homeassistant.components import bluetooth, tag, zeroconf from homeassistant.const import ( ATTR_DEVICE_ID, CONF_MODE, @@ -425,6 +425,8 @@ class ESPHomeManager: entry_data.disconnect_callbacks.add( async_connect_scanner(hass, entry_data, cli, device_info) ) + else: + bluetooth.async_remove_scanner(hass, device_info.mac_address) if device_info.voice_assistant_feature_flags_compat(api_version) and ( Platform.ASSIST_SATELLITE not in entry_data.loaded_platforms diff --git a/tests/components/esphome/test_bluetooth.py b/tests/components/esphome/test_bluetooth.py index 46858c5826b..31d9fcd34f9 100644 --- a/tests/components/esphome/test_bluetooth.py +++ b/tests/components/esphome/test_bluetooth.py @@ -1,5 +1,7 @@ """Test the ESPHome bluetooth integration.""" +from unittest.mock import patch + from homeassistant.components import bluetooth from homeassistant.core import HomeAssistant @@ -44,3 +46,22 @@ async def test_bluetooth_connect_with_legacy_adv( await hass.async_block_till_done() scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") assert scanner.scanning is True + + +async def test_bluetooth_cleanup_on_remove_entry( + hass: HomeAssistant, mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice +) -> None: + """Test bluetooth is cleaned up on entry removal.""" + scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") + assert scanner.connectable is True + await hass.config_entries.async_unload( + mock_bluetooth_entry_with_raw_adv.entry.entry_id + ) + + with patch("homeassistant.components.esphome.async_remove_scanner") as remove_mock: + await hass.config_entries.async_remove( + mock_bluetooth_entry_with_raw_adv.entry.entry_id + ) + await hass.async_block_till_done() + + remove_mock.assert_called_once_with(hass, scanner.source)