1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00
ha-core/homeassistant/components/nanoleaf/config_flow.py
epenet be89c07cac
Use dataclass properties in nanoleaf discovery (#60580)
Co-authored-by: epenet <epenet@users.noreply.github.com>
2021-11-30 06:05:50 -10:00

243 lines
9.1 KiB
Python

"""Config flow for Nanoleaf integration."""
from __future__ import annotations
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: dict[str, str]) -> 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_step_import(self, config: dict[str, Any]) -> FlowResult:
"""Handle Nanoleaf configuration import."""
self._async_abort_entries_match({CONF_HOST: config[CONF_HOST]})
_LOGGER.debug(
"Importing Nanoleaf on %s from your configuration.yaml", config[CONF_HOST]
)
self.nanoleaf = Nanoleaf(
async_get_clientsession(self.hass), config[CONF_HOST], config[CONF_TOKEN]
)
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,
},
)