Small cleanups to Yale Access Bluetooth (#76691)

- Abort the discovery flow if the user has already
  started interacting with a user flow or bluetooth
  discovery

- Remove docs_url from the flow

- Fix useless return
This commit is contained in:
J. Nick Koston 2022-08-12 21:55:48 -10:00 committed by GitHub
parent 6e03b12a93
commit 58883feaf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 21 deletions

View File

@ -16,7 +16,7 @@ from yalexs_ble import (
) )
from yalexs_ble.const import YALE_MFR_ID from yalexs_ble.const import YALE_MFR_ID
from homeassistant import config_entries from homeassistant import config_entries, data_entry_flow
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak, BluetoothServiceInfoBleak,
async_discovered_service_info, async_discovered_service_info,
@ -25,7 +25,6 @@ from homeassistant.const import CONF_ADDRESS
from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.data_entry_flow import AbortFlow, FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.helpers.typing import DiscoveryInfoType
from homeassistant.loader import async_get_integration
from .const import CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DOMAIN from .const import CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DOMAIN
from .util import async_get_service_info, human_readable_name from .util import async_get_service_info, human_readable_name
@ -85,39 +84,52 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
discovery_info["key"], discovery_info["key"],
discovery_info["slot"], discovery_info["slot"],
) )
address = lock_cfg.address
local_name = lock_cfg.local_name
hass = self.hass
# We do not want to raise on progress as integration_discovery takes # We do not want to raise on progress as integration_discovery takes
# precedence over other discovery flows since we already have the keys. # precedence over other discovery flows since we already have the keys.
await self.async_set_unique_id(lock_cfg.address, raise_on_progress=False) #
# After we do discovery we will abort the flows that do not have the keys
# below unless the user is already setting them up.
await self.async_set_unique_id(address, raise_on_progress=False)
new_data = {CONF_KEY: lock_cfg.key, CONF_SLOT: lock_cfg.slot} new_data = {CONF_KEY: lock_cfg.key, CONF_SLOT: lock_cfg.slot}
self._abort_if_unique_id_configured(updates=new_data) self._abort_if_unique_id_configured(updates=new_data)
for entry in self._async_current_entries(): for entry in self._async_current_entries():
if entry.data.get(CONF_LOCAL_NAME) == lock_cfg.local_name: if entry.data.get(CONF_LOCAL_NAME) == lock_cfg.local_name:
if self.hass.config_entries.async_update_entry( if hass.config_entries.async_update_entry(
entry, data={**entry.data, **new_data} entry, data={**entry.data, **new_data}
): ):
self.hass.async_create_task( hass.async_create_task(
self.hass.config_entries.async_reload(entry.entry_id) hass.config_entries.async_reload(entry.entry_id)
) )
raise AbortFlow(reason="already_configured") raise AbortFlow(reason="already_configured")
try: try:
self._discovery_info = await async_get_service_info( self._discovery_info = await async_get_service_info(
self.hass, lock_cfg.local_name, lock_cfg.address hass, local_name, address
) )
except asyncio.TimeoutError: except asyncio.TimeoutError:
return self.async_abort(reason="no_devices_found") return self.async_abort(reason="no_devices_found")
# Integration discovery should abort other flows unless they
# are already in the process of being set up since this discovery
# will already have all the keys and the user can simply confirm.
for progress in self._async_in_progress(include_uninitialized=True): for progress in self._async_in_progress(include_uninitialized=True):
# Integration discovery should abort other discovery types
# since it already has the keys and slots, and the other
# discovery types do not.
context = progress["context"] context = progress["context"]
if ( if (
not context.get("active") local_name_is_unique(local_name)
and context.get("local_name") == lock_cfg.local_name and context.get("local_name") == local_name
or context.get("unique_id") == lock_cfg.address ) or context.get("unique_id") == address:
): if context.get("active"):
self.hass.config_entries.flow.async_abort(progress["flow_id"]) # The user has already started interacting with this flow
# and entered the keys. We abort the discovery flow since
# we assume they do not want to use the discovered keys for
# some reason.
raise data_entry_flow.AbortFlow("already_in_progress")
hass.config_entries.flow.async_abort(progress["flow_id"])
self._lock_cfg = lock_cfg self._lock_cfg = lock_cfg
self.context["title_placeholders"] = { self.context["title_placeholders"] = {
@ -228,12 +240,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
vol.Required(CONF_SLOT): int, vol.Required(CONF_SLOT): int,
} }
) )
integration = await async_get_integration(self.hass, DOMAIN)
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user",
data_schema=data_schema, data_schema=data_schema,
errors=errors, errors=errors,
description_placeholders={"docs_url": integration.documentation},
) )

View File

@ -55,8 +55,8 @@ class YaleXSBLELock(YALEXSBLEEntity, LockEntity):
async def async_unlock(self, **kwargs: Any) -> None: async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the lock.""" """Unlock the lock."""
return await self._device.unlock() await self._device.unlock()
async def async_lock(self, **kwargs: Any) -> None: async def async_lock(self, **kwargs: Any) -> None:
"""Lock the lock.""" """Lock the lock."""
return await self._device.lock() await self._device.lock()

View File

@ -3,7 +3,7 @@
"flow_title": "{name}", "flow_title": "{name}",
"step": { "step": {
"user": { "user": {
"description": "Check the documentation at {docs_url} for how to find the offline key.", "description": "Check the documentation for how to find the offline key.",
"data": { "data": {
"address": "Bluetooth address", "address": "Bluetooth address",
"key": "Offline Key (32-byte hex string)", "key": "Offline Key (32-byte hex string)",
@ -22,6 +22,7 @@
"invalid_key_index": "The offline key slot must be an integer between 0 and 255." "invalid_key_index": "The offline key slot must be an integer between 0 and 255."
}, },
"abort": { "abort": {
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_unconfigured_devices": "No unconfigured devices found.", "no_unconfigured_devices": "No unconfigured devices found.",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"

View File

@ -2,6 +2,7 @@
"config": { "config": {
"abort": { "abort": {
"already_configured": "Device is already configured", "already_configured": "Device is already configured",
"already_in_progress": "Configuration flow is already in progress",
"no_devices_found": "No devices found on the network", "no_devices_found": "No devices found on the network",
"no_unconfigured_devices": "No unconfigured devices found." "no_unconfigured_devices": "No unconfigured devices found."
}, },
@ -23,7 +24,7 @@
"key": "Offline Key (32-byte hex string)", "key": "Offline Key (32-byte hex string)",
"slot": "Offline Key Slot (Integer between 0 and 255)" "slot": "Offline Key Slot (Integer between 0 and 255)"
}, },
"description": "Check the documentation at {docs_url} for how to find the offline key." "description": "Check the documentation for how to find the offline key."
} }
} }
} }

View File

@ -752,3 +752,83 @@ async def test_integration_discovery_takes_precedence_over_bluetooth_non_unique_
if flow["handler"] == DOMAIN if flow["handler"] == DOMAIN
] ]
assert len(flows) == 1 assert len(flows) == 1
async def test_user_is_setting_up_lock_and_discovery_happens_in_the_middle(
hass: HomeAssistant,
) -> None:
"""Test that the user is setting up the lock and waiting for validation and the keys get discovered.
In this case the integration discovery should abort and let the user continue setting up the lock.
"""
with patch(
"homeassistant.components.yalexs_ble.config_flow.async_discovered_service_info",
return_value=[NOT_YALE_DISCOVERY_INFO, YALE_ACCESS_LOCK_DISCOVERY_INFO],
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
user_flow_event = asyncio.Event()
valdidate_started = asyncio.Event()
async def _wait_for_user_flow():
valdidate_started.set()
await user_flow_event.wait()
with patch(
"homeassistant.components.yalexs_ble.config_flow.PushLock.validate",
side_effect=_wait_for_user_flow,
), patch(
"homeassistant.components.yalexs_ble.async_setup_entry",
return_value=True,
) as mock_setup_entry:
user_flow_task = asyncio.create_task(
hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_ADDRESS: YALE_ACCESS_LOCK_DISCOVERY_INFO.address,
CONF_KEY: "2fd51b8621c6a139eaffbedcb846b60f",
CONF_SLOT: 66,
},
)
)
await valdidate_started.wait()
with patch(
"homeassistant.components.yalexs_ble.util.async_process_advertisements",
return_value=LOCK_DISCOVERY_INFO_UUID_ADDRESS,
):
discovery_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
data={
"name": "Front Door",
"address": OLD_FIRMWARE_LOCK_DISCOVERY_INFO.address,
"key": "2fd51b8621c6a139eaffbedcb846b60f",
"slot": 66,
"serial": "M1XXX012LU",
},
)
await hass.async_block_till_done()
assert discovery_result["type"] == FlowResultType.ABORT
assert discovery_result["reason"] == "already_in_progress"
user_flow_event.set()
user_flow_result = await user_flow_task
assert user_flow_result["type"] == FlowResultType.CREATE_ENTRY
assert user_flow_result["title"] == YALE_ACCESS_LOCK_DISCOVERY_INFO.name
assert user_flow_result["data"] == {
CONF_LOCAL_NAME: YALE_ACCESS_LOCK_DISCOVERY_INFO.name,
CONF_ADDRESS: YALE_ACCESS_LOCK_DISCOVERY_INFO.address,
CONF_KEY: "2fd51b8621c6a139eaffbedcb846b60f",
CONF_SLOT: 66,
}
assert (
user_flow_result["result"].unique_id == YALE_ACCESS_LOCK_DISCOVERY_INFO.address
)
assert len(mock_setup_entry.mock_calls) == 1