Convert Nanoleaf integration to use Async library aionanoleaf (#56548)
This commit is contained in:
parent
7ece35cd6f
commit
0b53f73fe2
9 changed files with 127 additions and 136 deletions
|
@ -695,7 +695,6 @@ omit =
|
||||||
homeassistant/components/nad/media_player.py
|
homeassistant/components/nad/media_player.py
|
||||||
homeassistant/components/nanoleaf/__init__.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
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
"""The Nanoleaf integration."""
|
"""The Nanoleaf integration."""
|
||||||
from pynanoleaf.pynanoleaf import InvalidToken, Nanoleaf, Unavailable
|
from aionanoleaf import InvalidToken, Nanoleaf, Unavailable
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DEVICE, DOMAIN, NAME, SERIAL_NO
|
from .const import DEVICE, DOMAIN, NAME, SERIAL_NO
|
||||||
from .util import pynanoleaf_get_info
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Nanoleaf from a config entry."""
|
"""Set up Nanoleaf from a config entry."""
|
||||||
nanoleaf = Nanoleaf(entry.data[CONF_HOST])
|
nanoleaf = Nanoleaf(
|
||||||
nanoleaf.token = entry.data[CONF_TOKEN]
|
async_get_clientsession(hass), entry.data[CONF_HOST], entry.data[CONF_TOKEN]
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
info = await hass.async_add_executor_job(pynanoleaf_get_info, nanoleaf)
|
await nanoleaf.get_info()
|
||||||
except Unavailable as err:
|
except Unavailable as err:
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady from err
|
||||||
except InvalidToken as err:
|
except InvalidToken as err:
|
||||||
|
@ -23,8 +24,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
||||||
DEVICE: nanoleaf,
|
DEVICE: nanoleaf,
|
||||||
NAME: info["name"],
|
NAME: nanoleaf.name,
|
||||||
SERIAL_NO: info["serialNo"],
|
SERIAL_NO: nanoleaf.serial_no,
|
||||||
}
|
}
|
||||||
|
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
|
|
|
@ -5,17 +5,17 @@ import logging
|
||||||
import os
|
import os
|
||||||
from typing import Any, Final, cast
|
from typing import Any, Final, cast
|
||||||
|
|
||||||
from pynanoleaf import InvalidToken, Nanoleaf, NotAuthorizingNewTokens, Unavailable
|
from aionanoleaf import InvalidToken, Nanoleaf, Unauthorized, Unavailable
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||||
from homeassistant.util.json import load_json, save_json
|
from homeassistant.util.json import load_json, save_json
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .util import pynanoleaf_get_info
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -53,9 +53,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
step_id="user", data_schema=USER_SCHEMA, last_step=False
|
step_id="user", data_schema=USER_SCHEMA, last_step=False
|
||||||
)
|
)
|
||||||
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
|
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
|
||||||
self.nanoleaf = Nanoleaf(user_input[CONF_HOST])
|
self.nanoleaf = Nanoleaf(
|
||||||
|
async_get_clientsession(self.hass), user_input[CONF_HOST]
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(self.nanoleaf.authorize)
|
await self.nanoleaf.authorize()
|
||||||
except Unavailable:
|
except Unavailable:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
|
@ -63,7 +65,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
errors={"base": "cannot_connect"},
|
errors={"base": "cannot_connect"},
|
||||||
last_step=False,
|
last_step=False,
|
||||||
)
|
)
|
||||||
except NotAuthorizingNewTokens:
|
except Unauthorized:
|
||||||
pass
|
pass
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Unknown error connecting to Nanoleaf")
|
_LOGGER.exception("Unknown error connecting to Nanoleaf")
|
||||||
|
@ -81,7 +83,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
config_entries.ConfigEntry,
|
config_entries.ConfigEntry,
|
||||||
self.hass.config_entries.async_get_entry(self.context["entry_id"]),
|
self.hass.config_entries.async_get_entry(self.context["entry_id"]),
|
||||||
)
|
)
|
||||||
self.nanoleaf = Nanoleaf(data[CONF_HOST])
|
self.nanoleaf = Nanoleaf(async_get_clientsession(self.hass), data[CONF_HOST])
|
||||||
self.context["title_placeholders"] = {"name": self.reauth_entry.title}
|
self.context["title_placeholders"] = {"name": self.reauth_entry.title}
|
||||||
return await self.async_step_link()
|
return await self.async_step_link()
|
||||||
|
|
||||||
|
@ -106,7 +108,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
name = discovery_info["name"].replace(f".{discovery_info['type']}", "")
|
name = discovery_info["name"].replace(f".{discovery_info['type']}", "")
|
||||||
await self.async_set_unique_id(name)
|
await self.async_set_unique_id(name)
|
||||||
self._abort_if_unique_id_configured({CONF_HOST: host})
|
self._abort_if_unique_id_configured({CONF_HOST: host})
|
||||||
self.nanoleaf = Nanoleaf(host)
|
|
||||||
|
|
||||||
# Import from discovery integration
|
# Import from discovery integration
|
||||||
self.device_id = discovery_info["properties"]["id"]
|
self.device_id = discovery_info["properties"]["id"]
|
||||||
|
@ -116,16 +117,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
load_json, self.hass.config.path(CONFIG_FILE)
|
load_json, self.hass.config.path(CONFIG_FILE)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.nanoleaf.token = self.discovery_conf.get(self.device_id, {}).get(
|
auth_token: str | None = self.discovery_conf.get(self.device_id, {}).get(
|
||||||
"token", # >= 2021.4
|
"token", # >= 2021.4
|
||||||
self.discovery_conf.get(host, {}).get("token"), # < 2021.4
|
self.discovery_conf.get(host, {}).get("token"), # < 2021.4
|
||||||
)
|
)
|
||||||
if self.nanoleaf.token is not None:
|
if auth_token is not None:
|
||||||
|
self.nanoleaf = Nanoleaf(
|
||||||
|
async_get_clientsession(self.hass), host, auth_token
|
||||||
|
)
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Importing Nanoleaf %s from the discovery integration", name
|
"Importing Nanoleaf %s from the discovery integration", name
|
||||||
)
|
)
|
||||||
return await self.async_setup_finish(discovery_integration_import=True)
|
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}
|
self.context["title_placeholders"] = {"name": name}
|
||||||
return await self.async_step_link()
|
return await self.async_step_link()
|
||||||
|
|
||||||
|
@ -137,8 +141,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
return self.async_show_form(step_id="link")
|
return self.async_show_form(step_id="link")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(self.nanoleaf.authorize)
|
await self.nanoleaf.authorize()
|
||||||
except NotAuthorizingNewTokens:
|
except Unauthorized:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="link", errors={"base": "not_allowing_new_tokens"}
|
step_id="link", errors={"base": "not_allowing_new_tokens"}
|
||||||
)
|
)
|
||||||
|
@ -153,7 +157,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
self.reauth_entry,
|
self.reauth_entry,
|
||||||
data={
|
data={
|
||||||
**self.reauth_entry.data,
|
**self.reauth_entry.data,
|
||||||
CONF_TOKEN: self.nanoleaf.token,
|
CONF_TOKEN: self.nanoleaf.auth_token,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
|
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
|
||||||
|
@ -167,8 +171,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Importing Nanoleaf on %s from your configuration.yaml", config[CONF_HOST]
|
"Importing Nanoleaf on %s from your configuration.yaml", config[CONF_HOST]
|
||||||
)
|
)
|
||||||
self.nanoleaf = Nanoleaf(config[CONF_HOST])
|
self.nanoleaf = Nanoleaf(
|
||||||
self.nanoleaf.token = config[CONF_TOKEN]
|
async_get_clientsession(self.hass), config[CONF_HOST], config[CONF_TOKEN]
|
||||||
|
)
|
||||||
return await self.async_setup_finish()
|
return await self.async_setup_finish()
|
||||||
|
|
||||||
async def async_setup_finish(
|
async def async_setup_finish(
|
||||||
|
@ -176,9 +181,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Finish Nanoleaf config flow."""
|
"""Finish Nanoleaf config flow."""
|
||||||
try:
|
try:
|
||||||
info = await self.hass.async_add_executor_job(
|
await self.nanoleaf.get_info()
|
||||||
pynanoleaf_get_info, self.nanoleaf
|
|
||||||
)
|
|
||||||
except Unavailable:
|
except Unavailable:
|
||||||
return self.async_abort(reason="cannot_connect")
|
return self.async_abort(reason="cannot_connect")
|
||||||
except InvalidToken:
|
except InvalidToken:
|
||||||
|
@ -188,7 +191,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"Unknown error connecting with Nanoleaf at %s", self.nanoleaf.host
|
"Unknown error connecting with Nanoleaf at %s", self.nanoleaf.host
|
||||||
)
|
)
|
||||||
return self.async_abort(reason="unknown")
|
return self.async_abort(reason="unknown")
|
||||||
name = info["name"]
|
name = self.nanoleaf.name
|
||||||
|
|
||||||
await self.async_set_unique_id(name)
|
await self.async_set_unique_id(name)
|
||||||
self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host})
|
self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host})
|
||||||
|
@ -215,6 +218,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
title=name,
|
title=name,
|
||||||
data={
|
data={
|
||||||
CONF_HOST: self.nanoleaf.host,
|
CONF_HOST: self.nanoleaf.host,
|
||||||
CONF_TOKEN: self.nanoleaf.token,
|
CONF_TOKEN: self.nanoleaf.auth_token,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pynanoleaf import Unavailable
|
from aiohttp import ServerDisconnectedError
|
||||||
|
from aionanoleaf import Unavailable
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
|
@ -153,7 +154,7 @@ class NanoleafLight(LightEntity):
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if light is on."""
|
"""Return true if light is on."""
|
||||||
return self._state
|
return self._light.is_on
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hs_color(self):
|
def hs_color(self):
|
||||||
|
@ -165,7 +166,7 @@ class NanoleafLight(LightEntity):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORT_NANOLEAF
|
return SUPPORT_NANOLEAF
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Instruct the light to turn on."""
|
"""Instruct the light to turn on."""
|
||||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
hs_color = kwargs.get(ATTR_HS_COLOR)
|
||||||
|
@ -175,57 +176,61 @@ class NanoleafLight(LightEntity):
|
||||||
|
|
||||||
if hs_color:
|
if hs_color:
|
||||||
hue, saturation = hs_color
|
hue, saturation = hs_color
|
||||||
self._light.hue = int(hue)
|
await self._light.set_hue(int(hue))
|
||||||
self._light.saturation = int(saturation)
|
await self._light.set_saturation(int(saturation))
|
||||||
if color_temp_mired:
|
if color_temp_mired:
|
||||||
self._light.color_temperature = mired_to_kelvin(color_temp_mired)
|
await self._light.set_color_temperature(mired_to_kelvin(color_temp_mired))
|
||||||
|
|
||||||
if transition:
|
if transition:
|
||||||
if brightness: # tune to the required brightness in n seconds
|
if brightness: # tune to the required brightness in n seconds
|
||||||
self._light.brightness_transition(
|
await self._light.set_brightness(
|
||||||
int(brightness / 2.55), int(transition)
|
int(brightness / 2.55), transition=int(kwargs[ATTR_TRANSITION])
|
||||||
)
|
)
|
||||||
else: # If brightness is not specified, assume full brightness
|
else: # If brightness is not specified, assume full brightness
|
||||||
self._light.brightness_transition(100, int(transition))
|
await self._light.set_brightness(
|
||||||
|
100, transition=int(kwargs[ATTR_TRANSITION])
|
||||||
|
)
|
||||||
else: # If no transition is occurring, turn on the light
|
else: # If no transition is occurring, turn on the light
|
||||||
self._light.on = True
|
await self._light.turn_on()
|
||||||
if brightness:
|
if brightness:
|
||||||
self._light.brightness = int(brightness / 2.55)
|
await self._light.set_brightness(int(brightness / 2.55))
|
||||||
|
|
||||||
if effect:
|
if effect:
|
||||||
if effect not in self._effects_list:
|
if effect not in self._effects_list:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Attempting to apply effect not in the effect list: '{effect}'"
|
f"Attempting to apply effect not in the effect list: '{effect}'"
|
||||||
)
|
)
|
||||||
self._light.effect = effect
|
await self._light.set_effect(effect)
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs):
|
||||||
"""Instruct the light to turn off."""
|
"""Instruct the light to turn off."""
|
||||||
transition = kwargs.get(ATTR_TRANSITION)
|
transition = kwargs.get(ATTR_TRANSITION)
|
||||||
if transition:
|
if transition:
|
||||||
self._light.brightness_transition(0, int(transition))
|
await self._light.set_brightness(0, transition=int(transition))
|
||||||
else:
|
else:
|
||||||
self._light.on = False
|
await self._light.turn_off()
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self) -> None:
|
||||||
"""Fetch new state data for this light."""
|
"""Fetch new state data for this light."""
|
||||||
try:
|
try:
|
||||||
self._available = self._light.available
|
await self._light.get_info()
|
||||||
self._brightness = self._light.brightness
|
except ServerDisconnectedError:
|
||||||
self._effects_list = self._light.effects
|
# Retry the request once if the device disconnected
|
||||||
# Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color.
|
await self._light.get_info()
|
||||||
# This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359).
|
except Unavailable:
|
||||||
# Until fixed at the library level, we should ensure the effect exists before saving to light properties
|
|
||||||
self._effect = (
|
|
||||||
self._light.effect if self._light.effect in self._effects_list else None
|
|
||||||
)
|
|
||||||
if self._effect is None:
|
|
||||||
self._color_temp = self._light.color_temperature
|
|
||||||
self._hs_color = self._light.hue, self._light.saturation
|
|
||||||
else:
|
|
||||||
self._color_temp = None
|
|
||||||
self._hs_color = None
|
|
||||||
self._state = self._light.on
|
|
||||||
except Unavailable as err:
|
|
||||||
_LOGGER.error("Could not update status for %s (%s)", self.name, err)
|
|
||||||
self._available = False
|
self._available = False
|
||||||
|
return
|
||||||
|
self._available = True
|
||||||
|
self._brightness = self._light.brightness
|
||||||
|
self._effects_list = self._light.effects_list
|
||||||
|
# Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color.
|
||||||
|
# This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359).
|
||||||
|
# Until fixed at the library level, we should ensure the effect exists before saving to light properties
|
||||||
|
self._effect = (
|
||||||
|
self._light.effect if self._light.effect in self._effects_list else None
|
||||||
|
)
|
||||||
|
if self._effect is None:
|
||||||
|
self._color_temp = self._light.color_temperature
|
||||||
|
self._hs_color = self._light.hue, self._light.saturation
|
||||||
|
else:
|
||||||
|
self._color_temp = None
|
||||||
|
self._hs_color = None
|
||||||
|
self._state = self._light.is_on
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Nanoleaf",
|
"name": "Nanoleaf",
|
||||||
"config_flow": true,
|
"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": ["aionanoleaf==0.0.1"],
|
||||||
"zeroconf": ["_nanoleafms._tcp.local.", "_nanoleafapi._tcp.local."],
|
"zeroconf": ["_nanoleafms._tcp.local.", "_nanoleafapi._tcp.local."],
|
||||||
"homekit" : {
|
"homekit" : {
|
||||||
"models": [
|
"models": [
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
"""Nanoleaf integration util."""
|
|
||||||
from pynanoleaf.pynanoleaf import Nanoleaf
|
|
||||||
|
|
||||||
|
|
||||||
def pynanoleaf_get_info(nanoleaf_light: Nanoleaf) -> dict:
|
|
||||||
"""Get Nanoleaf light info."""
|
|
||||||
return nanoleaf_light.info
|
|
|
@ -218,6 +218,9 @@ aiomodernforms==0.1.8
|
||||||
# homeassistant.components.yamaha_musiccast
|
# homeassistant.components.yamaha_musiccast
|
||||||
aiomusiccast==0.9.2
|
aiomusiccast==0.9.2
|
||||||
|
|
||||||
|
# homeassistant.components.nanoleaf
|
||||||
|
aionanoleaf==0.0.1
|
||||||
|
|
||||||
# homeassistant.components.keyboard_remote
|
# homeassistant.components.keyboard_remote
|
||||||
aionotify==0.2.0
|
aionotify==0.2.0
|
||||||
|
|
||||||
|
@ -1649,9 +1652,6 @@ pymyq==3.1.4
|
||||||
# homeassistant.components.mysensors
|
# homeassistant.components.mysensors
|
||||||
pymysensors==0.21.0
|
pymysensors==0.21.0
|
||||||
|
|
||||||
# homeassistant.components.nanoleaf
|
|
||||||
pynanoleaf==0.1.0
|
|
||||||
|
|
||||||
# homeassistant.components.nello
|
# homeassistant.components.nello
|
||||||
pynello==2.0.3
|
pynello==2.0.3
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,9 @@ aiomodernforms==0.1.8
|
||||||
# homeassistant.components.yamaha_musiccast
|
# homeassistant.components.yamaha_musiccast
|
||||||
aiomusiccast==0.9.2
|
aiomusiccast==0.9.2
|
||||||
|
|
||||||
|
# homeassistant.components.nanoleaf
|
||||||
|
aionanoleaf==0.0.1
|
||||||
|
|
||||||
# homeassistant.components.notion
|
# homeassistant.components.notion
|
||||||
aionotion==3.0.2
|
aionotion==3.0.2
|
||||||
|
|
||||||
|
@ -968,9 +971,6 @@ pymyq==3.1.4
|
||||||
# homeassistant.components.mysensors
|
# homeassistant.components.mysensors
|
||||||
pymysensors==0.21.0
|
pymysensors==0.21.0
|
||||||
|
|
||||||
# homeassistant.components.nanoleaf
|
|
||||||
pynanoleaf==0.1.0
|
|
||||||
|
|
||||||
# homeassistant.components.netgear
|
# homeassistant.components.netgear
|
||||||
pynetgear==0.7.0
|
pynetgear==0.7.0
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
"""Test the Nanoleaf config flow."""
|
"""Test the Nanoleaf config flow."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from pynanoleaf import InvalidToken, NotAuthorizingNewTokens, Unavailable
|
from aionanoleaf import InvalidToken, NanoleafException, Unauthorized, Unavailable
|
||||||
from pynanoleaf.pynanoleaf import NanoleafError
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
@ -23,6 +22,21 @@ TEST_DEVICE_ID = "5E:2E:EA:XX:XX:XX"
|
||||||
TEST_OTHER_DEVICE_ID = "5E:2E:EA:YY:YY:YY"
|
TEST_OTHER_DEVICE_ID = "5E:2E:EA:YY:YY:YY"
|
||||||
|
|
||||||
|
|
||||||
|
def _mock_nanoleaf(
|
||||||
|
host: str = TEST_HOST,
|
||||||
|
auth_token: str = TEST_TOKEN,
|
||||||
|
authorize_error: Exception | None = None,
|
||||||
|
get_info_error: Exception | None = None,
|
||||||
|
):
|
||||||
|
nanoleaf = MagicMock()
|
||||||
|
nanoleaf.name = TEST_NAME
|
||||||
|
nanoleaf.host = host
|
||||||
|
nanoleaf.auth_token = auth_token
|
||||||
|
nanoleaf.authorize = AsyncMock(side_effect=authorize_error)
|
||||||
|
nanoleaf.get_info = AsyncMock(side_effect=get_info_error)
|
||||||
|
return nanoleaf
|
||||||
|
|
||||||
|
|
||||||
async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None:
|
async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None:
|
||||||
"""Test we handle Unavailable in user and link step."""
|
"""Test we handle Unavailable in user and link step."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
@ -30,7 +44,7 @@ async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None
|
||||||
)
|
)
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
side_effect=Unavailable("message"),
|
side_effect=Unavailable,
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
|
@ -58,7 +72,7 @@ async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
side_effect=Unavailable("message"),
|
side_effect=Unavailable,
|
||||||
):
|
):
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
|
@ -71,8 +85,8 @@ async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error, reason",
|
"error, reason",
|
||||||
[
|
[
|
||||||
(Unavailable("message"), "cannot_connect"),
|
(Unavailable, "cannot_connect"),
|
||||||
(InvalidToken("message"), "invalid_token"),
|
(InvalidToken, "invalid_token"),
|
||||||
(Exception, "unknown"),
|
(Exception, "unknown"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -85,7 +99,6 @@ async def test_user_error_setup_finish(
|
||||||
)
|
)
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
return_value=None,
|
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
|
@ -98,9 +111,8 @@ async def test_user_error_setup_finish(
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
return_value=None,
|
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info",
|
||||||
side_effect=error,
|
side_effect=error,
|
||||||
):
|
):
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
|
@ -117,19 +129,10 @@ async def test_user_not_authorizing_new_tokens_user_step_link_step(
|
||||||
"""Test we handle NotAuthorizingNewTokens in user step and link step."""
|
"""Test we handle NotAuthorizingNewTokens in user step and link step."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
||||||
|
return_value=_mock_nanoleaf(authorize_error=Unauthorized()),
|
||||||
) as mock_nanoleaf, patch(
|
) as mock_nanoleaf, patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
|
||||||
return_value={"name": TEST_NAME},
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.nanoleaf.async_setup_entry", return_value=True
|
"homeassistant.components.nanoleaf.async_setup_entry", return_value=True
|
||||||
) as mock_setup_entry:
|
) as mock_setup_entry:
|
||||||
nanoleaf = mock_nanoleaf.return_value
|
|
||||||
nanoleaf.authorize.side_effect = NotAuthorizingNewTokens(
|
|
||||||
"Not authorizing new tokens"
|
|
||||||
)
|
|
||||||
nanoleaf.host = TEST_HOST
|
|
||||||
nanoleaf.token = TEST_TOKEN
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
@ -160,8 +163,7 @@ async def test_user_not_authorizing_new_tokens_user_step_link_step(
|
||||||
assert result4["errors"] == {"base": "not_allowing_new_tokens"}
|
assert result4["errors"] == {"base": "not_allowing_new_tokens"}
|
||||||
assert result4["step_id"] == "link"
|
assert result4["step_id"] == "link"
|
||||||
|
|
||||||
nanoleaf.authorize.side_effect = None
|
mock_nanoleaf.return_value.authorize.side_effect = None
|
||||||
nanoleaf.authorize.return_value = None
|
|
||||||
|
|
||||||
result5 = await hass.config_entries.flow.async_configure(
|
result5 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
|
@ -183,8 +185,8 @@ async def test_user_exception_user_step(hass: HomeAssistant) -> None:
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
||||||
side_effect=Exception,
|
return_value=_mock_nanoleaf(authorize_error=Exception()),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
|
@ -198,36 +200,29 @@ async def test_user_exception_user_step(hass: HomeAssistant) -> None:
|
||||||
assert not result2["last_step"]
|
assert not result2["last_step"]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
||||||
return_value=None,
|
return_value=_mock_nanoleaf(),
|
||||||
):
|
) as mock_nanoleaf:
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{
|
{
|
||||||
CONF_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert result3["step_id"] == "link"
|
assert result3["step_id"] == "link"
|
||||||
|
|
||||||
|
mock_nanoleaf.return_value.authorize.side_effect = Exception()
|
||||||
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
|
||||||
side_effect=Exception,
|
|
||||||
):
|
|
||||||
result4 = await hass.config_entries.flow.async_configure(
|
result4 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
assert result4["type"] == "form"
|
assert result4["type"] == "form"
|
||||||
assert result4["step_id"] == "link"
|
assert result4["step_id"] == "link"
|
||||||
assert result4["errors"] == {"base": "unknown"}
|
assert result4["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
with patch(
|
mock_nanoleaf.return_value.authorize.side_effect = None
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
mock_nanoleaf.return_value.get_info.side_effect = Exception()
|
||||||
return_value=None,
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
|
||||||
side_effect=Exception,
|
|
||||||
):
|
|
||||||
result5 = await hass.config_entries.flow.async_configure(
|
result5 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{},
|
{},
|
||||||
|
@ -249,8 +244,7 @@ async def test_discovery_link_unavailable(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test discovery and abort if device is unavailable."""
|
"""Test discovery and abort if device is unavailable."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info",
|
||||||
return_value={"name": TEST_NAME},
|
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.load_json",
|
"homeassistant.components.nanoleaf.config_flow.load_json",
|
||||||
return_value={},
|
return_value={},
|
||||||
|
@ -278,7 +272,7 @@ async def test_discovery_link_unavailable(
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
|
||||||
side_effect=Unavailable("message"),
|
side_effect=Unavailable,
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
assert result["type"] == "abort"
|
assert result["type"] == "abort"
|
||||||
|
@ -287,10 +281,6 @@ async def test_discovery_link_unavailable(
|
||||||
|
|
||||||
async def test_reauth(hass: HomeAssistant) -> None:
|
async def test_reauth(hass: HomeAssistant) -> None:
|
||||||
"""Test Nanoleaf reauth flow."""
|
"""Test Nanoleaf reauth flow."""
|
||||||
nanoleaf = MagicMock()
|
|
||||||
nanoleaf.host = TEST_HOST
|
|
||||||
nanoleaf.token = TEST_TOKEN
|
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
unique_id=TEST_NAME,
|
unique_id=TEST_NAME,
|
||||||
|
@ -300,7 +290,7 @@ async def test_reauth(hass: HomeAssistant) -> None:
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
||||||
return_value=nanoleaf,
|
return_value=_mock_nanoleaf(),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.nanoleaf.async_setup_entry",
|
"homeassistant.components.nanoleaf.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
|
@ -331,8 +321,8 @@ async def test_reauth(hass: HomeAssistant) -> None:
|
||||||
async def test_import_config(hass: HomeAssistant) -> None:
|
async def test_import_config(hass: HomeAssistant) -> None:
|
||||||
"""Test configuration import."""
|
"""Test configuration import."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
||||||
return_value={"name": TEST_NAME},
|
return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.nanoleaf.async_setup_entry",
|
"homeassistant.components.nanoleaf.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
|
@ -355,17 +345,17 @@ async def test_import_config(hass: HomeAssistant) -> None:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error, reason",
|
"error, reason",
|
||||||
[
|
[
|
||||||
(Unavailable("message"), "cannot_connect"),
|
(Unavailable, "cannot_connect"),
|
||||||
(InvalidToken("message"), "invalid_token"),
|
(InvalidToken, "invalid_token"),
|
||||||
(Exception, "unknown"),
|
(Exception, "unknown"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_import_config_error(
|
async def test_import_config_error(
|
||||||
hass: HomeAssistant, error: NanoleafError, reason: str
|
hass: HomeAssistant, error: NanoleafException, reason: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test configuration import with errors in setup_finish."""
|
"""Test configuration import with errors in setup_finish."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info",
|
||||||
side_effect=error,
|
side_effect=error,
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
@ -432,8 +422,8 @@ async def test_import_discovery_integration(
|
||||||
"homeassistant.components.nanoleaf.config_flow.load_json",
|
"homeassistant.components.nanoleaf.config_flow.load_json",
|
||||||
return_value=dict(nanoleaf_conf_file),
|
return_value=dict(nanoleaf_conf_file),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
||||||
return_value={"name": TEST_NAME},
|
return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.nanoleaf.config_flow.save_json",
|
"homeassistant.components.nanoleaf.config_flow.save_json",
|
||||||
return_value=None,
|
return_value=None,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue