mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +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_DETAILS,
|
||||||
CONF_PASSIVE,
|
CONF_PASSIVE,
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||||
|
CONF_SOURCE_DEVICE_ID,
|
||||||
CONF_SOURCE_DOMAIN,
|
CONF_SOURCE_DOMAIN,
|
||||||
CONF_SOURCE_MODEL,
|
CONF_SOURCE_MODEL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -297,7 +298,12 @@ async def async_discover_adapters(
|
|||||||
|
|
||||||
|
|
||||||
async def async_update_device(
|
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:
|
) -> None:
|
||||||
"""Update device registry entry.
|
"""Update device registry entry.
|
||||||
|
|
||||||
@ -306,7 +312,8 @@ async def async_update_device(
|
|||||||
update the device with the new location so they can
|
update the device with the new location so they can
|
||||||
figure out where the adapter is.
|
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,
|
config_entry_id=entry.entry_id,
|
||||||
name=adapter_human_name(adapter, details[ADAPTER_ADDRESS]),
|
name=adapter_human_name(adapter, details[ADAPTER_ADDRESS]),
|
||||||
connections={(dr.CONNECTION_BLUETOOTH, 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),
|
sw_version=details.get(ADAPTER_SW_VERSION),
|
||||||
hw_version=details.get(ADAPTER_HW_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:
|
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,
|
entry,
|
||||||
source_entry.title,
|
source_entry.title,
|
||||||
details,
|
details,
|
||||||
|
source_domain,
|
||||||
|
entry.data.get(CONF_SOURCE_DEVICE_ID),
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
manager = _get_manager(hass)
|
manager = _get_manager(hass)
|
||||||
|
@ -181,10 +181,16 @@ def async_register_scanner(
|
|||||||
source_domain: str | None = None,
|
source_domain: str | None = None,
|
||||||
source_model: str | None = None,
|
source_model: str | None = None,
|
||||||
source_config_entry_id: str | None = None,
|
source_config_entry_id: str | None = None,
|
||||||
|
source_device_id: str | None = None,
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Register a BleakScanner."""
|
"""Register a BleakScanner."""
|
||||||
return _get_manager(hass).async_register_hass_scanner(
|
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_PASSIVE,
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||||
|
CONF_SOURCE_DEVICE_ID,
|
||||||
CONF_SOURCE_DOMAIN,
|
CONF_SOURCE_DOMAIN,
|
||||||
CONF_SOURCE_MODEL,
|
CONF_SOURCE_MODEL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -194,6 +195,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_SOURCE_MODEL: user_input[CONF_SOURCE_MODEL],
|
CONF_SOURCE_MODEL: user_input[CONF_SOURCE_MODEL],
|
||||||
CONF_SOURCE_DOMAIN: user_input[CONF_SOURCE_DOMAIN],
|
CONF_SOURCE_DOMAIN: user_input[CONF_SOURCE_DOMAIN],
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID: user_input[CONF_SOURCE_CONFIG_ENTRY_ID],
|
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)
|
self._abort_if_unique_id_configured(updates=data)
|
||||||
manager = get_manager()
|
manager = get_manager()
|
||||||
|
@ -22,7 +22,7 @@ CONF_SOURCE: Final = "source"
|
|||||||
CONF_SOURCE_DOMAIN: Final = "source_domain"
|
CONF_SOURCE_DOMAIN: Final = "source_domain"
|
||||||
CONF_SOURCE_MODEL: Final = "source_model"
|
CONF_SOURCE_MODEL: Final = "source_model"
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID: Final = "source_config_entry_id"
|
CONF_SOURCE_CONFIG_ENTRY_ID: Final = "source_config_entry_id"
|
||||||
|
CONF_SOURCE_DEVICE_ID: Final = "source_device_id"
|
||||||
|
|
||||||
SOURCE_LOCAL: Final = "local"
|
SOURCE_LOCAL: Final = "local"
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||||||
from .const import (
|
from .const import (
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||||
|
CONF_SOURCE_DEVICE_ID,
|
||||||
CONF_SOURCE_DOMAIN,
|
CONF_SOURCE_DOMAIN,
|
||||||
CONF_SOURCE_MODEL,
|
CONF_SOURCE_MODEL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -254,6 +255,7 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
|||||||
source_domain: str | None = None,
|
source_domain: str | None = None,
|
||||||
source_model: str | None = None,
|
source_model: str | None = None,
|
||||||
source_config_entry_id: str | None = None,
|
source_config_entry_id: str | None = None,
|
||||||
|
source_device_id: str | None = None,
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Register a scanner."""
|
"""Register a scanner."""
|
||||||
cancel = self.async_register_scanner(scanner, connection_slots)
|
cancel = self.async_register_scanner(scanner, connection_slots)
|
||||||
@ -261,9 +263,6 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
|||||||
isinstance(scanner, BaseHaRemoteScanner)
|
isinstance(scanner, BaseHaRemoteScanner)
|
||||||
and source_domain
|
and source_domain
|
||||||
and source_config_entry_id
|
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.async_create_task(
|
||||||
self.hass.config_entries.flow.async_init(
|
self.hass.config_entries.flow.async_init(
|
||||||
@ -274,6 +273,7 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
|||||||
CONF_SOURCE_DOMAIN: source_domain,
|
CONF_SOURCE_DOMAIN: source_domain,
|
||||||
CONF_SOURCE_MODEL: source_model,
|
CONF_SOURCE_MODEL: source_model,
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID: source_config_entry_id,
|
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,
|
entry_data: RuntimeEntryData,
|
||||||
cli: APIClient,
|
cli: APIClient,
|
||||||
device_info: DeviceInfo,
|
device_info: DeviceInfo,
|
||||||
|
device_id: str,
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Connect scanner."""
|
"""Connect scanner."""
|
||||||
client_data = connect_scanner(cli, device_info, entry_data.available)
|
client_data = connect_scanner(cli, device_info, entry_data.available)
|
||||||
@ -45,6 +46,7 @@ def async_connect_scanner(
|
|||||||
source_domain=DOMAIN,
|
source_domain=DOMAIN,
|
||||||
source_model=device_info.model,
|
source_model=device_info.model,
|
||||||
source_config_entry_id=entry_data.entry_id,
|
source_config_entry_id=entry_data.entry_id,
|
||||||
|
source_device_id=device_id,
|
||||||
),
|
),
|
||||||
scanner.async_setup(),
|
scanner.async_setup(),
|
||||||
],
|
],
|
||||||
|
@ -425,7 +425,9 @@ class ESPHomeManager:
|
|||||||
|
|
||||||
if device_info.bluetooth_proxy_feature_flags_compat(api_version):
|
if device_info.bluetooth_proxy_feature_flags_compat(api_version):
|
||||||
entry_data.disconnect_callbacks.add(
|
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:
|
else:
|
||||||
bluetooth.async_remove_scanner(hass, device_info.mac_address)
|
bluetooth.async_remove_scanner(hass, device_info.mac_address)
|
||||||
|
@ -21,6 +21,7 @@ async def async_connect_scanner(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
coordinator: ShellyRpcCoordinator,
|
coordinator: ShellyRpcCoordinator,
|
||||||
scanner_mode: BLEScannerMode,
|
scanner_mode: BLEScannerMode,
|
||||||
|
device_id: str,
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Connect scanner."""
|
"""Connect scanner."""
|
||||||
device = coordinator.device
|
device = coordinator.device
|
||||||
@ -34,6 +35,7 @@ async def async_connect_scanner(
|
|||||||
source_domain=entry.domain,
|
source_domain=entry.domain,
|
||||||
source_model=coordinator.model,
|
source_model=coordinator.model,
|
||||||
source_config_entry_id=entry.entry_id,
|
source_config_entry_id=entry.entry_id,
|
||||||
|
source_device_id=device_id,
|
||||||
),
|
),
|
||||||
scanner.async_setup(),
|
scanner.async_setup(),
|
||||||
coordinator.async_subscribe_events(scanner.async_on_event),
|
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
|
# BLE enable required a reboot, don't bother connecting
|
||||||
# the scanner since it will be disconnected anyway
|
# the scanner since it will be disconnected anyway
|
||||||
return
|
return
|
||||||
|
assert self.device_id is not None
|
||||||
self._disconnected_callbacks.append(
|
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
|
@callback
|
||||||
|
@ -13,12 +13,14 @@ from homeassistant.components.bluetooth.const import (
|
|||||||
CONF_PASSIVE,
|
CONF_PASSIVE,
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID,
|
CONF_SOURCE_CONFIG_ENTRY_ID,
|
||||||
|
CONF_SOURCE_DEVICE_ID,
|
||||||
CONF_SOURCE_DOMAIN,
|
CONF_SOURCE_DOMAIN,
|
||||||
CONF_SOURCE_MODEL,
|
CONF_SOURCE_MODEL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from . import FakeRemoteScanner, MockBleakClient, _get_manager
|
from . import FakeRemoteScanner, MockBleakClient, _get_manager
|
||||||
@ -535,34 +537,33 @@ async def test_async_step_user_linux_adapter_is_ignored(hass: HomeAssistant) ->
|
|||||||
|
|
||||||
@pytest.mark.usefixtures("enable_bluetooth")
|
@pytest.mark.usefixtures("enable_bluetooth")
|
||||||
async def test_async_step_integration_discovery_remote_adapter(
|
async def test_async_step_integration_discovery_remote_adapter(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test remote adapter configuration via integration discovery."""
|
"""Test remote adapter configuration via integration discovery."""
|
||||||
entry = MockConfigEntry(domain="test")
|
entry = MockConfigEntry(domain="test")
|
||||||
|
entry.add_to_hass(hass)
|
||||||
connector = (
|
connector = (
|
||||||
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
|
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
|
||||||
)
|
)
|
||||||
scanner = FakeRemoteScanner("esp32", "esp32", connector, True)
|
scanner = FakeRemoteScanner("esp32", "esp32", connector, True)
|
||||||
manager = _get_manager()
|
manager = _get_manager()
|
||||||
cancel_scanner = manager.async_register_scanner(scanner)
|
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)
|
result = await hass.config_entries.flow.async_init(
|
||||||
with (
|
DOMAIN,
|
||||||
patch("homeassistant.components.bluetooth.async_setup", return_value=True),
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
patch(
|
data={
|
||||||
"homeassistant.components.bluetooth.async_setup_entry", return_value=True
|
CONF_SOURCE: scanner.source,
|
||||||
) as mock_setup_entry,
|
CONF_SOURCE_DOMAIN: "test",
|
||||||
):
|
CONF_SOURCE_MODEL: "test",
|
||||||
result = await hass.config_entries.flow.async_init(
|
CONF_SOURCE_CONFIG_ENTRY_ID: entry.entry_id,
|
||||||
DOMAIN,
|
CONF_SOURCE_DEVICE_ID: device_entry.id,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
},
|
||||||
data={
|
)
|
||||||
CONF_SOURCE: scanner.source,
|
|
||||||
CONF_SOURCE_DOMAIN: "test",
|
|
||||||
CONF_SOURCE_MODEL: "test",
|
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID: entry.entry_id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result["title"] == "esp32"
|
assert result["title"] == "esp32"
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
@ -570,9 +571,22 @@ async def test_async_step_integration_discovery_remote_adapter(
|
|||||||
CONF_SOURCE_DOMAIN: "test",
|
CONF_SOURCE_DOMAIN: "test",
|
||||||
CONF_SOURCE_MODEL: "test",
|
CONF_SOURCE_MODEL: "test",
|
||||||
CONF_SOURCE_CONFIG_ENTRY_ID: entry.entry_id,
|
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()
|
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.config_entries.async_unload(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
cancel_scanner()
|
cancel_scanner()
|
||||||
|
@ -4,6 +4,7 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
from homeassistant.components import bluetooth
|
from homeassistant.components import bluetooth
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from .conftest import MockESPHomeDevice
|
from .conftest import MockESPHomeDevice
|
||||||
|
|
||||||
@ -48,6 +49,34 @@ async def test_bluetooth_connect_with_legacy_adv(
|
|||||||
assert scanner.scanning is True
|
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(
|
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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user