mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 17:57:55 +00:00
Convert Nanoleaf yaml and discovery to config flow (#52199)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
9555a34691
commit
8570502681
@ -685,7 +685,9 @@ omit =
|
|||||||
homeassistant/components/myq/cover.py
|
homeassistant/components/myq/cover.py
|
||||||
homeassistant/components/myq/light.py
|
homeassistant/components/myq/light.py
|
||||||
homeassistant/components/nad/media_player.py
|
homeassistant/components/nad/media_player.py
|
||||||
|
homeassistant/components/nanoleaf/__init__.py
|
||||||
homeassistant/components/nanoleaf/light.py
|
homeassistant/components/nanoleaf/light.py
|
||||||
|
homeassistant/components/nanoleaf/util.py
|
||||||
homeassistant/components/neato/__init__.py
|
homeassistant/components/neato/__init__.py
|
||||||
homeassistant/components/neato/api.py
|
homeassistant/components/neato/api.py
|
||||||
homeassistant/components/neato/camera.py
|
homeassistant/components/neato/camera.py
|
||||||
|
@ -326,6 +326,7 @@ homeassistant/components/myq/* @bdraco @ehendrix23
|
|||||||
homeassistant/components/mysensors/* @MartinHjelmare @functionpointer
|
homeassistant/components/mysensors/* @MartinHjelmare @functionpointer
|
||||||
homeassistant/components/mystrom/* @fabaff
|
homeassistant/components/mystrom/* @fabaff
|
||||||
homeassistant/components/nam/* @bieniu
|
homeassistant/components/nam/* @bieniu
|
||||||
|
homeassistant/components/nanoleaf/* @milanmeu
|
||||||
homeassistant/components/neato/* @dshokouhi @Santobert
|
homeassistant/components/neato/* @dshokouhi @Santobert
|
||||||
homeassistant/components/nederlandse_spoorwegen/* @YarmoM
|
homeassistant/components/nederlandse_spoorwegen/* @YarmoM
|
||||||
homeassistant/components/nello/* @pschmitt
|
homeassistant/components/nello/* @pschmitt
|
||||||
|
@ -55,7 +55,6 @@ SERVICE_HANDLERS = {
|
|||||||
"bose_soundtouch": ("media_player", "soundtouch"),
|
"bose_soundtouch": ("media_player", "soundtouch"),
|
||||||
"bluesound": ("media_player", "bluesound"),
|
"bluesound": ("media_player", "bluesound"),
|
||||||
"lg_smart_device": ("media_player", "lg_soundbar"),
|
"lg_smart_device": ("media_player", "lg_soundbar"),
|
||||||
"nanoleaf_aurora": ("light", "nanoleaf"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OPTIONAL_SERVICE_HANDLERS = {SERVICE_DLNA_DMR: ("media_player", "dlna_dmr")}
|
OPTIONAL_SERVICE_HANDLERS = {SERVICE_DLNA_DMR: ("media_player", "dlna_dmr")}
|
||||||
@ -87,6 +86,7 @@ MIGRATED_SERVICE_HANDLERS = [
|
|||||||
SERVICE_XIAOMI_GW,
|
SERVICE_XIAOMI_GW,
|
||||||
"volumio",
|
"volumio",
|
||||||
SERVICE_YEELIGHT,
|
SERVICE_YEELIGHT,
|
||||||
|
"nanoleaf_aurora",
|
||||||
]
|
]
|
||||||
|
|
||||||
DEFAULT_ENABLED = (
|
DEFAULT_ENABLED = (
|
||||||
|
@ -1 +1,31 @@
|
|||||||
"""The nanoleaf component."""
|
"""The Nanoleaf integration."""
|
||||||
|
from pynanoleaf.pynanoleaf import Nanoleaf, Unavailable
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
|
||||||
|
from .const import DEVICE, DOMAIN, NAME, SERIAL_NO
|
||||||
|
from .util import pynanoleaf_get_info
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Nanoleaf from a config entry."""
|
||||||
|
nanoleaf = Nanoleaf(entry.data[CONF_HOST])
|
||||||
|
nanoleaf.token = entry.data[CONF_TOKEN]
|
||||||
|
try:
|
||||||
|
info = await hass.async_add_executor_job(pynanoleaf_get_info, nanoleaf)
|
||||||
|
except Unavailable as err:
|
||||||
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
||||||
|
DEVICE: nanoleaf,
|
||||||
|
NAME: info["name"],
|
||||||
|
SERIAL_NO: info["serialNo"],
|
||||||
|
}
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, "light")
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
204
homeassistant/components/nanoleaf/config_flow.py
Normal file
204
homeassistant/components/nanoleaf/config_flow.py
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
"""Config flow for Nanoleaf integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Any, Final, cast
|
||||||
|
|
||||||
|
from pynanoleaf import InvalidToken, Nanoleaf, NotAuthorizingNewTokens, Unavailable
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components import persistent_notification
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||||
|
from homeassistant.util.json import load_json, save_json
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .util import pynanoleaf_get_info
|
||||||
|
|
||||||
|
_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."""
|
||||||
|
|
||||||
|
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(user_input[CONF_HOST])
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(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 NotAuthorizingNewTokens:
|
||||||
|
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_zeroconf(
|
||||||
|
self, discovery_info: DiscoveryInfoType
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle Nanoleaf Zeroconf discovery."""
|
||||||
|
_LOGGER.debug("Zeroconf discovered: %s", discovery_info)
|
||||||
|
return await self._async_discovery_handler(discovery_info)
|
||||||
|
|
||||||
|
async def async_step_homekit(self, discovery_info: DiscoveryInfoType) -> FlowResult:
|
||||||
|
"""Handle Nanoleaf Homekit discovery."""
|
||||||
|
_LOGGER.debug("Homekit discovered: %s", discovery_info)
|
||||||
|
return await self._async_discovery_handler(discovery_info)
|
||||||
|
|
||||||
|
async def _async_discovery_handler(
|
||||||
|
self, discovery_info: DiscoveryInfoType
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle Nanoleaf discovery."""
|
||||||
|
host = discovery_info["host"]
|
||||||
|
# The name is unique and printed on the device and cannot be changed.
|
||||||
|
name = discovery_info["name"].replace(f".{discovery_info['type']}", "")
|
||||||
|
await self.async_set_unique_id(name)
|
||||||
|
self._abort_if_unique_id_configured({CONF_HOST: host})
|
||||||
|
self.nanoleaf = Nanoleaf(host)
|
||||||
|
|
||||||
|
# Import from discovery integration
|
||||||
|
self.device_id = discovery_info["properties"]["id"]
|
||||||
|
self.discovery_conf = cast(
|
||||||
|
dict,
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
load_json, self.hass.config.path(CONFIG_FILE)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.nanoleaf.token = self.discovery_conf.get(self.device_id, {}).get(
|
||||||
|
"token", # >= 2021.4
|
||||||
|
self.discovery_conf.get(host, {}).get("token"), # < 2021.4
|
||||||
|
)
|
||||||
|
if self.nanoleaf.token is not None:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Importing Nanoleaf %s from the discovery integration", name
|
||||||
|
)
|
||||||
|
return await self.async_setup_finish(discovery_integration_import=True)
|
||||||
|
|
||||||
|
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.hass.async_add_executor_job(self.nanoleaf.authorize)
|
||||||
|
except NotAuthorizingNewTokens:
|
||||||
|
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"})
|
||||||
|
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(config[CONF_HOST])
|
||||||
|
self.nanoleaf.token = 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:
|
||||||
|
info = await self.hass.async_add_executor_job(
|
||||||
|
pynanoleaf_get_info, self.nanoleaf
|
||||||
|
)
|
||||||
|
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 with token %s",
|
||||||
|
self.nanoleaf.host,
|
||||||
|
self.nanoleaf.token,
|
||||||
|
)
|
||||||
|
return self.async_abort(reason="unknown")
|
||||||
|
name = info["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)
|
||||||
|
)
|
||||||
|
persistent_notification.async_create(
|
||||||
|
self.hass,
|
||||||
|
"All Nanoleaf devices from the discovery integration are imported. If you used the discovery integration only for Nanoleaf you can remove it from your configuration.yaml",
|
||||||
|
f"Imported Nanoleaf {name}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=name,
|
||||||
|
data={
|
||||||
|
CONF_HOST: self.nanoleaf.host,
|
||||||
|
CONF_TOKEN: self.nanoleaf.token,
|
||||||
|
},
|
||||||
|
)
|
7
homeassistant/components/nanoleaf/const.py
Normal file
7
homeassistant/components/nanoleaf/const.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"""Constants for Nanoleaf integration."""
|
||||||
|
|
||||||
|
DOMAIN = "nanoleaf"
|
||||||
|
|
||||||
|
DEVICE = "device"
|
||||||
|
SERIAL_NO = "serial_no"
|
||||||
|
NAME = "name"
|
@ -1,7 +1,9 @@
|
|||||||
"""Support for Nanoleaf Lights."""
|
"""Support for Nanoleaf Lights."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pynanoleaf import Nanoleaf, Unavailable
|
from pynanoleaf import Unavailable
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
@ -18,22 +20,23 @@ from homeassistant.components.light import (
|
|||||||
SUPPORT_TRANSITION,
|
SUPPORT_TRANSITION,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
)
|
)
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.util import color as color_util
|
from homeassistant.util import color as color_util
|
||||||
from homeassistant.util.color import (
|
from homeassistant.util.color import (
|
||||||
color_temperature_mired_to_kelvin as mired_to_kelvin,
|
color_temperature_mired_to_kelvin as mired_to_kelvin,
|
||||||
)
|
)
|
||||||
from homeassistant.util.json import load_json, save_json
|
|
||||||
|
from .const import DEVICE, DOMAIN, NAME, SERIAL_NO
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_NAME = "Nanoleaf"
|
DEFAULT_NAME = "Nanoleaf"
|
||||||
|
|
||||||
DATA_NANOLEAF = "nanoleaf"
|
|
||||||
|
|
||||||
CONFIG_FILE = ".nanoleaf.conf"
|
|
||||||
|
|
||||||
ICON = "mdi:triangle-outline"
|
ICON = "mdi:triangle-outline"
|
||||||
|
|
||||||
SUPPORT_NANOLEAF = (
|
SUPPORT_NANOLEAF = (
|
||||||
@ -53,69 +56,34 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: ConfigType,
|
||||||
|
add_entities: AddEntitiesCallback,
|
||||||
|
discovery_info: DiscoveryInfoType | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Import Nanoleaf light platform."""
|
||||||
|
await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_IMPORT},
|
||||||
|
data={CONF_HOST: config[CONF_HOST], CONF_TOKEN: config[CONF_TOKEN]},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
"""Set up the Nanoleaf light."""
|
"""Set up the Nanoleaf light."""
|
||||||
|
data = hass.data[DOMAIN][entry.entry_id]
|
||||||
if DATA_NANOLEAF not in hass.data:
|
add_entities([NanoleafLight(data[DEVICE], data[NAME], data[SERIAL_NO])], True)
|
||||||
hass.data[DATA_NANOLEAF] = {}
|
|
||||||
|
|
||||||
token = ""
|
|
||||||
if discovery_info is not None:
|
|
||||||
host = discovery_info["host"]
|
|
||||||
name = None
|
|
||||||
device_id = discovery_info["properties"]["id"]
|
|
||||||
|
|
||||||
# if device already exists via config, skip discovery setup
|
|
||||||
if host in hass.data[DATA_NANOLEAF]:
|
|
||||||
return
|
|
||||||
_LOGGER.info("Discovered a new Nanoleaf: %s", discovery_info)
|
|
||||||
conf = load_json(hass.config.path(CONFIG_FILE))
|
|
||||||
if host in conf and device_id not in conf:
|
|
||||||
conf[device_id] = conf.pop(host)
|
|
||||||
save_json(hass.config.path(CONFIG_FILE), conf)
|
|
||||||
token = conf.get(device_id, {}).get("token", "")
|
|
||||||
else:
|
|
||||||
host = config[CONF_HOST]
|
|
||||||
name = config[CONF_NAME]
|
|
||||||
token = config[CONF_TOKEN]
|
|
||||||
|
|
||||||
nanoleaf_light = Nanoleaf(host)
|
|
||||||
|
|
||||||
if not token:
|
|
||||||
token = nanoleaf_light.request_token()
|
|
||||||
if not token:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Could not generate the auth token, did you press "
|
|
||||||
"and hold the power button on %s"
|
|
||||||
"for 5-7 seconds?",
|
|
||||||
name,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
conf = load_json(hass.config.path(CONFIG_FILE))
|
|
||||||
conf[host] = {"token": token}
|
|
||||||
save_json(hass.config.path(CONFIG_FILE), conf)
|
|
||||||
|
|
||||||
nanoleaf_light.token = token
|
|
||||||
|
|
||||||
try:
|
|
||||||
info = nanoleaf_light.info
|
|
||||||
except Unavailable:
|
|
||||||
_LOGGER.error("Could not connect to Nanoleaf Light: %s on %s", name, host)
|
|
||||||
return
|
|
||||||
|
|
||||||
if name is None:
|
|
||||||
name = info.name
|
|
||||||
|
|
||||||
hass.data[DATA_NANOLEAF][host] = nanoleaf_light
|
|
||||||
add_entities([NanoleafLight(nanoleaf_light, name)], True)
|
|
||||||
|
|
||||||
|
|
||||||
class NanoleafLight(LightEntity):
|
class NanoleafLight(LightEntity):
|
||||||
"""Representation of a Nanoleaf Light."""
|
"""Representation of a Nanoleaf Light."""
|
||||||
|
|
||||||
def __init__(self, light, name):
|
def __init__(self, light, name, unique_id):
|
||||||
"""Initialize an Nanoleaf light."""
|
"""Initialize an Nanoleaf light."""
|
||||||
self._unique_id = light.serialNo
|
self._unique_id = unique_id
|
||||||
self._available = True
|
self._available = True
|
||||||
self._brightness = None
|
self._brightness = None
|
||||||
self._color_temp = None
|
self._color_temp = None
|
||||||
@ -239,7 +207,6 @@ class NanoleafLight(LightEntity):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Fetch new state data for this light."""
|
"""Fetch new state data for this light."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._available = self._light.available
|
self._available = self._light.available
|
||||||
self._brightness = self._light.brightness
|
self._brightness = self._light.brightness
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
{
|
{
|
||||||
"domain": "nanoleaf",
|
"domain": "nanoleaf",
|
||||||
"name": "Nanoleaf",
|
"name": "Nanoleaf",
|
||||||
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/nanoleaf",
|
"documentation": "https://www.home-assistant.io/integrations/nanoleaf",
|
||||||
"requirements": ["pynanoleaf==0.1.0"],
|
"requirements": ["pynanoleaf==0.1.0"],
|
||||||
"codeowners": [],
|
"zeroconf": ["_nanoleafms._tcp.local.", "_nanoleafapi._tcp.local."],
|
||||||
|
"homekit" : {
|
||||||
|
"models": [
|
||||||
|
"NL*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"codeowners": ["@milanmeu"],
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
}
|
}
|
27
homeassistant/components/nanoleaf/strings.json
Normal file
27
homeassistant/components/nanoleaf/strings.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"flow_title": "{name}",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "[%key:common::config_flow::data::host%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"title": "Link Nanoleaf",
|
||||||
|
"description": "Press and hold the power button on your Nanoleaf for 5 seconds until the button LEDs start flashing, then click **SUBMIT** within 30 seconds."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"not_allowing_new_tokens": "Nanoleaf is not allowing new tokens, follow the instructions above.",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
homeassistant/components/nanoleaf/translations/en.json
Normal file
27
homeassistant/components/nanoleaf/translations/en.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured",
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"invalid_token": "Invalid access token",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"not_allowing_new_tokens": "Nanoleaf is not allowing new tokens, follow the instructions above.",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"flow_title": "{name}",
|
||||||
|
"step": {
|
||||||
|
"link": {
|
||||||
|
"description": "Press and hold the power button on your Nanoleaf for 5 seconds until the button LEDs start flashing, then click **SUBMIT** within 30 seconds.",
|
||||||
|
"title": "Link Nanoleaf"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
homeassistant/components/nanoleaf/util.py
Normal file
7
homeassistant/components/nanoleaf/util.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"""Nanoleaf integration util."""
|
||||||
|
from pynanoleaf.pynanoleaf import Nanoleaf
|
||||||
|
|
||||||
|
|
||||||
|
def pynanoleaf_get_info(nanoleaf_light: Nanoleaf) -> dict:
|
||||||
|
"""Get Nanoleaf light info."""
|
||||||
|
return nanoleaf_light.info
|
@ -176,6 +176,7 @@ FLOWS = [
|
|||||||
"myq",
|
"myq",
|
||||||
"mysensors",
|
"mysensors",
|
||||||
"nam",
|
"nam",
|
||||||
|
"nanoleaf",
|
||||||
"neato",
|
"neato",
|
||||||
"nest",
|
"nest",
|
||||||
"netatmo",
|
"netatmo",
|
||||||
|
@ -165,6 +165,16 @@ ZEROCONF = {
|
|||||||
"domain": "xiaomi_miio"
|
"domain": "xiaomi_miio"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"_nanoleafapi._tcp.local.": [
|
||||||
|
{
|
||||||
|
"domain": "nanoleaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_nanoleafms._tcp.local.": [
|
||||||
|
{
|
||||||
|
"domain": "nanoleaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
"_nut._tcp.local.": [
|
"_nut._tcp.local.": [
|
||||||
{
|
{
|
||||||
"domain": "nut"
|
"domain": "nut"
|
||||||
@ -251,6 +261,7 @@ HOMEKIT = {
|
|||||||
"Iota": "abode",
|
"Iota": "abode",
|
||||||
"LIFX": "lifx",
|
"LIFX": "lifx",
|
||||||
"MYQ": "myq",
|
"MYQ": "myq",
|
||||||
|
"NL*": "nanoleaf",
|
||||||
"Netatmo Relay": "netatmo",
|
"Netatmo Relay": "netatmo",
|
||||||
"PowerView": "hunterdouglas_powerview",
|
"PowerView": "hunterdouglas_powerview",
|
||||||
"Presence": "netatmo",
|
"Presence": "netatmo",
|
||||||
|
@ -941,6 +941,9 @@ pymyq==3.1.3
|
|||||||
# homeassistant.components.mysensors
|
# homeassistant.components.mysensors
|
||||||
pymysensors==0.21.0
|
pymysensors==0.21.0
|
||||||
|
|
||||||
|
# homeassistant.components.nanoleaf
|
||||||
|
pynanoleaf==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.nuki
|
# homeassistant.components.nuki
|
||||||
pynuki==1.4.1
|
pynuki==1.4.1
|
||||||
|
|
||||||
|
1
tests/components/nanoleaf/__init__.py
Normal file
1
tests/components/nanoleaf/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Nanoleaf integration."""
|
399
tests/components/nanoleaf/test_config_flow.py
Normal file
399
tests/components/nanoleaf/test_config_flow.py
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
"""Test the Nanoleaf config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pynanoleaf import InvalidToken, NotAuthorizingNewTokens, Unavailable
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.nanoleaf.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
TEST_NAME = "Canvas ADF9"
|
||||||
|
TEST_HOST = "192.168.0.100"
|
||||||
|
TEST_TOKEN = "R34F1c92FNv3pcZs4di17RxGqiLSwHM"
|
||||||
|
TEST_OTHER_TOKEN = "Qs4dxGcHR34l29RF1c92FgiLQBt3pcM"
|
||||||
|
TEST_DEVICE_ID = "5E:2E:EA:XX:XX:XX"
|
||||||
|
TEST_OTHER_DEVICE_ID = "5E:2E:EA:YY:YY:YY"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_unavailable_user_step(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle Unavailable errors when host is not available in user step."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
side_effect=Unavailable("message"),
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["step_id"] == "user"
|
||||||
|
assert result2["errors"] == {"base": "cannot_connect"}
|
||||||
|
assert not result2["last_step"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_unavailable_link_step(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we abort if the device becomes unavailable in the link step."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
return_value=None,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["step_id"] == "link"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
side_effect=Unavailable("message"),
|
||||||
|
):
|
||||||
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert result3["type"] == "abort"
|
||||||
|
assert result3["reason"] == "cannot_connect"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_unavailable_setup_finish(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we abort if the device becomes unavailable during setup_finish."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
return_value=None,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["step_id"] == "link"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
return_value=None,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
||||||
|
side_effect=Unavailable("message"),
|
||||||
|
):
|
||||||
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert result3["type"] == "abort"
|
||||||
|
assert result3["reason"] == "cannot_connect"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_not_authorizing_new_tokens(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle NotAuthorizingNewTokens errors."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] is None
|
||||||
|
assert not result["last_step"]
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
side_effect=NotAuthorizingNewTokens("message"),
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] is None
|
||||||
|
assert result2["step_id"] == "link"
|
||||||
|
|
||||||
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
)
|
||||||
|
assert result3["type"] == "form"
|
||||||
|
assert result3["errors"] is None
|
||||||
|
assert result3["step_id"] == "link"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
side_effect=NotAuthorizingNewTokens("message"),
|
||||||
|
):
|
||||||
|
result4 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert result4["type"] == "form"
|
||||||
|
assert result4["step_id"] == "link"
|
||||||
|
assert result4["errors"] == {"base": "not_allowing_new_tokens"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_exception(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle Exception errors."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["step_id"] == "user"
|
||||||
|
assert result2["errors"] == {"base": "unknown"}
|
||||||
|
assert not result2["last_step"]
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
return_value=None,
|
||||||
|
):
|
||||||
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result3["step_id"] == "link"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
result4 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert result4["type"] == "form"
|
||||||
|
assert result4["step_id"] == "link"
|
||||||
|
assert result4["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
return_value=None,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
result5 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert result5["type"] == "abort"
|
||||||
|
assert result5["reason"] == "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zeroconf_discovery(hass: HomeAssistant) -> None:
|
||||||
|
"""Test zeroconfig discovery flow init."""
|
||||||
|
zeroconf = "_nanoleafms._tcp.local"
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
||||||
|
return_value={"name": TEST_NAME},
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.load_json",
|
||||||
|
return_value={},
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
|
data={
|
||||||
|
"host": TEST_HOST,
|
||||||
|
"name": f"{TEST_NAME}.{zeroconf}",
|
||||||
|
"type": zeroconf,
|
||||||
|
"properties": {"id": TEST_DEVICE_ID},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "link"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_discovery_link_unavailable(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test homekit discovery and abort if device is unavailable."""
|
||||||
|
homekit = "_hap._tcp.local"
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
||||||
|
return_value={"name": TEST_NAME},
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.load_json",
|
||||||
|
return_value={},
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_HOMEKIT},
|
||||||
|
data={
|
||||||
|
"host": TEST_HOST,
|
||||||
|
"name": f"{TEST_NAME}.{homekit}",
|
||||||
|
"type": homekit,
|
||||||
|
"properties": {"id": TEST_DEVICE_ID},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "link"
|
||||||
|
|
||||||
|
context = next(
|
||||||
|
flow["context"]
|
||||||
|
for flow in hass.config_entries.flow.async_progress()
|
||||||
|
if flow["flow_id"] == result["flow_id"]
|
||||||
|
)
|
||||||
|
assert context["title_placeholders"] == {"name": TEST_NAME}
|
||||||
|
assert context["unique_id"] == TEST_NAME
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
|
side_effect=Unavailable("message"),
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "cannot_connect"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_config(hass: HomeAssistant) -> None:
|
||||||
|
"""Test configuration import."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
||||||
|
return_value={"name": TEST_NAME},
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN},
|
||||||
|
)
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_TOKEN: TEST_TOKEN,
|
||||||
|
}
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_config_invalid_token(hass: HomeAssistant) -> None:
|
||||||
|
"""Test configuration import with invalid token."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
||||||
|
side_effect=InvalidToken("message"),
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN},
|
||||||
|
)
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "invalid_token"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_last_discovery_integration_host_zeroconf(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Test discovery integration import from < 2021.4 (host) with zeroconf.
|
||||||
|
|
||||||
|
Device is last in Nanoleaf config file.
|
||||||
|
"""
|
||||||
|
zeroconf = "_nanoleafapi._tcp.local"
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.load_json",
|
||||||
|
return_value={TEST_HOST: {"token": TEST_TOKEN}},
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
||||||
|
return_value={"name": TEST_NAME},
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.os.remove",
|
||||||
|
return_value=None,
|
||||||
|
) as mock_remove, patch(
|
||||||
|
"homeassistant.components.nanoleaf.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
|
data={
|
||||||
|
"host": TEST_HOST,
|
||||||
|
"name": f"{TEST_NAME}.{zeroconf}",
|
||||||
|
"type": zeroconf,
|
||||||
|
"properties": {"id": TEST_DEVICE_ID},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_TOKEN: TEST_TOKEN,
|
||||||
|
}
|
||||||
|
mock_remove.assert_called_once()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_not_last_discovery_integration_device_id_homekit(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Test discovery integration import from >= 2021.4 (device_id) with homekit.
|
||||||
|
|
||||||
|
Device is not the only one in the Nanoleaf config file.
|
||||||
|
"""
|
||||||
|
homekit = "_hap._tcp.local"
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.load_json",
|
||||||
|
return_value={
|
||||||
|
TEST_DEVICE_ID: {"token": TEST_TOKEN},
|
||||||
|
TEST_OTHER_DEVICE_ID: {"token": TEST_OTHER_TOKEN},
|
||||||
|
},
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
||||||
|
return_value={"name": TEST_NAME},
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.save_json",
|
||||||
|
return_value=None,
|
||||||
|
) as mock_save_json, patch(
|
||||||
|
"homeassistant.components.nanoleaf.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_HOMEKIT},
|
||||||
|
data={
|
||||||
|
"host": TEST_HOST,
|
||||||
|
"name": f"{TEST_NAME}.{homekit}",
|
||||||
|
"type": homekit,
|
||||||
|
"properties": {"id": TEST_DEVICE_ID},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == TEST_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_TOKEN: TEST_TOKEN,
|
||||||
|
}
|
||||||
|
mock_save_json.assert_called_once()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
Loading…
x
Reference in New Issue
Block a user