Add bluetooth adapter model and manufacturer to config flow (#115780)

* Show bluetooth adapter model and manufacturer in config flow

If there are multiple adapters, it could be a bit difficult
to figure out which one is which

* Show bluetooth adapter model and manufacturer in config flow

If there are multiple adapters, it could be a bit difficult
to figure out which one is which

* reorder

* reorder

* names

* remove

* fix incomplete mocking

* more missing mocks
This commit is contained in:
J. Nick Koston 2024-04-18 09:39:32 -05:00 committed by GitHub
parent fbdef7f5cd
commit 53c48537d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 102 additions and 46 deletions

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -11,7 +11,7 @@
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
}
},
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"error": {
"unknown": "[%key:common::config_flow::error::unknown%]"
},

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -6,8 +6,10 @@ from typing import Any, cast
from bluetooth_adapters import (
ADAPTER_ADDRESS,
ADAPTER_MANUFACTURER,
AdapterDetails,
adapter_human_name,
adapter_model,
adapter_unique_name,
get_adapters,
)
@ -35,6 +37,22 @@ OPTIONS_FLOW = {
}
def adapter_display_info(adapter: str, details: AdapterDetails) -> str:
"""Return the adapter display info."""
name = adapter_human_name(adapter, details[ADAPTER_ADDRESS])
model = adapter_model(details)
manufacturer = details[ADAPTER_MANUFACTURER] or "Unknown"
return f"{name} {manufacturer} {model}"
def adapter_title(adapter: str, details: AdapterDetails) -> str:
"""Return the adapter title."""
unique_name = adapter_unique_name(adapter, details[ADAPTER_ADDRESS])
model = adapter_model(details)
manufacturer = details[ADAPTER_MANUFACTURER] or "Unknown"
return f"{manufacturer} {model} ({unique_name})"
class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
"""Config flow for Bluetooth."""
@ -45,6 +63,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
self._adapter: str | None = None
self._details: AdapterDetails | None = None
self._adapters: dict[str, AdapterDetails] = {}
self._placeholders: dict[str, str] = {}
async def async_step_integration_discovery(
self, discovery_info: DiscoveryInfoType
@ -54,11 +73,23 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
self._details = cast(AdapterDetails, discovery_info[CONF_DETAILS])
await self.async_set_unique_id(self._details[ADAPTER_ADDRESS])
self._abort_if_unique_id_configured()
self.context["title_placeholders"] = {
"name": adapter_human_name(self._adapter, self._details[ADAPTER_ADDRESS])
}
details = self._details
self._async_set_adapter_info(self._adapter, details)
return await self.async_step_single_adapter()
@callback
def _async_set_adapter_info(self, adapter: str, details: AdapterDetails) -> None:
"""Set the adapter info."""
name = adapter_human_name(adapter, details[ADAPTER_ADDRESS])
model = adapter_model(details)
manufacturer = details[ADAPTER_MANUFACTURER]
self._placeholders = {
"name": name,
"model": model,
"manufacturer": manufacturer or "Unknown",
}
self.context["title_placeholders"] = self._placeholders
async def async_step_single_adapter(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@ -67,6 +98,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
details = self._details
assert adapter is not None
assert details is not None
assert self._placeholders is not None
address = details[ADAPTER_ADDRESS]
@ -74,12 +106,12 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(address, raise_on_progress=False)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=adapter_unique_name(adapter, address), data={}
title=adapter_title(adapter, details), data={}
)
return self.async_show_form(
step_id="single_adapter",
description_placeholders={"name": adapter_human_name(adapter, address)},
description_placeholders=self._placeholders,
)
async def async_step_multiple_adapters(
@ -89,11 +121,12 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
assert self._adapters is not None
adapter = user_input[CONF_ADAPTER]
address = self._adapters[adapter][ADAPTER_ADDRESS]
details = self._adapters[adapter]
address = details[ADAPTER_ADDRESS]
await self.async_set_unique_id(address, raise_on_progress=False)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=adapter_unique_name(adapter, address), data={}
title=adapter_title(adapter, details), data={}
)
configured_addresses = self._async_current_ids()
@ -116,6 +149,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
if len(unconfigured_adapters) == 1:
self._adapter = list(self._adapters)[0]
self._details = self._adapters[self._adapter]
self._async_set_adapter_info(self._adapter, self._details)
return await self.async_step_single_adapter()
return self.async_show_form(
@ -124,8 +158,8 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
{
vol.Required(CONF_ADAPTER): vol.In(
{
adapter: adapter_human_name(
adapter, self._adapters[adapter][ADAPTER_ADDRESS]
adapter: adapter_display_info(
adapter, self._adapters[adapter]
)
for adapter in sorted(unconfigured_adapters)
}

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "{name}",
"flow_title": "{name} {manufacturer} {model}",
"step": {
"user": {
"description": "Choose a device to set up",
@ -18,7 +18,7 @@
}
},
"single_adapter": {
"description": "Do you want to set up the Bluetooth adapter {name}?"
"description": "Do you want to set up the Bluetooth adapter {name} {manufacturer} {model}?"
}
},
"abort": {

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "What is the IRK (Identity Resolving Key) of the BLE device you want to track?",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -1,6 +1,6 @@
{
"config": {
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
"flow_title": "{name}",
"step": {
"user": {
"description": "[%key:component::bluetooth::config::step::user::description%]",

View File

@ -65,7 +65,7 @@ async def test_async_step_user_macos(hass: HomeAssistant, macos_adapter: None) -
result["flow_id"], user_input={}
)
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "Core Bluetooth"
assert result2["title"] == "Apple Unknown MacOS Model (Core Bluetooth)"
assert result2["data"] == {}
assert len(mock_setup_entry.mock_calls) == 1
@ -81,6 +81,11 @@ async def test_async_step_user_linux_one_adapter(
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "single_adapter"
assert result["description_placeholders"] == {
"name": "hci0 (00:00:00:00:00:01)",
"model": "Bluetooth Adapter 5.0 (cc01:aa01)",
"manufacturer": "ACME",
}
with (
patch("homeassistant.components.bluetooth.async_setup", return_value=True),
patch(
@ -91,7 +96,9 @@ async def test_async_step_user_linux_one_adapter(
result["flow_id"], user_input={}
)
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "00:00:00:00:00:01"
assert (
result2["title"] == "ACME Bluetooth Adapter 5.0 (cc01:aa01) (00:00:00:00:00:01)"
)
assert result2["data"] == {}
assert len(mock_setup_entry.mock_calls) == 1
@ -107,6 +114,10 @@ async def test_async_step_user_linux_two_adapters(
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "multiple_adapters"
assert result["data_schema"].schema["adapter"].container == {
"hci0": "hci0 (00:00:00:00:00:01) ACME Bluetooth Adapter 5.0 (cc01:aa01)",
"hci1": "hci1 (00:00:00:00:00:02) ACME Bluetooth Adapter 5.0 (cc01:aa01)",
}
with (
patch("homeassistant.components.bluetooth.async_setup", return_value=True),
patch(
@ -117,7 +128,9 @@ async def test_async_step_user_linux_two_adapters(
result["flow_id"], user_input={CONF_ADAPTER: "hci1"}
)
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "00:00:00:00:00:02"
assert (
result2["title"] == "ACME Bluetooth Adapter 5.0 (cc01:aa01) (00:00:00:00:00:02)"
)
assert result2["data"] == {}
assert len(mock_setup_entry.mock_calls) == 1
@ -153,6 +166,11 @@ async def test_async_step_integration_discovery(hass: HomeAssistant) -> None:
data={CONF_ADAPTER: "hci0", CONF_DETAILS: details},
)
assert result["type"] is FlowResultType.FORM
assert result["description_placeholders"] == {
"name": "hci0 (00:00:00:00:00:01)",
"model": "Unknown",
"manufacturer": "ACME",
}
assert result["step_id"] == "single_adapter"
with (
patch("homeassistant.components.bluetooth.async_setup", return_value=True),
@ -164,7 +182,7 @@ async def test_async_step_integration_discovery(hass: HomeAssistant) -> None:
result["flow_id"], user_input={}
)
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "00:00:00:00:00:01"
assert result2["title"] == "ACME Unknown (00:00:00:00:00:01)"
assert result2["data"] == {}
assert len(mock_setup_entry.mock_calls) == 1
@ -196,7 +214,7 @@ async def test_async_step_integration_discovery_during_onboarding_one_adapter(
data={CONF_ADAPTER: "hci0", CONF_DETAILS: details},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "00:00:00:00:00:01"
assert result["title"] == "ACME Unknown (00:00:00:00:00:01)"
assert result["data"] == {}
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_onboarding.mock_calls) == 1
@ -240,11 +258,11 @@ async def test_async_step_integration_discovery_during_onboarding_two_adapters(
data={CONF_ADAPTER: "hci1", CONF_DETAILS: details2},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "00:00:00:00:00:01"
assert result["title"] == "ACME Unknown (00:00:00:00:00:01)"
assert result["data"] == {}
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "00:00:00:00:00:02"
assert result2["title"] == "ACME Unknown (00:00:00:00:00:02)"
assert result2["data"] == {}
assert len(mock_setup_entry.mock_calls) == 2
@ -278,7 +296,7 @@ async def test_async_step_integration_discovery_during_onboarding(
data={CONF_ADAPTER: "Core Bluetooth", CONF_DETAILS: details},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "Core Bluetooth"
assert result["title"] == "ACME Unknown (Core Bluetooth)"
assert result["data"] == {}
assert len(mock_setup_entry.mock_calls) == 1
assert len(mock_onboarding.mock_calls) == 1

View File

@ -3015,12 +3015,14 @@ async def test_discover_new_usb_adapters(
"hw_version": "usb:v1D6Bp0246d053F",
"passive_scan": False,
"sw_version": "homeassistant",
"manufacturer": "ACME",
},
"hci1": {
"address": "00:00:00:00:00:02",
"hw_version": "usb:v1D6Bp0246d053F",
"passive_scan": False,
"sw_version": "homeassistant",
"manufacturer": "ACME",
},
},
),
@ -3088,12 +3090,14 @@ async def test_discover_new_usb_adapters_with_firmware_fallback_delay(
"hw_version": "usb:v1D6Bp0246d053F",
"passive_scan": False,
"sw_version": "homeassistant",
"manufacturer": "ACME",
},
"hci1": {
"address": "00:00:00:00:00:02",
"hw_version": "usb:v1D6Bp0246d053F",
"passive_scan": False,
"sw_version": "homeassistant",
"manufacturer": "ACME",
},
},
),