Bump bleak-esphome to 2.9.0 (#139467)

* Bump bleak-esphome to 2.9.0

changelog: https://github.com/Bluetooth-Devices/bleak-esphome/compare/v2.8.0...v2.9.0

* fixes
This commit is contained in:
J. Nick Koston 2025-02-28 22:17:44 +00:00 committed by GitHub
parent db05aa17d3
commit ee1fe2cae4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 131 additions and 26 deletions

View File

@ -22,5 +22,5 @@
"integration_type": "device", "integration_type": "device",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["eq3btsmart"], "loggers": ["eq3btsmart"],
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.8.0"] "requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.9.0"]
} }

View File

@ -16,7 +16,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .const import CONF_NOISE_PSK, DATA_FFMPEG_PROXY, DOMAIN from .const import CONF_BLUETOOTH_MAC_ADDRESS, CONF_NOISE_PSK, DATA_FFMPEG_PROXY, DOMAIN
from .dashboard import async_setup as async_setup_dashboard from .dashboard import async_setup as async_setup_dashboard
from .domain_data import DomainData from .domain_data import DomainData
@ -87,6 +87,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) ->
async def async_remove_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) -> None: async def async_remove_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) -> None:
"""Remove an esphome config entry.""" """Remove an esphome config entry."""
if mac_address := entry.unique_id: if bluetooth_mac_address := entry.data.get(CONF_BLUETOOTH_MAC_ADDRESS):
async_remove_scanner(hass, mac_address.upper()) async_remove_scanner(hass, bluetooth_mac_address.upper())
await DomainData.get(hass).get_or_create_store(hass, entry).async_remove() await DomainData.get(hass).get_or_create_store(hass, entry).async_remove()

View File

@ -8,6 +8,7 @@ CONF_ALLOW_SERVICE_CALLS = "allow_service_calls"
CONF_SUBSCRIBE_LOGS = "subscribe_logs" CONF_SUBSCRIBE_LOGS = "subscribe_logs"
CONF_DEVICE_NAME = "device_name" CONF_DEVICE_NAME = "device_name"
CONF_NOISE_PSK = "noise_psk" CONF_NOISE_PSK = "noise_psk"
CONF_BLUETOOTH_MAC_ADDRESS = "bluetooth_mac_address"
DEFAULT_ALLOW_SERVICE_CALLS = True DEFAULT_ALLOW_SERVICE_CALLS = True
DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False

View File

@ -25,13 +25,17 @@ async def async_get_config_entry_diagnostics(
diag["config"] = config_entry.as_dict() diag["config"] = config_entry.as_dict()
entry_data = config_entry.runtime_data entry_data = config_entry.runtime_data
device_info = entry_data.device_info
if (storage_data := await entry_data.store.async_load()) is not None: if (storage_data := await entry_data.store.async_load()) is not None:
diag["storage_data"] = storage_data diag["storage_data"] = storage_data
if ( if (
config_entry.unique_id device_info
and (scanner := async_scanner_by_source(hass, config_entry.unique_id.upper())) and (
scanner_mac := device_info.bluetooth_mac_address or device_info.mac_address
)
and (scanner := async_scanner_by_source(hass, scanner_mac.upper()))
and (bluetooth_device := entry_data.bluetooth_device) and (bluetooth_device := entry_data.bluetooth_device)
): ):
diag["bluetooth"] = { diag["bluetooth"] = {

View File

@ -63,6 +63,7 @@ from homeassistant.util.async_ import create_eager_task
from .bluetooth import async_connect_scanner from .bluetooth import async_connect_scanner
from .const import ( from .const import (
CONF_ALLOW_SERVICE_CALLS, CONF_ALLOW_SERVICE_CALLS,
CONF_BLUETOOTH_MAC_ADDRESS,
CONF_DEVICE_NAME, CONF_DEVICE_NAME,
CONF_SUBSCRIBE_LOGS, CONF_SUBSCRIBE_LOGS,
DEFAULT_ALLOW_SERVICE_CALLS, DEFAULT_ALLOW_SERVICE_CALLS,
@ -431,6 +432,13 @@ class ESPHomeManager:
device_mac = format_mac(device_info.mac_address) device_mac = format_mac(device_info.mac_address)
mac_address_matches = unique_id == device_mac mac_address_matches = unique_id == device_mac
if (
bluetooth_mac_address := device_info.bluetooth_mac_address
) and entry.data.get(CONF_BLUETOOTH_MAC_ADDRESS) != bluetooth_mac_address:
hass.config_entries.async_update_entry(
entry,
data={**entry.data, CONF_BLUETOOTH_MAC_ADDRESS: bluetooth_mac_address},
)
# #
# Migrate config entry to new unique ID if the current # Migrate config entry to new unique ID if the current
# unique id is not a mac address. # unique id is not a mac address.
@ -498,7 +506,9 @@ class ESPHomeManager:
) )
) )
else: else:
bluetooth.async_remove_scanner(hass, device_info.mac_address) bluetooth.async_remove_scanner(
hass, device_info.bluetooth_mac_address or device_info.mac_address
)
if device_info.voice_assistant_feature_flags_compat(api_version) and ( if device_info.voice_assistant_feature_flags_compat(api_version) and (
Platform.ASSIST_SATELLITE not in entry_data.loaded_platforms Platform.ASSIST_SATELLITE not in entry_data.loaded_platforms
@ -617,11 +627,22 @@ class ESPHomeManager:
) )
_setup_services(hass, entry_data, services) _setup_services(hass, entry_data, services)
if entry_data.device_info is not None and entry_data.device_info.name: if (device_info := entry_data.device_info) is not None:
reconnect_logic.name = entry_data.device_info.name if device_info.name:
reconnect_logic.name = device_info.name
if (
bluetooth_mac_address := device_info.bluetooth_mac_address
) and entry.data.get(CONF_BLUETOOTH_MAC_ADDRESS) != bluetooth_mac_address:
hass.config_entries.async_update_entry(
entry,
data={
**entry.data,
CONF_BLUETOOTH_MAC_ADDRESS: bluetooth_mac_address,
},
)
if entry.unique_id is None: if entry.unique_id is None:
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
entry, unique_id=format_mac(entry_data.device_info.mac_address) entry, unique_id=format_mac(device_info.mac_address)
) )
await reconnect_logic.start() await reconnect_logic.start()

View File

@ -18,7 +18,7 @@
"requirements": [ "requirements": [
"aioesphomeapi==29.3.1", "aioesphomeapi==29.3.1",
"esphome-dashboard-api==1.2.3", "esphome-dashboard-api==1.2.3",
"bleak-esphome==2.8.0" "bleak-esphome==2.9.0"
], ],
"zeroconf": ["_esphomelib._tcp.local."] "zeroconf": ["_esphomelib._tcp.local."]
} }

2
requirements_all.txt generated
View File

@ -603,7 +603,7 @@ bizkaibus==0.1.1
# homeassistant.components.eq3btsmart # homeassistant.components.eq3btsmart
# homeassistant.components.esphome # homeassistant.components.esphome
bleak-esphome==2.8.0 bleak-esphome==2.9.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bleak-retry-connector==3.9.0 bleak-retry-connector==3.9.0

View File

@ -534,7 +534,7 @@ bimmer-connected[china]==0.17.2
# homeassistant.components.eq3btsmart # homeassistant.components.eq3btsmart
# homeassistant.components.esphome # homeassistant.components.esphome
bleak-esphome==2.8.0 bleak-esphome==2.9.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
bleak-retry-connector==3.9.0 bleak-retry-connector==3.9.0

View File

@ -30,6 +30,7 @@ from zeroconf import Zeroconf
from homeassistant.components.esphome import dashboard from homeassistant.components.esphome import dashboard
from homeassistant.components.esphome.const import ( from homeassistant.components.esphome.const import (
CONF_ALLOW_SERVICE_CALLS, CONF_ALLOW_SERVICE_CALLS,
CONF_BLUETOOTH_MAC_ADDRESS,
CONF_DEVICE_NAME, CONF_DEVICE_NAME,
CONF_NOISE_PSK, CONF_NOISE_PSK,
DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS, DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS,
@ -578,6 +579,19 @@ async def mock_bluetooth_entry(
async def _mock_bluetooth_entry( async def _mock_bluetooth_entry(
bluetooth_proxy_feature_flags: BluetoothProxyFeature, bluetooth_proxy_feature_flags: BluetoothProxyFeature,
) -> MockESPHomeDevice: ) -> MockESPHomeDevice:
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "test.local",
CONF_PORT: 6053,
CONF_PASSWORD: "",
CONF_BLUETOOTH_MAC_ADDRESS: "AA:BB:CC:DD:EE:FC",
},
options={
CONF_ALLOW_SERVICE_CALLS: DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS
},
)
entry.add_to_hass(hass)
return await _mock_generic_device_entry( return await _mock_generic_device_entry(
hass, hass,
mock_client, mock_client,
@ -587,6 +601,7 @@ async def mock_bluetooth_entry(
}, },
([], []), ([], []),
[], [],
entry=entry,
) )
return _mock_bluetooth_entry return _mock_bluetooth_entry

View File

@ -13,7 +13,7 @@ async def test_bluetooth_connect_with_raw_adv(
hass: HomeAssistant, mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice hass: HomeAssistant, mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice
) -> None: ) -> None:
"""Test bluetooth connect with raw advertisements.""" """Test bluetooth connect with raw advertisements."""
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner is not None assert scanner is not None
assert scanner.connectable is True assert scanner.connectable is True
assert scanner.scanning is True assert scanner.scanning is True
@ -21,11 +21,11 @@ async def test_bluetooth_connect_with_raw_adv(
await mock_bluetooth_entry_with_raw_adv.mock_disconnect(True) await mock_bluetooth_entry_with_raw_adv.mock_disconnect(True)
await hass.async_block_till_done() await hass.async_block_till_done()
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner is None assert scanner is None
await mock_bluetooth_entry_with_raw_adv.mock_connect() await mock_bluetooth_entry_with_raw_adv.mock_connect()
await hass.async_block_till_done() await hass.async_block_till_done()
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner.scanning is True assert scanner.scanning is True
@ -33,7 +33,7 @@ async def test_bluetooth_connect_with_legacy_adv(
hass: HomeAssistant, mock_bluetooth_entry_with_legacy_adv: MockESPHomeDevice hass: HomeAssistant, mock_bluetooth_entry_with_legacy_adv: MockESPHomeDevice
) -> None: ) -> None:
"""Test bluetooth connect with legacy advertisements.""" """Test bluetooth connect with legacy advertisements."""
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner is not None assert scanner is not None
assert scanner.connectable is True assert scanner.connectable is True
assert scanner.scanning is True assert scanner.scanning is True
@ -41,11 +41,11 @@ async def test_bluetooth_connect_with_legacy_adv(
await mock_bluetooth_entry_with_legacy_adv.mock_disconnect(True) await mock_bluetooth_entry_with_legacy_adv.mock_disconnect(True)
await hass.async_block_till_done() await hass.async_block_till_done()
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner is None assert scanner is None
await mock_bluetooth_entry_with_legacy_adv.mock_connect() await mock_bluetooth_entry_with_legacy_adv.mock_connect()
await hass.async_block_till_done() await hass.async_block_till_done()
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner.scanning is True assert scanner.scanning is True
@ -55,10 +55,10 @@ async def test_bluetooth_device_linked_via_device(
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
) -> None: ) -> None:
"""Test the Bluetooth device is linked to the ESPHome device.""" """Test the Bluetooth device is linked to the ESPHome device."""
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner.connectable is True assert scanner.connectable is True
entry = hass.config_entries.async_entry_for_domain_unique_id( entry = hass.config_entries.async_entry_for_domain_unique_id(
"bluetooth", "11:22:33:44:55:AA" "bluetooth", "AA:BB:CC:DD:EE:FC"
) )
assert entry is not None assert entry is not None
esp_device = device_registry.async_get_device( esp_device = device_registry.async_get_device(
@ -71,7 +71,7 @@ async def test_bluetooth_device_linked_via_device(
) )
assert esp_device is not None assert esp_device is not None
device = device_registry.async_get_device( device = device_registry.async_get_device(
connections={(dr.CONNECTION_BLUETOOTH, "11:22:33:44:55:AA")} connections={(dr.CONNECTION_BLUETOOTH, "AA:BB:CC:DD:EE:FC")}
) )
assert device is not None assert device is not None
assert device.via_device_id == esp_device.id assert device.via_device_id == esp_device.id
@ -81,7 +81,7 @@ async def test_bluetooth_cleanup_on_remove_entry(
hass: HomeAssistant, mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice hass: HomeAssistant, mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice
) -> None: ) -> None:
"""Test bluetooth is cleaned up on entry removal.""" """Test bluetooth is cleaned up on entry removal."""
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner.connectable is True assert scanner.connectable is True
await hass.config_entries.async_unload( await hass.config_entries.async_unload(
mock_bluetooth_entry_with_raw_adv.entry.entry_id mock_bluetooth_entry_with_raw_adv.entry.entry_id

View File

@ -37,7 +37,7 @@ async def test_diagnostics_with_bluetooth(
mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice, mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice,
) -> None: ) -> None:
"""Test diagnostics for config entry with Bluetooth.""" """Test diagnostics for config entry with Bluetooth."""
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA") scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FC")
assert scanner is not None assert scanner is not None
assert scanner.connectable is True assert scanner.connectable is True
entry = mock_bluetooth_entry_with_raw_adv.entry entry = mock_bluetooth_entry_with_raw_adv.entry
@ -55,9 +55,9 @@ async def test_diagnostics_with_bluetooth(
"discovered_devices_and_advertisement_data": [], "discovered_devices_and_advertisement_data": [],
"last_detection": ANY, "last_detection": ANY,
"monotonic_time": ANY, "monotonic_time": ANY,
"name": "test (11:22:33:44:55:AA)", "name": "test (AA:BB:CC:DD:EE:FC)",
"scanning": True, "scanning": True,
"source": "11:22:33:44:55:AA", "source": "AA:BB:CC:DD:EE:FC",
"start_time": ANY, "start_time": ANY,
"time_since_last_device_detection": {}, "time_since_last_device_detection": {},
"type": "ESPHomeScanner", "type": "ESPHomeScanner",
@ -66,6 +66,7 @@ async def test_diagnostics_with_bluetooth(
"config": { "config": {
"created_at": ANY, "created_at": ANY,
"data": { "data": {
"bluetooth_mac_address": "**REDACTED**",
"device_name": "test", "device_name": "test",
"host": "test.local", "host": "test.local",
"password": "", "password": "",

View File

@ -25,6 +25,7 @@ import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.esphome.const import ( from homeassistant.components.esphome.const import (
CONF_ALLOW_SERVICE_CALLS, CONF_ALLOW_SERVICE_CALLS,
CONF_BLUETOOTH_MAC_ADDRESS,
CONF_DEVICE_NAME, CONF_DEVICE_NAME,
CONF_SUBSCRIBE_LOGS, CONF_SUBSCRIBE_LOGS,
DOMAIN, DOMAIN,
@ -475,6 +476,39 @@ async def test_unique_id_updated_to_mac(hass: HomeAssistant, mock_client) -> Non
assert entry.unique_id == "11:22:33:44:55:aa" assert entry.unique_id == "11:22:33:44:55:aa"
@pytest.mark.usefixtures("mock_zeroconf")
async def test_add_missing_bluetooth_mac_address(
hass: HomeAssistant, mock_client
) -> None:
"""Test bluetooth mac is added if its missing."""
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: "test.local", CONF_PORT: 6053, CONF_PASSWORD: ""},
unique_id="mock-config-name",
)
entry.add_to_hass(hass)
subscribe_done = hass.loop.create_future()
def async_subscribe_states(*args, **kwargs) -> None:
subscribe_done.set_result(None)
mock_client.subscribe_states = async_subscribe_states
mock_client.device_info = AsyncMock(
return_value=DeviceInfo(
mac_address="1122334455aa",
bluetooth_mac_address="AA:BB:CC:DD:EE:FF",
)
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
async with asyncio.timeout(1):
await subscribe_done
assert entry.unique_id == "11:22:33:44:55:aa"
assert entry.data.get(CONF_BLUETOOTH_MAC_ADDRESS) == "AA:BB:CC:DD:EE:FF"
@pytest.mark.usefixtures("mock_zeroconf") @pytest.mark.usefixtures("mock_zeroconf")
async def test_unique_id_not_updated_if_name_same_and_already_mac( async def test_unique_id_not_updated_if_name_same_and_already_mac(
hass: HomeAssistant, mock_client: APIClient hass: HomeAssistant, mock_client: APIClient
@ -1337,3 +1371,32 @@ async def test_entry_missing_unique_id(
await mock_esphome_device(mock_client=mock_client, mock_storage=True) await mock_esphome_device(mock_client=mock_client, mock_storage=True)
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.unique_id == "11:22:33:44:55:aa" assert entry.unique_id == "11:22:33:44:55:aa"
async def test_entry_missing_bluetooth_mac_address(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test the bluetooth_mac_address is added if available."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id=None,
data={
CONF_HOST: "test.local",
CONF_PORT: 6053,
CONF_PASSWORD: "",
},
options={CONF_ALLOW_SERVICE_CALLS: True},
)
entry.add_to_hass(hass)
await mock_esphome_device(
mock_client=mock_client,
mock_storage=True,
device_info={"bluetooth_mac_address": "AA:BB:CC:DD:EE:FC"},
)
await hass.async_block_till_done()
assert entry.data[CONF_BLUETOOTH_MAC_ADDRESS] == "AA:BB:CC:DD:EE:FC"