1
mirror of https://github.com/home-assistant/core synced 2024-09-03 08:14:07 +02:00
ha-core/homeassistant/components/nanoleaf/config_flow.py

233 lines
8.6 KiB
Python

"""Config flow for Nanoleaf integration."""
from __future__ import annotations
from collections.abc import Mapping
import logging
import os
from typing import Any, Final, cast
from aionanoleaf import InvalidToken, Nanoleaf, Unauthorized, Unavailable
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import ssdp, zeroconf
from homeassistant.const import CONF_HOST, CONF_TOKEN
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.json import load_json, save_json
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# For discovery integration import
CONFIG_FILE: Final = ".nanoleaf.conf"
USER_SCHEMA: Final = vol.Schema(
{
vol.Required(CONF_HOST): str,
}
)
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Nanoleaf config flow."""
reauth_entry: config_entries.ConfigEntry | None = None
VERSION = 1
def __init__(self) -> None:
"""Initialize a Nanoleaf flow."""
self.nanoleaf: Nanoleaf
# For discovery integration import
self.discovery_conf: dict
self.device_id: str
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle Nanoleaf flow initiated by the user."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=USER_SCHEMA, last_step=False
)
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
self.nanoleaf = Nanoleaf(
async_get_clientsession(self.hass), user_input[CONF_HOST]
)
try:
await self.nanoleaf.authorize()
except Unavailable:
return self.async_show_form(
step_id="user",
data_schema=USER_SCHEMA,
errors={"base": "cannot_connect"},
last_step=False,
)
except Unauthorized:
pass
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unknown error connecting to Nanoleaf")
return self.async_show_form(
step_id="user",
data_schema=USER_SCHEMA,
last_step=False,
errors={"base": "unknown"},
)
return await self.async_step_link()
async def async_step_reauth(self, data: Mapping[str, Any]) -> FlowResult:
"""Handle Nanoleaf reauth flow if token is invalid."""
self.reauth_entry = cast(
config_entries.ConfigEntry,
self.hass.config_entries.async_get_entry(self.context["entry_id"]),
)
self.nanoleaf = Nanoleaf(async_get_clientsession(self.hass), data[CONF_HOST])
self.context["title_placeholders"] = {"name": self.reauth_entry.title}
return await self.async_step_link()
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle Nanoleaf Zeroconf discovery."""
_LOGGER.debug("Zeroconf discovered: %s", discovery_info)
return await self._async_homekit_zeroconf_discovery_handler(discovery_info)
async def async_step_homekit(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle Nanoleaf Homekit discovery."""
_LOGGER.debug("Homekit discovered: %s", discovery_info)
return await self._async_homekit_zeroconf_discovery_handler(discovery_info)
async def _async_homekit_zeroconf_discovery_handler(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle Nanoleaf Homekit and Zeroconf discovery."""
return await self._async_discovery_handler(
discovery_info.host,
discovery_info.name.replace(f".{discovery_info.type}", ""),
discovery_info.properties[zeroconf.ATTR_PROPERTIES_ID],
)
async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult:
"""Handle Nanoleaf SSDP discovery."""
_LOGGER.debug("SSDP discovered: %s", discovery_info)
return await self._async_discovery_handler(
discovery_info.ssdp_headers["_host"],
discovery_info.ssdp_headers["nl-devicename"],
discovery_info.ssdp_headers["nl-deviceid"],
)
async def _async_discovery_handler(
self, host: str, name: str, device_id: str
) -> FlowResult:
"""Handle Nanoleaf discovery."""
# The name is unique and printed on the device and cannot be changed.
await self.async_set_unique_id(name)
self._abort_if_unique_id_configured({CONF_HOST: host})
# Import from discovery integration
self.device_id = device_id
self.discovery_conf = cast(
dict,
await self.hass.async_add_executor_job(
load_json, self.hass.config.path(CONFIG_FILE)
),
)
auth_token: str | None = self.discovery_conf.get(self.device_id, {}).get(
"token", # >= 2021.4
self.discovery_conf.get(host, {}).get("token"), # < 2021.4
)
if auth_token is not None:
self.nanoleaf = Nanoleaf(
async_get_clientsession(self.hass), host, auth_token
)
_LOGGER.warning(
"Importing Nanoleaf %s from the discovery integration", name
)
return await self.async_setup_finish(discovery_integration_import=True)
self.nanoleaf = Nanoleaf(async_get_clientsession(self.hass), host)
self.context["title_placeholders"] = {"name": name}
return await self.async_step_link()
async def async_step_link(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle Nanoleaf link step."""
if user_input is None:
return self.async_show_form(step_id="link")
try:
await self.nanoleaf.authorize()
except Unauthorized:
return self.async_show_form(
step_id="link", errors={"base": "not_allowing_new_tokens"}
)
except Unavailable:
return self.async_abort(reason="cannot_connect")
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unknown error authorizing Nanoleaf")
return self.async_show_form(step_id="link", errors={"base": "unknown"})
if self.reauth_entry is not None:
self.hass.config_entries.async_update_entry(
self.reauth_entry,
data={
**self.reauth_entry.data,
CONF_TOKEN: self.nanoleaf.auth_token,
},
)
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return await self.async_setup_finish()
async def async_setup_finish(
self, discovery_integration_import: bool = False
) -> FlowResult:
"""Finish Nanoleaf config flow."""
try:
await self.nanoleaf.get_info()
except Unavailable:
return self.async_abort(reason="cannot_connect")
except InvalidToken:
return self.async_abort(reason="invalid_token")
except Exception: # pylint: disable=broad-except
_LOGGER.exception(
"Unknown error connecting with Nanoleaf at %s", self.nanoleaf.host
)
return self.async_abort(reason="unknown")
name = self.nanoleaf.name
await self.async_set_unique_id(name)
self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host})
if discovery_integration_import:
if self.nanoleaf.host in self.discovery_conf:
self.discovery_conf.pop(self.nanoleaf.host)
if self.device_id in self.discovery_conf:
self.discovery_conf.pop(self.device_id)
_LOGGER.info(
"Successfully imported Nanoleaf %s from the discovery integration",
name,
)
if self.discovery_conf:
await self.hass.async_add_executor_job(
save_json, self.hass.config.path(CONFIG_FILE), self.discovery_conf
)
else:
await self.hass.async_add_executor_job(
os.remove, self.hass.config.path(CONFIG_FILE)
)
return self.async_create_entry(
title=name,
data={
CONF_HOST: self.nanoleaf.host,
CONF_TOKEN: self.nanoleaf.auth_token,
},
)