Fix ElkM1 systems that do not use password authentication (#67194)

This commit is contained in:
J. Nick Koston 2022-02-24 09:07:17 -10:00 committed by GitHub
parent 4dc6aab17e
commit 00c6e30988
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 9 deletions

View File

@ -228,7 +228,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.debug("Setting up elkm1 %s", conf["host"])
if not entry.unique_id or ":" not in entry.unique_id and is_ip_address(host):
if (not entry.unique_id or ":" not in entry.unique_id) and is_ip_address(host):
_LOGGER.debug(
"Unique id for %s is missing during setup, trying to fill from discovery",
host,
)
if device := await async_discover_device(hass, host):
async_update_entry_from_discovery(hass, entry, device)
@ -276,7 +280,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try:
if not await async_wait_for_elk_to_sync(
elk, LOGIN_TIMEOUT, SYNC_TIMEOUT, conf[CONF_HOST]
elk, LOGIN_TIMEOUT, SYNC_TIMEOUT, bool(conf[CONF_USERNAME])
):
return False
except asyncio.TimeoutError as exc:
@ -327,7 +331,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_wait_for_elk_to_sync(
elk: elkm1.Elk, login_timeout: int, sync_timeout: int, conf_host: str
elk: elkm1.Elk,
login_timeout: int,
sync_timeout: int,
password_auth: bool,
) -> bool:
"""Wait until the elk has finished sync. Can fail login or timeout."""
@ -353,15 +360,21 @@ async def async_wait_for_elk_to_sync(
success = True
elk.add_handler("login", login_status)
elk.add_handler("sync_complete", sync_complete)
events = ((login_event, login_timeout), (sync_event, sync_timeout))
events = []
if password_auth:
events.append(("login", login_event, login_timeout))
events.append(("sync_complete", sync_event, sync_timeout))
for event, timeout in events:
for name, event, timeout in events:
_LOGGER.debug("Waiting for %s event for %s seconds", name, timeout)
try:
async with async_timeout.timeout(timeout):
await event.wait()
except asyncio.TimeoutError:
_LOGGER.debug("Timed out waiting for %s event", name)
elk.disconnect()
raise
_LOGGER.debug("Received %s event", name)
return success

View File

@ -24,6 +24,7 @@ from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.typing import DiscoveryInfoType
from homeassistant.util import slugify
from homeassistant.util.network import is_ip_address
from . import async_wait_for_elk_to_sync
from .const import CONF_AUTO_CONFIGURE, DISCOVER_SCAN_TIMEOUT, DOMAIN, LOGIN_TIMEOUT
@ -80,7 +81,9 @@ async def validate_input(data: dict[str, str], mac: str | None) -> dict[str, str
)
elk.connect()
if not await async_wait_for_elk_to_sync(elk, LOGIN_TIMEOUT, VALIDATE_TIMEOUT, url):
if not await async_wait_for_elk_to_sync(
elk, LOGIN_TIMEOUT, VALIDATE_TIMEOUT, bool(userid)
):
raise InvalidAuth
short_mac = _short_mac(mac) if mac else None
@ -124,6 +127,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._discovered_device = ElkSystem(
discovery_info.macaddress, discovery_info.ip, 0
)
_LOGGER.debug("Elk discovered from dhcp: %s", self._discovered_device)
return await self._async_handle_discovery()
async def async_step_integration_discovery(
@ -135,6 +139,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
discovery_info["ip_address"],
discovery_info["port"],
)
_LOGGER.debug(
"Elk discovered from integration discovery: %s", self._discovered_device
)
return await self._async_handle_discovery()
async def _async_handle_discovery(self) -> FlowResult:
@ -304,11 +311,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, user_input):
"""Handle import."""
if device := await async_discover_device(
self.hass, urlparse(user_input[CONF_HOST]).hostname
_LOGGER.debug("Elk is importing from yaml")
url = _make_url_from_data(user_input)
if self._url_already_configured(url):
return self.async_abort(reason="address_already_configured")
host = urlparse(url).hostname
_LOGGER.debug(
"Importing is trying to fill unique id from discovery for %s", host
)
if is_ip_address(host) and (
device := await async_discover_device(self.hass, host)
):
await self.async_set_unique_id(dr.format_mac(device.mac_address))
self._abort_if_unique_id_configured()
return (await self._async_create_or_error(user_input, True))[1]
def _url_already_configured(self, url):

View File

@ -9,7 +9,7 @@ from homeassistant.const import ATTR_CODE, CONF_ZONE
DOMAIN = "elkm1"
LOGIN_TIMEOUT = 15
LOGIN_TIMEOUT = 20
CONF_AUTO_CONFIGURE = "auto_configure"
CONF_AREA = "area"

View File

@ -29,9 +29,11 @@ def async_update_entry_from_discovery(
) -> bool:
"""Update a config entry from a discovery."""
if not entry.unique_id or ":" not in entry.unique_id:
_LOGGER.debug("Adding unique id from discovery: %s", device)
return hass.config_entries.async_update_entry(
entry, unique_id=dr.format_mac(device.mac_address)
)
_LOGGER.debug("Unique id is already present from discovery: %s", device)
return False

View File

@ -652,6 +652,50 @@ async def test_form_import_device_discovered(hass):
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_import_existing(hass):
"""Test we abort on existing import."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: f"elks://{MOCK_IP_ADDRESS}"},
unique_id="cc:cc:cc:cc:cc:cc",
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
"host": f"elks://{MOCK_IP_ADDRESS}",
"username": "friend",
"password": "love",
"temperature_unit": "C",
"auto_configure": False,
"keypad": {
"enabled": True,
"exclude": [],
"include": [[1, 1], [2, 2], [3, 3]],
},
"output": {"enabled": False, "exclude": [], "include": []},
"counter": {"enabled": False, "exclude": [], "include": []},
"plc": {"enabled": False, "exclude": [], "include": []},
"prefix": "ohana",
"setting": {"enabled": False, "exclude": [], "include": []},
"area": {"enabled": False, "exclude": [], "include": []},
"task": {"enabled": False, "exclude": [], "include": []},
"thermostat": {"enabled": False, "exclude": [], "include": []},
"zone": {
"enabled": True,
"exclude": [[15, 15], [28, 208]],
"include": [],
},
},
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "address_already_configured"
@pytest.mark.parametrize(
"source, data",
[