Ensure lutron caseta imports set the unique id (#66754)

This commit is contained in:
J. Nick Koston 2022-02-17 17:03:20 -06:00 committed by GitHub
parent 90a0d5518d
commit 64277058b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 24 additions and 10 deletions

View File

@ -1,4 +1,6 @@
"""Config flow for Lutron Caseta.""" """Config flow for Lutron Caseta."""
from __future__ import annotations
import asyncio import asyncio
import logging import logging
import os import os
@ -17,6 +19,7 @@ from homeassistant.data_entry_flow import FlowResult
from .const import ( from .const import (
ABORT_REASON_CANNOT_CONNECT, ABORT_REASON_CANNOT_CONNECT,
BRIDGE_DEVICE_ID,
BRIDGE_TIMEOUT, BRIDGE_TIMEOUT,
CONF_CA_CERTS, CONF_CA_CERTS,
CONF_CERTFILE, CONF_CERTFILE,
@ -101,7 +104,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if ( if (
not self.attempted_tls_validation not self.attempted_tls_validation
and await self.hass.async_add_executor_job(self._tls_assets_exist) and await self.hass.async_add_executor_job(self._tls_assets_exist)
and await self.async_validate_connectable_bridge_config() and await self.async_get_lutron_id()
): ):
self.tls_assets_validated = True self.tls_assets_validated = True
self.attempted_tls_validation = True self.attempted_tls_validation = True
@ -177,7 +180,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self.data[CONF_CERTFILE] = import_info[CONF_CERTFILE] self.data[CONF_CERTFILE] = import_info[CONF_CERTFILE]
self.data[CONF_CA_CERTS] = import_info[CONF_CA_CERTS] self.data[CONF_CA_CERTS] = import_info[CONF_CA_CERTS]
if not await self.async_validate_connectable_bridge_config(): if not (lutron_id := await self.async_get_lutron_id()):
# Ultimately we won't have a dedicated step for import failure, but # Ultimately we won't have a dedicated step for import failure, but
# in order to keep configuration.yaml-based configs transparently # in order to keep configuration.yaml-based configs transparently
# working without requiring further actions from the user, we don't # working without requiring further actions from the user, we don't
@ -189,6 +192,8 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
# will require users to go through a confirmation flow for imports). # will require users to go through a confirmation flow for imports).
return await self.async_step_import_failed() return await self.async_step_import_failed()
await self.async_set_unique_id(lutron_id, raise_on_progress=False)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=ENTRY_DEFAULT_TITLE, data=self.data) return self.async_create_entry(title=ENTRY_DEFAULT_TITLE, data=self.data)
async def async_step_import_failed(self, user_input=None): async def async_step_import_failed(self, user_input=None):
@ -204,10 +209,8 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_abort(reason=ABORT_REASON_CANNOT_CONNECT) return self.async_abort(reason=ABORT_REASON_CANNOT_CONNECT)
async def async_validate_connectable_bridge_config(self): async def async_get_lutron_id(self) -> str | None:
"""Check if we can connect to the bridge with the current config.""" """Check if we can connect to the bridge with the current config."""
bridge = None
try: try:
bridge = Smartbridge.create_tls( bridge = Smartbridge.create_tls(
hostname=self.data[CONF_HOST], hostname=self.data[CONF_HOST],
@ -220,18 +223,23 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"Invalid certificate used to connect to bridge at %s", "Invalid certificate used to connect to bridge at %s",
self.data[CONF_HOST], self.data[CONF_HOST],
) )
return False return None
connected_ok = False
try: try:
async with async_timeout.timeout(BRIDGE_TIMEOUT): async with async_timeout.timeout(BRIDGE_TIMEOUT):
await bridge.connect() await bridge.connect()
connected_ok = bridge.is_connected()
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.error( _LOGGER.error(
"Timeout while trying to connect to bridge at %s", "Timeout while trying to connect to bridge at %s",
self.data[CONF_HOST], self.data[CONF_HOST],
) )
else:
if not bridge.is_connected():
return None
devices = bridge.get_devices()
bridge_device = devices[BRIDGE_DEVICE_ID]
return hex(bridge_device["serial"])[2:].zfill(8)
finally:
await bridge.close() await bridge.close()
return connected_ok
return None

View File

@ -56,6 +56,10 @@ class MockBridge:
"""Return whether the mock bridge is connected.""" """Return whether the mock bridge is connected."""
return self.is_currently_connected return self.is_currently_connected
def get_devices(self):
"""Return devices on the bridge."""
return {"1": {"serial": 1234}}
async def close(self): async def close(self):
"""Close the mock bridge connection.""" """Close the mock bridge connection."""
self.is_currently_connected = False self.is_currently_connected = False
@ -90,6 +94,8 @@ async def test_bridge_import_flow(hass):
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == CasetaConfigFlow.ENTRY_DEFAULT_TITLE assert result["title"] == CasetaConfigFlow.ENTRY_DEFAULT_TITLE
assert result["data"] == entry_mock_data assert result["data"] == entry_mock_data
assert result["result"].unique_id == "000004d2"
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1