mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Set via_device for remote Bluetooth adapters to link to the parent device (#137091)
This commit is contained in:
parent
e76ff0a0de
commit
2d1d9bbe5a
@ -80,6 +80,7 @@ from .const import (
|
||||
CONF_DETAILS,
|
||||
CONF_PASSIVE,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||
CONF_SOURCE_DEVICE_ID,
|
||||
CONF_SOURCE_DOMAIN,
|
||||
CONF_SOURCE_MODEL,
|
||||
DOMAIN,
|
||||
@ -297,7 +298,12 @@ async def async_discover_adapters(
|
||||
|
||||
|
||||
async def async_update_device(
|
||||
hass: HomeAssistant, entry: ConfigEntry, adapter: str, details: AdapterDetails
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
adapter: str,
|
||||
details: AdapterDetails,
|
||||
via_device_domain: str | None = None,
|
||||
via_device_id: str | None = None,
|
||||
) -> None:
|
||||
"""Update device registry entry.
|
||||
|
||||
@ -306,7 +312,8 @@ async def async_update_device(
|
||||
update the device with the new location so they can
|
||||
figure out where the adapter is.
|
||||
"""
|
||||
dr.async_get(hass).async_get_or_create(
|
||||
device_registry = dr.async_get(hass)
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
name=adapter_human_name(adapter, details[ADAPTER_ADDRESS]),
|
||||
connections={(dr.CONNECTION_BLUETOOTH, details[ADAPTER_ADDRESS])},
|
||||
@ -315,6 +322,10 @@ async def async_update_device(
|
||||
sw_version=details.get(ADAPTER_SW_VERSION),
|
||||
hw_version=details.get(ADAPTER_HW_VERSION),
|
||||
)
|
||||
if via_device_id:
|
||||
device_registry.async_update_device(
|
||||
device_entry.id, via_device_id=via_device_id
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
@ -349,6 +360,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
entry,
|
||||
source_entry.title,
|
||||
details,
|
||||
source_domain,
|
||||
entry.data.get(CONF_SOURCE_DEVICE_ID),
|
||||
)
|
||||
return True
|
||||
manager = _get_manager(hass)
|
||||
|
@ -181,10 +181,16 @@ def async_register_scanner(
|
||||
source_domain: str | None = None,
|
||||
source_model: str | None = None,
|
||||
source_config_entry_id: str | None = None,
|
||||
source_device_id: str | None = None,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Register a BleakScanner."""
|
||||
return _get_manager(hass).async_register_hass_scanner(
|
||||
scanner, connection_slots, source_domain, source_model, source_config_entry_id
|
||||
scanner,
|
||||
connection_slots,
|
||||
source_domain,
|
||||
source_model,
|
||||
source_config_entry_id,
|
||||
source_device_id,
|
||||
)
|
||||
|
||||
|
||||
|
@ -37,6 +37,7 @@ from .const import (
|
||||
CONF_PASSIVE,
|
||||
CONF_SOURCE,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||
CONF_SOURCE_DEVICE_ID,
|
||||
CONF_SOURCE_DOMAIN,
|
||||
CONF_SOURCE_MODEL,
|
||||
DOMAIN,
|
||||
@ -194,6 +195,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_SOURCE_MODEL: user_input[CONF_SOURCE_MODEL],
|
||||
CONF_SOURCE_DOMAIN: user_input[CONF_SOURCE_DOMAIN],
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID: user_input[CONF_SOURCE_CONFIG_ENTRY_ID],
|
||||
CONF_SOURCE_DEVICE_ID: user_input[CONF_SOURCE_DEVICE_ID],
|
||||
}
|
||||
self._abort_if_unique_id_configured(updates=data)
|
||||
manager = get_manager()
|
||||
|
@ -22,7 +22,7 @@ CONF_SOURCE: Final = "source"
|
||||
CONF_SOURCE_DOMAIN: Final = "source_domain"
|
||||
CONF_SOURCE_MODEL: Final = "source_model"
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID: Final = "source_config_entry_id"
|
||||
|
||||
CONF_SOURCE_DEVICE_ID: Final = "source_device_id"
|
||||
|
||||
SOURCE_LOCAL: Final = "local"
|
||||
|
||||
|
@ -25,6 +25,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from .const import (
|
||||
CONF_SOURCE,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||
CONF_SOURCE_DEVICE_ID,
|
||||
CONF_SOURCE_DOMAIN,
|
||||
CONF_SOURCE_MODEL,
|
||||
DOMAIN,
|
||||
@ -254,6 +255,7 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
||||
source_domain: str | None = None,
|
||||
source_model: str | None = None,
|
||||
source_config_entry_id: str | None = None,
|
||||
source_device_id: str | None = None,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Register a scanner."""
|
||||
cancel = self.async_register_scanner(scanner, connection_slots)
|
||||
@ -261,9 +263,6 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
||||
isinstance(scanner, BaseHaRemoteScanner)
|
||||
and source_domain
|
||||
and source_config_entry_id
|
||||
and not self.hass.config_entries.async_entry_for_domain_unique_id(
|
||||
DOMAIN, scanner.source
|
||||
)
|
||||
):
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.flow.async_init(
|
||||
@ -274,6 +273,7 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
||||
CONF_SOURCE_DOMAIN: source_domain,
|
||||
CONF_SOURCE_MODEL: source_model,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID: source_config_entry_id,
|
||||
CONF_SOURCE_DEVICE_ID: source_device_id,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
@ -28,6 +28,7 @@ def async_connect_scanner(
|
||||
entry_data: RuntimeEntryData,
|
||||
cli: APIClient,
|
||||
device_info: DeviceInfo,
|
||||
device_id: str,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Connect scanner."""
|
||||
client_data = connect_scanner(cli, device_info, entry_data.available)
|
||||
@ -45,6 +46,7 @@ def async_connect_scanner(
|
||||
source_domain=DOMAIN,
|
||||
source_model=device_info.model,
|
||||
source_config_entry_id=entry_data.entry_id,
|
||||
source_device_id=device_id,
|
||||
),
|
||||
scanner.async_setup(),
|
||||
],
|
||||
|
@ -425,7 +425,9 @@ class ESPHomeManager:
|
||||
|
||||
if device_info.bluetooth_proxy_feature_flags_compat(api_version):
|
||||
entry_data.disconnect_callbacks.add(
|
||||
async_connect_scanner(hass, entry_data, cli, device_info)
|
||||
async_connect_scanner(
|
||||
hass, entry_data, cli, device_info, self.device_id
|
||||
)
|
||||
)
|
||||
else:
|
||||
bluetooth.async_remove_scanner(hass, device_info.mac_address)
|
||||
|
@ -21,6 +21,7 @@ async def async_connect_scanner(
|
||||
hass: HomeAssistant,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
scanner_mode: BLEScannerMode,
|
||||
device_id: str,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Connect scanner."""
|
||||
device = coordinator.device
|
||||
@ -34,6 +35,7 @@ async def async_connect_scanner(
|
||||
source_domain=entry.domain,
|
||||
source_model=coordinator.model,
|
||||
source_config_entry_id=entry.entry_id,
|
||||
source_device_id=device_id,
|
||||
),
|
||||
scanner.async_setup(),
|
||||
coordinator.async_subscribe_events(scanner.async_on_event),
|
||||
|
@ -704,8 +704,11 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
||||
# BLE enable required a reboot, don't bother connecting
|
||||
# the scanner since it will be disconnected anyway
|
||||
return
|
||||
assert self.device_id is not None
|
||||
self._disconnected_callbacks.append(
|
||||
await async_connect_scanner(self.hass, self, ble_scanner_mode)
|
||||
await async_connect_scanner(
|
||||
self.hass, self, ble_scanner_mode, self.device_id
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@ -13,12 +13,14 @@ from homeassistant.components.bluetooth.const import (
|
||||
CONF_PASSIVE,
|
||||
CONF_SOURCE,
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||
CONF_SOURCE_DEVICE_ID,
|
||||
CONF_SOURCE_DOMAIN,
|
||||
CONF_SOURCE_MODEL,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import FakeRemoteScanner, MockBleakClient, _get_manager
|
||||
@ -535,24 +537,22 @@ async def test_async_step_user_linux_adapter_is_ignored(hass: HomeAssistant) ->
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
async def test_async_step_integration_discovery_remote_adapter(
|
||||
hass: HomeAssistant,
|
||||
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
||||
) -> None:
|
||||
"""Test remote adapter configuration via integration discovery."""
|
||||
entry = MockConfigEntry(domain="test")
|
||||
entry.add_to_hass(hass)
|
||||
connector = (
|
||||
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
|
||||
)
|
||||
scanner = FakeRemoteScanner("esp32", "esp32", connector, True)
|
||||
manager = _get_manager()
|
||||
cancel_scanner = manager.async_register_scanner(scanner)
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={("test", "BB:BB:BB:BB:BB:BB")},
|
||||
)
|
||||
|
||||
entry.add_to_hass(hass)
|
||||
with (
|
||||
patch("homeassistant.components.bluetooth.async_setup", return_value=True),
|
||||
patch(
|
||||
"homeassistant.components.bluetooth.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||
@ -561,6 +561,7 @@ async def test_async_step_integration_discovery_remote_adapter(
|
||||
CONF_SOURCE_DOMAIN: "test",
|
||||
CONF_SOURCE_MODEL: "test",
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID: entry.entry_id,
|
||||
CONF_SOURCE_DEVICE_ID: device_entry.id,
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
@ -570,9 +571,22 @@ async def test_async_step_integration_discovery_remote_adapter(
|
||||
CONF_SOURCE_DOMAIN: "test",
|
||||
CONF_SOURCE_MODEL: "test",
|
||||
CONF_SOURCE_CONFIG_ENTRY_ID: entry.entry_id,
|
||||
CONF_SOURCE_DEVICE_ID: device_entry.id,
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
await hass.async_block_till_done()
|
||||
|
||||
new_entry_id: str = result["result"].entry_id
|
||||
new_entry = hass.config_entries.async_get_entry(new_entry_id)
|
||||
assert new_entry is not None
|
||||
assert new_entry.state is config_entries.ConfigEntryState.LOADED
|
||||
|
||||
ble_device_entry = device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_BLUETOOTH, scanner.source)}
|
||||
)
|
||||
assert ble_device_entry is not None
|
||||
assert ble_device_entry.via_device_id == device_entry.id
|
||||
|
||||
await hass.config_entries.async_unload(new_entry.entry_id)
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
cancel_scanner()
|
||||
|
@ -4,6 +4,7 @@ from unittest.mock import patch
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .conftest import MockESPHomeDevice
|
||||
|
||||
@ -48,6 +49,34 @@ async def test_bluetooth_connect_with_legacy_adv(
|
||||
assert scanner.scanning is True
|
||||
|
||||
|
||||
async def test_bluetooth_device_linked_via_device(
|
||||
hass: HomeAssistant,
|
||||
mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test the Bluetooth device is linked to the ESPHome device."""
|
||||
scanner = bluetooth.async_scanner_by_source(hass, "11:22:33:44:55:AA")
|
||||
assert scanner.connectable is True
|
||||
entry = hass.config_entries.async_entry_for_domain_unique_id(
|
||||
"bluetooth", "11:22:33:44:55:AA"
|
||||
)
|
||||
assert entry is not None
|
||||
esp_device = device_registry.async_get_device(
|
||||
connections={
|
||||
(
|
||||
dr.CONNECTION_NETWORK_MAC,
|
||||
mock_bluetooth_entry_with_raw_adv.device_info.mac_address,
|
||||
)
|
||||
}
|
||||
)
|
||||
assert esp_device is not None
|
||||
device = device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_BLUETOOTH, "11:22:33:44:55:AA")}
|
||||
)
|
||||
assert device is not None
|
||||
assert device.via_device_id == esp_device.id
|
||||
|
||||
|
||||
async def test_bluetooth_cleanup_on_remove_entry(
|
||||
hass: HomeAssistant, mock_bluetooth_entry_with_raw_adv: MockESPHomeDevice
|
||||
) -> None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user