Cleanups in Twinkly code (#64139)
* Cleanup Twinkly code * Add codeowner' * Change const namespull/64159/head
parent
e609f196bc
commit
efe34c8d13
|
@ -973,8 +973,8 @@ homeassistant/components/tuya/* @Tuya @zlinoliver @METISU @frenck
|
||||||
tests/components/tuya/* @Tuya @zlinoliver @METISU @frenck
|
tests/components/tuya/* @Tuya @zlinoliver @METISU @frenck
|
||||||
homeassistant/components/twentemilieu/* @frenck
|
homeassistant/components/twentemilieu/* @frenck
|
||||||
tests/components/twentemilieu/* @frenck
|
tests/components/twentemilieu/* @frenck
|
||||||
homeassistant/components/twinkly/* @dr1rrb
|
homeassistant/components/twinkly/* @dr1rrb @Robbie1221
|
||||||
tests/components/twinkly/* @dr1rrb
|
tests/components/twinkly/* @dr1rrb @Robbie1221
|
||||||
homeassistant/components/ubus/* @noltari
|
homeassistant/components/ubus/* @noltari
|
||||||
homeassistant/components/unifi/* @Kane610
|
homeassistant/components/unifi/* @Kane610
|
||||||
tests/components/unifi/* @Kane610
|
tests/components/unifi/* @Kane610
|
||||||
|
|
|
@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import CONF_ENTRY_HOST, CONF_ENTRY_ID, DATA_CLIENT, DATA_DEVICE_INFO, DOMAIN
|
from .const import CONF_HOST, DATA_CLIENT, DATA_DEVICE_INFO, DOMAIN
|
||||||
|
|
||||||
PLATFORMS = [Platform.LIGHT]
|
PLATFORMS = [Platform.LIGHT]
|
||||||
|
|
||||||
|
@ -22,10 +22,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
||||||
# We setup the client here so if at some point we add any other entity for this device,
|
# We setup the client here so if at some point we add any other entity for this device,
|
||||||
# we will be able to properly share the connection.
|
# we will be able to properly share the connection.
|
||||||
uuid = entry.data[CONF_ENTRY_ID]
|
host = entry.data[CONF_HOST]
|
||||||
host = entry.data[CONF_ENTRY_HOST]
|
|
||||||
|
|
||||||
hass.data[DOMAIN].setdefault(uuid, {})
|
hass.data[DOMAIN].setdefault(entry.entry_id, {})
|
||||||
|
|
||||||
client = Twinkly(host, async_get_clientsession(hass))
|
client = Twinkly(host, async_get_clientsession(hass))
|
||||||
|
|
||||||
|
@ -34,8 +33,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
except (asyncio.TimeoutError, ClientError) as exception:
|
except (asyncio.TimeoutError, ClientError) as exception:
|
||||||
raise ConfigEntryNotReady from exception
|
raise ConfigEntryNotReady from exception
|
||||||
|
|
||||||
hass.data[DOMAIN][uuid][DATA_CLIENT] = client
|
hass.data[DOMAIN][entry.entry_id][DATA_CLIENT] = client
|
||||||
hass.data[DOMAIN][uuid][DATA_DEVICE_INFO] = device_info
|
hass.data[DOMAIN][entry.entry_id][DATA_DEVICE_INFO] = device_info
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
@ -45,9 +44,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Remove a twinkly entry."""
|
"""Remove a twinkly entry."""
|
||||||
|
|
||||||
# For now light entries don't have unload method, so we don't have to async_forward_entry_unload
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
# However we still have to cleanup the shared client!
|
if unload_ok:
|
||||||
uuid = entry.data[CONF_ENTRY_ID]
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
hass.data[DOMAIN].pop(uuid)
|
|
||||||
|
|
||||||
return True
|
return unload_ok
|
||||||
|
|
|
@ -14,16 +14,7 @@ from homeassistant.components import dhcp
|
||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import (
|
from .const import CONF_ID, CONF_MODEL, CONF_NAME, DEV_ID, DEV_MODEL, DEV_NAME, DOMAIN
|
||||||
CONF_ENTRY_HOST,
|
|
||||||
CONF_ENTRY_ID,
|
|
||||||
CONF_ENTRY_MODEL,
|
|
||||||
CONF_ENTRY_NAME,
|
|
||||||
DEV_ID,
|
|
||||||
DEV_MODEL,
|
|
||||||
DEV_NAME,
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -49,16 +40,14 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
device_info = await Twinkly(
|
device_info = await Twinkly(
|
||||||
host, async_get_clientsession(self.hass)
|
host, async_get_clientsession(self.hass)
|
||||||
).get_details()
|
).get_details()
|
||||||
|
except (asyncio.TimeoutError, ClientError):
|
||||||
|
errors[CONF_HOST] = "cannot_connect"
|
||||||
|
else:
|
||||||
await self.async_set_unique_id(device_info[DEV_ID])
|
await self.async_set_unique_id(device_info[DEV_ID])
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
return self._create_entry_from_device(device_info, host)
|
return self._create_entry_from_device(device_info, host)
|
||||||
|
|
||||||
except (asyncio.TimeoutError, ClientError) as err:
|
|
||||||
_LOGGER.info("Cannot reach Twinkly '%s' (client)", host, exc_info=err)
|
|
||||||
errors[CONF_HOST] = "cannot_connect"
|
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user", data_schema=Schema(schema), errors=errors
|
step_id="user", data_schema=Schema(schema), errors=errors
|
||||||
)
|
)
|
||||||
|
@ -67,14 +56,12 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
self, discovery_info: dhcp.DhcpServiceInfo
|
self, discovery_info: dhcp.DhcpServiceInfo
|
||||||
) -> data_entry_flow.FlowResult:
|
) -> data_entry_flow.FlowResult:
|
||||||
"""Handle dhcp discovery for twinkly."""
|
"""Handle dhcp discovery for twinkly."""
|
||||||
self._async_abort_entries_match({CONF_ENTRY_HOST: discovery_info.ip})
|
self._async_abort_entries_match({CONF_HOST: discovery_info.ip})
|
||||||
device_info = await Twinkly(
|
device_info = await Twinkly(
|
||||||
discovery_info.ip, async_get_clientsession(self.hass)
|
discovery_info.ip, async_get_clientsession(self.hass)
|
||||||
).get_details()
|
).get_details()
|
||||||
await self.async_set_unique_id(device_info[DEV_ID])
|
await self.async_set_unique_id(device_info[DEV_ID])
|
||||||
self._abort_if_unique_id_configured(
|
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
|
||||||
updates={CONF_ENTRY_HOST: discovery_info.ip}
|
|
||||||
)
|
|
||||||
|
|
||||||
self._discovered_device = (device_info, discovery_info.ip)
|
self._discovered_device = (device_info, discovery_info.ip)
|
||||||
return await self.async_step_discovery_confirm()
|
return await self.async_step_discovery_confirm()
|
||||||
|
@ -106,9 +93,9 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=device_info[DEV_NAME],
|
title=device_info[DEV_NAME],
|
||||||
data={
|
data={
|
||||||
CONF_ENTRY_HOST: host,
|
CONF_HOST: host,
|
||||||
CONF_ENTRY_ID: device_info[DEV_ID],
|
CONF_ID: device_info[DEV_ID],
|
||||||
CONF_ENTRY_NAME: device_info[DEV_NAME],
|
CONF_NAME: device_info[DEV_NAME],
|
||||||
CONF_ENTRY_MODEL: device_info[DEV_MODEL],
|
CONF_MODEL: device_info[DEV_MODEL],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
DOMAIN = "twinkly"
|
DOMAIN = "twinkly"
|
||||||
|
|
||||||
# Keys of the config entry
|
# Keys of the config entry
|
||||||
CONF_ENTRY_ID = "id"
|
CONF_ID = "id"
|
||||||
CONF_ENTRY_HOST = "host"
|
CONF_HOST = "host"
|
||||||
CONF_ENTRY_NAME = "name"
|
CONF_NAME = "name"
|
||||||
CONF_ENTRY_MODEL = "model"
|
CONF_MODEL = "model"
|
||||||
|
|
||||||
# Strongly named HA attributes keys
|
# Strongly named HA attributes keys
|
||||||
ATTR_HOST = "host"
|
ATTR_HOST = "host"
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientError
|
from aiohttp import ClientError
|
||||||
from ttls.client import Twinkly
|
from ttls.client import Twinkly
|
||||||
|
@ -22,11 +23,10 @@ from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_HOST,
|
CONF_HOST,
|
||||||
CONF_ENTRY_HOST,
|
CONF_ID,
|
||||||
CONF_ENTRY_ID,
|
CONF_MODEL,
|
||||||
CONF_ENTRY_MODEL,
|
CONF_NAME,
|
||||||
CONF_ENTRY_NAME,
|
|
||||||
DATA_CLIENT,
|
DATA_CLIENT,
|
||||||
DATA_DEVICE_INFO,
|
DATA_DEVICE_INFO,
|
||||||
DEV_LED_PROFILE,
|
DEV_LED_PROFILE,
|
||||||
|
@ -48,8 +48,8 @@ async def async_setup_entry(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Setups an entity from a config entry (UI config flow)."""
|
"""Setups an entity from a config entry (UI config flow)."""
|
||||||
|
|
||||||
client = hass.data[DOMAIN][config_entry.data[CONF_ENTRY_ID]][DATA_CLIENT]
|
client = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT]
|
||||||
device_info = hass.data[DOMAIN][config_entry.data[CONF_ENTRY_ID]][DATA_DEVICE_INFO]
|
device_info = hass.data[DOMAIN][config_entry.entry_id][DATA_DEVICE_INFO]
|
||||||
|
|
||||||
entity = TwinklyLight(config_entry, client, device_info)
|
entity = TwinklyLight(config_entry, client, device_info)
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ class TwinklyLight(LightEntity):
|
||||||
device_info,
|
device_info,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a TwinklyLight entity."""
|
"""Initialize a TwinklyLight entity."""
|
||||||
self._id = conf.data[CONF_ENTRY_ID]
|
self._id = conf.data[CONF_ID]
|
||||||
self._conf = conf
|
self._conf = conf
|
||||||
|
|
||||||
if device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGBW:
|
if device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGBW:
|
||||||
|
@ -84,20 +84,15 @@ class TwinklyLight(LightEntity):
|
||||||
# Those are saved in the config entry in order to have meaningful values even
|
# Those are saved in the config entry in order to have meaningful values even
|
||||||
# if the device is currently offline.
|
# if the device is currently offline.
|
||||||
# They are expected to be updated using the device_info.
|
# They are expected to be updated using the device_info.
|
||||||
self.__name = conf.data[CONF_ENTRY_NAME]
|
self._name = conf.data[CONF_NAME]
|
||||||
self.__model = conf.data[CONF_ENTRY_MODEL]
|
self._model = conf.data[CONF_MODEL]
|
||||||
|
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
# Set default state before any update
|
# Set default state before any update
|
||||||
self._is_on = False
|
self._is_on = False
|
||||||
self._is_available = False
|
self._is_available = False
|
||||||
self._attributes = {ATTR_HOST: self._client.host}
|
self._attributes: dict[Any, Any] = {}
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self) -> bool:
|
|
||||||
"""Get a boolean which indicates if this entity should be polled."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
|
@ -112,12 +107,12 @@ class TwinklyLight(LightEntity):
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Name of the device."""
|
"""Name of the device."""
|
||||||
return self.__name if self.__name else "Twinkly light"
|
return self._name if self._name else "Twinkly light"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self) -> str:
|
def model(self) -> str:
|
||||||
"""Name of the device."""
|
"""Name of the device."""
|
||||||
return self.__model
|
return self._model
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self) -> str:
|
def icon(self) -> str:
|
||||||
|
@ -127,15 +122,11 @@ class TwinklyLight(LightEntity):
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo | None:
|
def device_info(self) -> DeviceInfo | None:
|
||||||
"""Get device specific attributes."""
|
"""Get device specific attributes."""
|
||||||
return (
|
return DeviceInfo(
|
||||||
DeviceInfo(
|
identifiers={(DOMAIN, self._id)},
|
||||||
identifiers={(DOMAIN, self._id)},
|
manufacturer="LEDWORKS",
|
||||||
manufacturer="LEDWORKS",
|
model=self.model,
|
||||||
model=self.model,
|
name=self.name,
|
||||||
name=self.name,
|
|
||||||
)
|
|
||||||
if self._id
|
|
||||||
else None # device_info is available only for entities configured from the UI
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -149,10 +140,6 @@ class TwinklyLight(LightEntity):
|
||||||
|
|
||||||
attributes = self._attributes
|
attributes = self._attributes
|
||||||
|
|
||||||
# Make sure to update any normalized property
|
|
||||||
attributes[ATTR_HOST] = self._client.host
|
|
||||||
attributes[ATTR_BRIGHTNESS] = self._attr_brightness
|
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs) -> None:
|
async def async_turn_on(self, **kwargs) -> None:
|
||||||
|
@ -204,7 +191,7 @@ class TwinklyLight(LightEntity):
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Asynchronously updates the device properties."""
|
"""Asynchronously updates the device properties."""
|
||||||
_LOGGER.info("Updating '%s'", self._client.host)
|
_LOGGER.debug("Updating '%s'", self._client.host)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._is_on = await self._client.is_on()
|
self._is_on = await self._client.is_on()
|
||||||
|
@ -224,25 +211,24 @@ class TwinklyLight(LightEntity):
|
||||||
DEV_NAME in device_info
|
DEV_NAME in device_info
|
||||||
and DEV_MODEL in device_info
|
and DEV_MODEL in device_info
|
||||||
and (
|
and (
|
||||||
device_info[DEV_NAME] != self.__name
|
device_info[DEV_NAME] != self._name
|
||||||
or device_info[DEV_MODEL] != self.__model
|
or device_info[DEV_MODEL] != self._model
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
self.__name = device_info[DEV_NAME]
|
self._name = device_info[DEV_NAME]
|
||||||
self.__model = device_info[DEV_MODEL]
|
self._model = device_info[DEV_MODEL]
|
||||||
|
|
||||||
if self._conf is not None:
|
# If the name has changed, persist it in conf entry,
|
||||||
# If the name has changed, persist it in conf entry,
|
# so we will be able to restore this new name if hass is started while the LED string is offline.
|
||||||
# so we will be able to restore this new name if hass is started while the LED string is offline.
|
self.hass.config_entries.async_update_entry(
|
||||||
self.hass.config_entries.async_update_entry(
|
self._conf,
|
||||||
self._conf,
|
data={
|
||||||
data={
|
CONF_HOST: self._client.host, # this cannot change
|
||||||
CONF_ENTRY_HOST: self._client.host, # this cannot change
|
CONF_ID: self._id, # this cannot change
|
||||||
CONF_ENTRY_ID: self._id, # this cannot change
|
CONF_NAME: self._name,
|
||||||
CONF_ENTRY_NAME: self.__name,
|
CONF_MODEL: self._model,
|
||||||
CONF_ENTRY_MODEL: self.__model,
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
|
|
||||||
for key, value in device_info.items():
|
for key, value in device_info.items():
|
||||||
if key not in HIDDEN_DEV_VALUES:
|
if key not in HIDDEN_DEV_VALUES:
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
"name": "Twinkly",
|
"name": "Twinkly",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/twinkly",
|
"documentation": "https://www.home-assistant.io/integrations/twinkly",
|
||||||
"requirements": ["ttls==1.4.2"],
|
"requirements": ["ttls==1.4.2"],
|
||||||
"dependencies": [],
|
"codeowners": ["@dr1rrb", "@Robbie1221"],
|
||||||
"codeowners": ["@dr1rrb"],
|
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dhcp": [{ "hostname": "twinkly_*" }],
|
"dhcp": [{ "hostname": "twinkly_*" }],
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"title": "Twinkly",
|
"title": "Twinkly",
|
||||||
"description": "Set up your Twinkly led string",
|
"description": "Set up your Twinkly led string",
|
||||||
"data": {
|
"data": {
|
||||||
"host": "Host (or IP address) of your twinkly device"
|
"host": "[%key:common::config_flow::data::host%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"discovery_confirm": {
|
"discovery_confirm": {
|
||||||
|
|
|
@ -4,10 +4,10 @@ from unittest.mock import patch
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import dhcp
|
from homeassistant.components import dhcp
|
||||||
from homeassistant.components.twinkly.const import (
|
from homeassistant.components.twinkly.const import (
|
||||||
CONF_ENTRY_HOST,
|
CONF_HOST,
|
||||||
CONF_ENTRY_ID,
|
CONF_ID,
|
||||||
CONF_ENTRY_MODEL,
|
CONF_MODEL,
|
||||||
CONF_ENTRY_NAME,
|
CONF_NAME,
|
||||||
DOMAIN as TWINKLY_DOMAIN,
|
DOMAIN as TWINKLY_DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,12 +31,12 @@ async def test_invalid_host(hass):
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_ENTRY_HOST: "dummy"},
|
{CONF_HOST: "dummy"},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
assert result["errors"] == {CONF_ENTRY_HOST: "cannot_connect"}
|
assert result["errors"] == {CONF_HOST: "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
async def test_success_flow(hass):
|
async def test_success_flow(hass):
|
||||||
|
@ -55,16 +55,16 @@ async def test_success_flow(hass):
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_ENTRY_HOST: "dummy"},
|
{CONF_HOST: "dummy"},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "create_entry"
|
assert result["type"] == "create_entry"
|
||||||
assert result["title"] == client.id
|
assert result["title"] == client.id
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_ENTRY_HOST: "dummy",
|
CONF_HOST: "dummy",
|
||||||
CONF_ENTRY_ID: client.id,
|
CONF_ID: client.id,
|
||||||
CONF_ENTRY_NAME: client.id,
|
CONF_NAME: client.id,
|
||||||
CONF_ENTRY_MODEL: TEST_MODEL,
|
CONF_MODEL: TEST_MODEL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,10 +114,10 @@ async def test_dhcp_success(hass):
|
||||||
assert result["type"] == "create_entry"
|
assert result["type"] == "create_entry"
|
||||||
assert result["title"] == client.id
|
assert result["title"] == client.id
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_ENTRY_HOST: "1.2.3.4",
|
CONF_HOST: "1.2.3.4",
|
||||||
CONF_ENTRY_ID: client.id,
|
CONF_ID: client.id,
|
||||||
CONF_ENTRY_NAME: client.id,
|
CONF_NAME: client.id,
|
||||||
CONF_ENTRY_MODEL: TEST_MODEL,
|
CONF_MODEL: TEST_MODEL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,10 +128,10 @@ async def test_dhcp_already_exists(hass):
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=TWINKLY_DOMAIN,
|
domain=TWINKLY_DOMAIN,
|
||||||
data={
|
data={
|
||||||
CONF_ENTRY_HOST: "1.2.3.4",
|
CONF_HOST: "1.2.3.4",
|
||||||
CONF_ENTRY_ID: client.id,
|
CONF_ID: client.id,
|
||||||
CONF_ENTRY_NAME: client.id,
|
CONF_NAME: client.id,
|
||||||
CONF_ENTRY_MODEL: TEST_MODEL,
|
CONF_MODEL: TEST_MODEL,
|
||||||
},
|
},
|
||||||
unique_id=client.id,
|
unique_id=client.id,
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from homeassistant.components.twinkly import async_setup_entry, async_unload_entry
|
|
||||||
from homeassistant.components.twinkly.const import (
|
from homeassistant.components.twinkly.const import (
|
||||||
CONF_ENTRY_HOST,
|
CONF_HOST,
|
||||||
CONF_ENTRY_ID,
|
CONF_ID,
|
||||||
CONF_ENTRY_MODEL,
|
CONF_MODEL,
|
||||||
CONF_ENTRY_NAME,
|
CONF_NAME,
|
||||||
DOMAIN as TWINKLY_DOMAIN,
|
DOMAIN as TWINKLY_DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
@ -23,7 +22,7 @@ from tests.components.twinkly import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_entry(hass: HomeAssistant):
|
async def test_load_unload_entry(hass: HomeAssistant):
|
||||||
"""Validate that setup entry also configure the client."""
|
"""Validate that setup entry also configure the client."""
|
||||||
client = ClientMock()
|
client = ClientMock()
|
||||||
|
|
||||||
|
@ -31,47 +30,24 @@ async def test_setup_entry(hass: HomeAssistant):
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
domain=TWINKLY_DOMAIN,
|
domain=TWINKLY_DOMAIN,
|
||||||
data={
|
data={
|
||||||
CONF_ENTRY_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
CONF_ENTRY_ID: id,
|
CONF_ID: id,
|
||||||
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL,
|
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||||
CONF_ENTRY_MODEL: TEST_MODEL,
|
CONF_MODEL: TEST_MODEL,
|
||||||
},
|
},
|
||||||
entry_id=id,
|
entry_id=id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def setup_mock(_, __):
|
config_entry.add_to_hass(hass)
|
||||||
return True
|
|
||||||
|
|
||||||
with patch(
|
with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
||||||
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setup",
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
side_effect=setup_mock,
|
|
||||||
), patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
|
||||||
await async_setup_entry(hass, config_entry)
|
|
||||||
|
|
||||||
assert hass.data[TWINKLY_DOMAIN][id] is not None
|
assert config_entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
|
|
||||||
async def test_unload_entry(hass: HomeAssistant):
|
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||||
"""Validate that unload entry also clear the client."""
|
|
||||||
|
|
||||||
id = str(uuid4())
|
|
||||||
config_entry = MockConfigEntry(
|
|
||||||
domain=TWINKLY_DOMAIN,
|
|
||||||
data={
|
|
||||||
CONF_ENTRY_HOST: TEST_HOST,
|
|
||||||
CONF_ENTRY_ID: id,
|
|
||||||
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL,
|
|
||||||
CONF_ENTRY_MODEL: TEST_MODEL,
|
|
||||||
},
|
|
||||||
entry_id=id,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Put random content at the location where the client should have been placed by setup
|
|
||||||
hass.data.setdefault(TWINKLY_DOMAIN, {})[id] = config_entry
|
|
||||||
|
|
||||||
await async_unload_entry(hass, config_entry)
|
|
||||||
|
|
||||||
assert hass.data[TWINKLY_DOMAIN].get(id) is None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_config_entry_not_ready(hass: HomeAssistant):
|
async def test_config_entry_not_ready(hass: HomeAssistant):
|
||||||
|
@ -82,10 +58,10 @@ async def test_config_entry_not_ready(hass: HomeAssistant):
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
domain=TWINKLY_DOMAIN,
|
domain=TWINKLY_DOMAIN,
|
||||||
data={
|
data={
|
||||||
CONF_ENTRY_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
CONF_ENTRY_ID: id,
|
CONF_ID: id,
|
||||||
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL,
|
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||||
CONF_ENTRY_MODEL: TEST_MODEL,
|
CONF_MODEL: TEST_MODEL,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@ from __future__ import annotations
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||||
from homeassistant.components.twinkly.const import (
|
from homeassistant.components.twinkly.const import (
|
||||||
CONF_ENTRY_HOST,
|
CONF_HOST,
|
||||||
CONF_ENTRY_ID,
|
CONF_ID,
|
||||||
CONF_ENTRY_MODEL,
|
CONF_MODEL,
|
||||||
CONF_ENTRY_NAME,
|
CONF_NAME,
|
||||||
DOMAIN as TWINKLY_DOMAIN,
|
DOMAIN as TWINKLY_DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -16,25 +17,19 @@ from homeassistant.helpers.device_registry import DeviceEntry
|
||||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
from tests.components.twinkly import (
|
from tests.components.twinkly import TEST_MODEL, TEST_NAME_ORIGINAL, ClientMock
|
||||||
TEST_HOST,
|
|
||||||
TEST_MODEL,
|
|
||||||
TEST_NAME_ORIGINAL,
|
|
||||||
ClientMock,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_initial_state(hass: HomeAssistant):
|
async def test_initial_state(hass: HomeAssistant):
|
||||||
"""Validate that entity and device states are updated on startup."""
|
"""Validate that entity and device states are updated on startup."""
|
||||||
entity, device, _ = await _create_entries(hass)
|
entity, device, _, _ = await _create_entries(hass)
|
||||||
|
|
||||||
state = hass.states.get(entity.entity_id)
|
state = hass.states.get(entity.entity_id)
|
||||||
|
|
||||||
# Basic state properties
|
# Basic state properties
|
||||||
assert state.name == entity.unique_id
|
assert state.name == entity.unique_id
|
||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
assert state.attributes["host"] == TEST_HOST
|
assert state.attributes[ATTR_BRIGHTNESS] == 26
|
||||||
assert state.attributes["brightness"] == 26
|
|
||||||
assert state.attributes["friendly_name"] == entity.unique_id
|
assert state.attributes["friendly_name"] == entity.unique_id
|
||||||
assert state.attributes["icon"] == "mdi:string-lights"
|
assert state.attributes["icon"] == "mdi:string-lights"
|
||||||
|
|
||||||
|
@ -54,7 +49,7 @@ async def test_turn_on_off(hass: HomeAssistant):
|
||||||
client = ClientMock()
|
client = ClientMock()
|
||||||
client.state = False
|
client.state = False
|
||||||
client.brightness = {"mode": "enabled", "value": 20}
|
client.brightness = {"mode": "enabled", "value": 20}
|
||||||
entity, _, _ = await _create_entries(hass, client)
|
entity, _, _, _ = await _create_entries(hass, client)
|
||||||
|
|
||||||
assert hass.states.get(entity.entity_id).state == "off"
|
assert hass.states.get(entity.entity_id).state == "off"
|
||||||
|
|
||||||
|
@ -66,7 +61,7 @@ async def test_turn_on_off(hass: HomeAssistant):
|
||||||
state = hass.states.get(entity.entity_id)
|
state = hass.states.get(entity.entity_id)
|
||||||
|
|
||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
assert state.attributes["brightness"] == 51
|
assert state.attributes[ATTR_BRIGHTNESS] == 51
|
||||||
|
|
||||||
|
|
||||||
async def test_turn_on_with_brightness(hass: HomeAssistant):
|
async def test_turn_on_with_brightness(hass: HomeAssistant):
|
||||||
|
@ -74,7 +69,7 @@ async def test_turn_on_with_brightness(hass: HomeAssistant):
|
||||||
client = ClientMock()
|
client = ClientMock()
|
||||||
client.state = False
|
client.state = False
|
||||||
client.brightness = {"mode": "enabled", "value": 20}
|
client.brightness = {"mode": "enabled", "value": 20}
|
||||||
entity, _, _ = await _create_entries(hass, client)
|
entity, _, _, _ = await _create_entries(hass, client)
|
||||||
|
|
||||||
assert hass.states.get(entity.entity_id).state == "off"
|
assert hass.states.get(entity.entity_id).state == "off"
|
||||||
|
|
||||||
|
@ -88,7 +83,7 @@ async def test_turn_on_with_brightness(hass: HomeAssistant):
|
||||||
state = hass.states.get(entity.entity_id)
|
state = hass.states.get(entity.entity_id)
|
||||||
|
|
||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
assert state.attributes["brightness"] == 255
|
assert state.attributes[ATTR_BRIGHTNESS] == 255
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"light",
|
"light",
|
||||||
|
@ -100,7 +95,6 @@ async def test_turn_on_with_brightness(hass: HomeAssistant):
|
||||||
state = hass.states.get(entity.entity_id)
|
state = hass.states.get(entity.entity_id)
|
||||||
|
|
||||||
assert state.state == "off"
|
assert state.state == "off"
|
||||||
assert state.attributes["brightness"] == 0
|
|
||||||
|
|
||||||
|
|
||||||
async def test_turn_on_with_color_rgbw(hass: HomeAssistant):
|
async def test_turn_on_with_color_rgbw(hass: HomeAssistant):
|
||||||
|
@ -109,7 +103,7 @@ async def test_turn_on_with_color_rgbw(hass: HomeAssistant):
|
||||||
client.state = False
|
client.state = False
|
||||||
client.device_info["led_profile"] = "RGBW"
|
client.device_info["led_profile"] = "RGBW"
|
||||||
client.brightness = {"mode": "enabled", "value": 255}
|
client.brightness = {"mode": "enabled", "value": 255}
|
||||||
entity, _, _ = await _create_entries(hass, client)
|
entity, _, _, _ = await _create_entries(hass, client)
|
||||||
|
|
||||||
assert hass.states.get(entity.entity_id).state == "off"
|
assert hass.states.get(entity.entity_id).state == "off"
|
||||||
|
|
||||||
|
@ -132,7 +126,7 @@ async def test_turn_on_with_color_rgb(hass: HomeAssistant):
|
||||||
client.state = False
|
client.state = False
|
||||||
client.device_info["led_profile"] = "RGB"
|
client.device_info["led_profile"] = "RGB"
|
||||||
client.brightness = {"mode": "enabled", "value": 255}
|
client.brightness = {"mode": "enabled", "value": 255}
|
||||||
entity, _, _ = await _create_entries(hass, client)
|
entity, _, _, _ = await _create_entries(hass, client)
|
||||||
|
|
||||||
assert hass.states.get(entity.entity_id).state == "off"
|
assert hass.states.get(entity.entity_id).state == "off"
|
||||||
|
|
||||||
|
@ -151,7 +145,7 @@ async def test_turn_on_with_color_rgb(hass: HomeAssistant):
|
||||||
|
|
||||||
async def test_turn_off(hass: HomeAssistant):
|
async def test_turn_off(hass: HomeAssistant):
|
||||||
"""Test support of the light.turn_off service."""
|
"""Test support of the light.turn_off service."""
|
||||||
entity, _, _ = await _create_entries(hass)
|
entity, _, _, _ = await _create_entries(hass)
|
||||||
|
|
||||||
assert hass.states.get(entity.entity_id).state == "on"
|
assert hass.states.get(entity.entity_id).state == "on"
|
||||||
|
|
||||||
|
@ -163,7 +157,6 @@ async def test_turn_off(hass: HomeAssistant):
|
||||||
state = hass.states.get(entity.entity_id)
|
state = hass.states.get(entity.entity_id)
|
||||||
|
|
||||||
assert state.state == "off"
|
assert state.state == "off"
|
||||||
assert state.attributes["brightness"] == 0
|
|
||||||
|
|
||||||
|
|
||||||
async def test_update_name(hass: HomeAssistant):
|
async def test_update_name(hass: HomeAssistant):
|
||||||
|
@ -174,15 +167,7 @@ async def test_update_name(hass: HomeAssistant):
|
||||||
then the name of the entity is updated and it's also persisted,
|
then the name of the entity is updated and it's also persisted,
|
||||||
so it can be restored when starting HA while Twinkly is offline.
|
so it can be restored when starting HA while Twinkly is offline.
|
||||||
"""
|
"""
|
||||||
entity, _, client = await _create_entries(hass)
|
entity, _, client, config_entry = await _create_entries(hass)
|
||||||
|
|
||||||
updated_config_entry = None
|
|
||||||
|
|
||||||
async def on_update(ha, co):
|
|
||||||
nonlocal updated_config_entry
|
|
||||||
updated_config_entry = co
|
|
||||||
|
|
||||||
hass.config_entries.async_get_entry(entity.unique_id).add_update_listener(on_update)
|
|
||||||
|
|
||||||
client.change_name("new_device_name")
|
client.change_name("new_device_name")
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
|
@ -192,15 +177,14 @@ async def test_update_name(hass: HomeAssistant):
|
||||||
|
|
||||||
state = hass.states.get(entity.entity_id)
|
state = hass.states.get(entity.entity_id)
|
||||||
|
|
||||||
assert updated_config_entry is not None
|
assert config_entry.data[CONF_NAME] == "new_device_name"
|
||||||
assert updated_config_entry.data[CONF_ENTRY_NAME] == "new_device_name"
|
|
||||||
assert state.attributes["friendly_name"] == "new_device_name"
|
assert state.attributes["friendly_name"] == "new_device_name"
|
||||||
|
|
||||||
|
|
||||||
async def test_unload(hass: HomeAssistant):
|
async def test_unload(hass: HomeAssistant):
|
||||||
"""Validate that entities can be unloaded from the UI."""
|
"""Validate that entities can be unloaded from the UI."""
|
||||||
|
|
||||||
_, _, client = await _create_entries(hass)
|
_, _, client, _ = await _create_entries(hass)
|
||||||
entry_id = client.id
|
entry_id = client.id
|
||||||
|
|
||||||
assert await hass.config_entries.async_unload(entry_id)
|
assert await hass.config_entries.async_unload(entry_id)
|
||||||
|
@ -215,10 +199,10 @@ async def _create_entries(
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
domain=TWINKLY_DOMAIN,
|
domain=TWINKLY_DOMAIN,
|
||||||
data={
|
data={
|
||||||
CONF_ENTRY_HOST: client,
|
CONF_HOST: client,
|
||||||
CONF_ENTRY_ID: client.id,
|
CONF_ID: client.id,
|
||||||
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL,
|
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||||
CONF_ENTRY_MODEL: TEST_MODEL,
|
CONF_MODEL: TEST_MODEL,
|
||||||
},
|
},
|
||||||
entry_id=client.id,
|
entry_id=client.id,
|
||||||
)
|
)
|
||||||
|
@ -230,10 +214,10 @@ async def _create_entries(
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, client.id)
|
entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, client.id)
|
||||||
entity = entity_registry.async_get(entity_id)
|
entity_entry = entity_registry.async_get(entity_id)
|
||||||
device = device_registry.async_get_device({(TWINKLY_DOMAIN, client.id)})
|
device = device_registry.async_get_device({(TWINKLY_DOMAIN, client.id)})
|
||||||
|
|
||||||
assert entity is not None
|
assert entity_entry is not None
|
||||||
assert device is not None
|
assert device is not None
|
||||||
|
|
||||||
return entity, device, client
|
return entity_entry, device, client, config_entry
|
Loading…
Reference in New Issue