Improve Awair config flow (#76838)

This commit is contained in:
Paulus Schoutsen 2022-08-16 08:30:07 -04:00 committed by GitHub
parent 3e1c9f1ac7
commit c7d46bc719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 130 additions and 19 deletions

View File

@ -4,14 +4,15 @@ from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
from typing import Any from typing import Any
from aiohttp.client_exceptions import ClientConnectorError from aiohttp.client_exceptions import ClientError
from python_awair import Awair, AwairLocal, AwairLocalDevice from python_awair import Awair, AwairLocal, AwairLocalDevice
from python_awair.exceptions import AuthError, AwairError from python_awair.exceptions import AuthError, AwairError
import voluptuous as vol import voluptuous as vol
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import SOURCE_ZEROCONF, ConfigFlow
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DEVICE, CONF_HOST
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -40,10 +41,11 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured(error="already_configured_device") self._abort_if_unique_id_configured(error="already_configured_device")
self.context.update( self.context.update(
{ {
"host": host,
"title_placeholders": { "title_placeholders": {
"model": self._device.model, "model": self._device.model,
"device_id": self._device.device_id, "device_id": self._device.device_id,
} },
} }
) )
else: else:
@ -109,31 +111,76 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN):
errors=errors, errors=errors,
) )
async def async_step_local(self, user_input: Mapping[str, Any]) -> FlowResult: @callback
def _get_discovered_entries(self) -> dict[str, str]:
"""Get discovered entries."""
entries: dict[str, str] = {}
for flow in self._async_in_progress():
if flow["context"]["source"] == SOURCE_ZEROCONF:
info = flow["context"]["title_placeholders"]
entries[
flow["context"]["host"]
] = f"{info['model']} ({info['device_id']})"
return entries
async def async_step_local(
self, user_input: Mapping[str, Any] | None = None
) -> FlowResult:
"""Show how to enable local API."""
if user_input is not None:
return await self.async_step_local_pick()
return self.async_show_form(
step_id="local",
description_placeholders={
"url": "https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Element-Local-API-Feature#h_01F40FBBW5323GBPV7D6XMG4J8"
},
)
async def async_step_local_pick(
self, user_input: Mapping[str, Any] | None = None
) -> FlowResult:
"""Handle collecting and verifying Awair Local API hosts.""" """Handle collecting and verifying Awair Local API hosts."""
errors = {} errors = {}
if user_input is not None: # User input is either:
# 1. None if first time on this step
# 2. {device: manual} if picked manual entry option
# 3. {device: <host>} if picked a device
# 4. {host: <host>} if manually entered a host
#
# Option 1 and 2 will show the form again.
if user_input and user_input.get(CONF_DEVICE) != "manual":
if CONF_DEVICE in user_input:
user_input = {CONF_HOST: user_input[CONF_DEVICE]}
self._device, error = await self._check_local_connection( self._device, error = await self._check_local_connection(
user_input[CONF_HOST] user_input.get(CONF_DEVICE) or user_input[CONF_HOST]
) )
if self._device is not None: if self._device is not None:
await self.async_set_unique_id(self._device.mac_address) await self.async_set_unique_id(
self._abort_if_unique_id_configured(error="already_configured_device") self._device.mac_address, raise_on_progress=False
)
title = f"{self._device.model} ({self._device.device_id})" title = f"{self._device.model} ({self._device.device_id})"
return self.async_create_entry(title=title, data=user_input) return self.async_create_entry(title=title, data=user_input)
if error is not None: if error is not None:
errors = {CONF_HOST: error} errors = {"base": error}
discovered = self._get_discovered_entries()
if not discovered or (user_input and user_input.get(CONF_DEVICE) == "manual"):
data_schema = vol.Schema({vol.Required(CONF_HOST): str})
elif discovered:
discovered["manual"] = "Manual"
data_schema = vol.Schema({vol.Required(CONF_DEVICE): vol.In(discovered)})
return self.async_show_form( return self.async_show_form(
step_id="local", step_id="local_pick",
data_schema=vol.Schema({vol.Required(CONF_HOST): str}), data_schema=data_schema,
description_placeholders={
"url": "https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Element-Local-API-Feature"
},
errors=errors, errors=errors,
) )
@ -177,7 +224,7 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN):
devices = await awair.devices() devices = await awair.devices()
return (devices[0], None) return (devices[0], None)
except ClientConnectorError as err: except ClientError as err:
LOGGER.error("Unable to connect error: %s", err) LOGGER.error("Unable to connect error: %s", err)
return (None, "unreachable") return (None, "unreachable")

View File

@ -12,5 +12,10 @@
"type": "_http._tcp.local.", "type": "_http._tcp.local.",
"name": "awair*" "name": "awair*"
} }
],
"dhcp": [
{
"macaddress": "70886B1*"
}
] ]
} }

View File

@ -9,10 +9,13 @@
} }
}, },
"local": { "local": {
"description": "Follow [these instructions]({url}) on how to enable the Awair Local API.\n\nClick submit when done."
},
"local_pick": {
"data": { "data": {
"device": "[%key:common::config_flow::data::device%]",
"host": "[%key:common::config_flow::data::ip%]" "host": "[%key:common::config_flow::data::ip%]"
}, }
"description": "Awair Local API must be enabled following these steps: {url}"
}, },
"reauth_confirm": { "reauth_confirm": {
"description": "Please re-enter your Awair developer access token.", "description": "Please re-enter your Awair developer access token.",

View File

@ -11,6 +11,7 @@ DHCP: list[dict[str, str | bool]] = [
{'domain': 'august', 'hostname': 'connect', 'macaddress': 'B8B7F1*'}, {'domain': 'august', 'hostname': 'connect', 'macaddress': 'B8B7F1*'},
{'domain': 'august', 'hostname': 'connect', 'macaddress': '2C9FFB*'}, {'domain': 'august', 'hostname': 'connect', 'macaddress': '2C9FFB*'},
{'domain': 'august', 'hostname': 'august*', 'macaddress': 'E076D0*'}, {'domain': 'august', 'hostname': 'august*', 'macaddress': 'E076D0*'},
{'domain': 'awair', 'macaddress': '70886B1*'},
{'domain': 'axis', 'registered_devices': True}, {'domain': 'axis', 'registered_devices': True},
{'domain': 'axis', 'hostname': 'axis-00408c*', 'macaddress': '00408C*'}, {'domain': 'axis', 'hostname': 'axis-00408c*', 'macaddress': '00408C*'},
{'domain': 'axis', 'hostname': 'axis-accc8e*', 'macaddress': 'ACCC8E*'}, {'domain': 'axis', 'hostname': 'axis-accc8e*', 'macaddress': 'ACCC8E*'},

View File

@ -26,6 +26,7 @@
"confirm_setup": "Do you want to start set up?" "confirm_setup": "Do you want to start set up?"
}, },
"data": { "data": {
"device": "Device",
"name": "Name", "name": "Name",
"email": "Email", "email": "Email",
"username": "Username", "username": "Username",

View File

@ -240,6 +240,12 @@ async def test_create_local_entry(hass: HomeAssistant, local_devices):
{"next_step_id": "local"}, {"next_step_id": "local"},
) )
# We're being shown the local instructions
form_step = await hass.config_entries.flow.async_configure(
form_step["flow_id"],
{},
)
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
form_step["flow_id"], form_step["flow_id"],
LOCAL_CONFIG, LOCAL_CONFIG,
@ -251,6 +257,48 @@ async def test_create_local_entry(hass: HomeAssistant, local_devices):
assert result["result"].unique_id == LOCAL_UNIQUE_ID assert result["result"].unique_id == LOCAL_UNIQUE_ID
async def test_create_local_entry_from_discovery(hass: HomeAssistant, local_devices):
"""Test local API when device discovered after instructions shown."""
menu_step = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=LOCAL_CONFIG
)
form_step = await hass.config_entries.flow.async_configure(
menu_step["flow_id"],
{"next_step_id": "local"},
)
# Create discovered entry in progress
with patch("python_awair.AwairClient.query", side_effect=[local_devices]):
await hass.config_entries.flow.async_init(
DOMAIN,
data=Mock(host=LOCAL_CONFIG[CONF_HOST]),
context={"source": SOURCE_ZEROCONF},
)
# We're being shown the local instructions
form_step = await hass.config_entries.flow.async_configure(
form_step["flow_id"],
{},
)
with patch("python_awair.AwairClient.query", side_effect=[local_devices]), patch(
"homeassistant.components.awair.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_configure(
form_step["flow_id"],
{"device": LOCAL_CONFIG[CONF_HOST]},
)
print(result)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["title"] == "Awair Element (24947)"
assert result["data"][CONF_HOST] == LOCAL_CONFIG[CONF_HOST]
assert result["result"].unique_id == LOCAL_UNIQUE_ID
async def test_create_local_entry_awair_error(hass: HomeAssistant): async def test_create_local_entry_awair_error(hass: HomeAssistant):
"""Test overall flow when using local API and device is returns error.""" """Test overall flow when using local API and device is returns error."""
@ -267,6 +315,12 @@ async def test_create_local_entry_awair_error(hass: HomeAssistant):
{"next_step_id": "local"}, {"next_step_id": "local"},
) )
# We're being shown the local instructions
form_step = await hass.config_entries.flow.async_configure(
form_step["flow_id"],
{},
)
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
form_step["flow_id"], form_step["flow_id"],
LOCAL_CONFIG, LOCAL_CONFIG,
@ -274,7 +328,7 @@ async def test_create_local_entry_awair_error(hass: HomeAssistant):
# User is returned to form to try again # User is returned to form to try again
assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "local" assert result["step_id"] == "local_pick"
async def test_create_zeroconf_entry(hass: HomeAssistant, local_devices): async def test_create_zeroconf_entry(hass: HomeAssistant, local_devices):