1 """Config flow for Lutron Caseta."""
3 from __future__
import annotations
11 from pylutron_caseta.pairing
import PAIR_CA, PAIR_CERT, PAIR_KEY, async_pair
12 from pylutron_caseta.smartbridge
import Smartbridge
13 import voluptuous
as vol
21 ABORT_REASON_CANNOT_CONNECT,
36 PAIR_KEY: CONF_KEYFILE,
37 PAIR_CERT: CONF_CERTFILE,
38 PAIR_CA: CONF_CA_CERTS,
41 _LOGGER = logging.getLogger(__name__)
43 ENTRY_DEFAULT_TITLE =
"Caséta bridge"
45 DATA_SCHEMA_USER = vol.Schema({vol.Required(CONF_HOST): str})
46 TLS_ASSET_TEMPLATE =
"lutron_caseta-{}-{}.pem"
50 """Handle Lutron Caseta config flow."""
55 """Initialize a Lutron Caseta flow."""
56 self.data: dict[str, Any] = {}
57 self.
lutron_idlutron_id: str |
None =
None
62 self, user_input: dict[str, Any] |
None =
None
63 ) -> ConfigFlowResult:
64 """Handle a flow initialized by the user."""
65 if user_input
is not None:
66 self.data[CONF_HOST] = user_input[CONF_HOST]
72 self, discovery_info: zeroconf.ZeroconfServiceInfo
73 ) -> ConfigFlowResult:
74 """Handle a flow initialized by zeroconf discovery."""
75 hostname = discovery_info.hostname
76 if hostname
is None or not hostname.lower().startswith(
"lutron-"):
79 self.
lutron_idlutron_id = hostname.split(
"-")[1].replace(
".local.",
"")
82 host = discovery_info.host
85 self.data[CONF_HOST] = host
86 self.context[
"title_placeholders"] = {
93 self, discovery_info: zeroconf.ZeroconfServiceInfo
94 ) -> ConfigFlowResult:
95 """Handle a flow initialized by homekit discovery."""
99 self, user_input: dict[str, Any] |
None =
None
100 ) -> ConfigFlowResult:
101 """Handle pairing with the hub."""
110 and await self.hass.async_add_executor_job(self.
_tls_assets_exist_tls_assets_exist)
116 if user_input
is not None:
124 assets = await async_pair(self.data[CONF_HOST])
125 except (TimeoutError, OSError):
126 errors[
"base"] =
"cannot_connect"
129 await self.hass.async_add_executor_job(self.
_write_tls_assets_write_tls_assets, assets)
135 description_placeholders={
137 CONF_HOST: self.data[CONF_HOST],
143 """Return the best identifier for the bridge.
145 If the bridge was not discovered via zeroconf,
146 we fallback to using the host.
148 return self.
lutron_idlutron_id
or self.data[CONF_HOST]
151 """Write the tls assets to disk."""
152 for asset_key, conf_key
in FILE_MAPPING.items():
154 self.hass.config.path(self.data[conf_key]),
"w", encoding=
"utf8"
156 file_handle.write(assets[asset_key])
159 """Check to see if tls assets are already on disk."""
160 for conf_key
in FILE_MAPPING.values():
161 if not os.path.exists(self.hass.config.path(self.data[conf_key])):
167 """Fill the tls asset locations in self.data."""
168 for asset_key, conf_key
in FILE_MAPPING.items():
169 self.data[conf_key] = TLS_ASSET_TEMPLATE.format(self.
bridge_idbridge_id, asset_key)
172 """Import a new Caseta bridge as a config entry.
174 This flow is triggered by `async_setup`.
176 host = import_data[CONF_HOST]
178 self.data[CONF_HOST] = host
183 self.data[CONF_KEYFILE] = import_data[CONF_KEYFILE]
184 self.data[CONF_CERTFILE] = import_data[CONF_CERTFILE]
185 self.data[CONF_CA_CERTS] = import_data[CONF_CA_CERTS]
204 self, user_input: dict[str, Any] |
None =
None
205 ) -> ConfigFlowResult:
206 """Make failed import surfaced to user."""
207 self.context[
"title_placeholders"] = {CONF_NAME: self.data[CONF_HOST]}
209 if user_input
is None:
211 step_id=STEP_IMPORT_FAILED,
212 description_placeholders={
"host": self.data[CONF_HOST]},
213 errors={
"base": ERROR_CANNOT_CONNECT},
219 """Check if we can connect to the bridge with the current config."""
221 bridge = Smartbridge.create_tls(
222 hostname=self.data[CONF_HOST],
223 keyfile=self.hass.config.path(self.data[CONF_KEYFILE]),
224 certfile=self.hass.config.path(self.data[CONF_CERTFILE]),
225 ca_certs=self.hass.config.path(self.data[CONF_CA_CERTS]),
229 "Invalid certificate used to connect to bridge at %s",
230 self.data[CONF_HOST],
235 async
with asyncio.timeout(BRIDGE_TIMEOUT):
236 await bridge.connect()
239 "Timeout while trying to connect to bridge at %s",
240 self.data[CONF_HOST],
243 if not bridge.is_connected():
245 devices = bridge.get_devices()
246 bridge_device = devices[BRIDGE_DEVICE_ID]
247 return hex(bridge_device[
"serial"])[2:].zfill(8)
def _tls_assets_exist(self)
def _write_tls_assets(self, assets)
str|None async_get_lutron_id(self)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
def _configure_tls_assets(self)
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_import_failed(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_link(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_homekit(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_import(self, dict[str, Any] import_data)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ConfigFlowResult async_step_zeroconf(self, ZeroconfServiceInfo discovery_info)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
None open(self, **Any kwargs)