mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add support for setting RGB and RGBW values for Twinkly lights (#62337)
* Change library to ttls * Add rgbw support * Add client session to config flow * Fix config flow * Adjust tests 1 * Fix more tests * Fix last tests * Add new tests * Update test for coverage * Update test for coverage 2 * Update test for coverage 3 * Change brightness to attribute * Set RGBW mode only when available * Add RGB support
This commit is contained in:
parent
5f9a351889
commit
49a32c398c
@ -1,28 +1,41 @@
|
||||
"""The twinkly component."""
|
||||
|
||||
import twinkly_client
|
||||
import asyncio
|
||||
|
||||
from aiohttp import ClientError
|
||||
from ttls.client import Twinkly
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONF_ENTRY_HOST, CONF_ENTRY_ID, DOMAIN
|
||||
from .const import CONF_ENTRY_HOST, CONF_ENTRY_ID, DATA_CLIENT, DATA_DEVICE_INFO, DOMAIN
|
||||
|
||||
PLATFORMS = [Platform.LIGHT]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up entries from config flow."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
# 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.
|
||||
uuid = entry.data[CONF_ENTRY_ID]
|
||||
host = entry.data[CONF_ENTRY_HOST]
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[uuid] = twinkly_client.TwinklyClient(
|
||||
host, async_get_clientsession(hass)
|
||||
)
|
||||
hass.data[DOMAIN].setdefault(uuid, {})
|
||||
|
||||
client = Twinkly(host, async_get_clientsession(hass))
|
||||
|
||||
try:
|
||||
device_info = await client.get_details()
|
||||
except (asyncio.TimeoutError, ClientError) as exception:
|
||||
raise ConfigEntryNotReady from exception
|
||||
|
||||
hass.data[DOMAIN][uuid][DATA_CLIENT] = client
|
||||
hass.data[DOMAIN][uuid][DATA_DEVICE_INFO] = device_info
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
|
@ -6,12 +6,13 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
import twinkly_client
|
||||
from ttls.client import Twinkly
|
||||
from voluptuous import Required, Schema
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components import dhcp
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import (
|
||||
CONF_ENTRY_HOST,
|
||||
@ -45,7 +46,9 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
if host is not None:
|
||||
try:
|
||||
device_info = await twinkly_client.TwinklyClient(host).get_device_info()
|
||||
device_info = await Twinkly(
|
||||
host, async_get_clientsession(self.hass)
|
||||
).get_details()
|
||||
|
||||
await self.async_set_unique_id(device_info[DEV_ID])
|
||||
self._abort_if_unique_id_configured()
|
||||
@ -65,9 +68,9 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle dhcp discovery for twinkly."""
|
||||
self._async_abort_entries_match({CONF_ENTRY_HOST: discovery_info.ip})
|
||||
device_info = await twinkly_client.TwinklyClient(
|
||||
discovery_info.ip
|
||||
).get_device_info()
|
||||
device_info = await Twinkly(
|
||||
discovery_info.ip, async_get_clientsession(self.hass)
|
||||
).get_details()
|
||||
await self.async_set_unique_id(device_info[DEV_ID])
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_ENTRY_HOST: discovery_info.ip}
|
||||
|
@ -15,6 +15,13 @@ ATTR_HOST = "host"
|
||||
DEV_ID = "uuid"
|
||||
DEV_NAME = "device_name"
|
||||
DEV_MODEL = "product_code"
|
||||
DEV_LED_PROFILE = "led_profile"
|
||||
|
||||
DEV_PROFILE_RGB = "RGB"
|
||||
DEV_PROFILE_RGBW = "RGBW"
|
||||
|
||||
DATA_CLIENT = "client"
|
||||
DATA_DEVICE_INFO = "device_info"
|
||||
|
||||
HIDDEN_DEV_VALUES = (
|
||||
"code", # This is the internal status code of the API response
|
||||
|
@ -5,10 +5,15 @@ import asyncio
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientError
|
||||
from ttls.client import Twinkly
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
COLOR_MODE_BRIGHTNESS,
|
||||
COLOR_MODE_RGB,
|
||||
COLOR_MODE_RGBW,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -22,8 +27,13 @@ from .const import (
|
||||
CONF_ENTRY_ID,
|
||||
CONF_ENTRY_MODEL,
|
||||
CONF_ENTRY_NAME,
|
||||
DATA_CLIENT,
|
||||
DATA_DEVICE_INFO,
|
||||
DEV_LED_PROFILE,
|
||||
DEV_MODEL,
|
||||
DEV_NAME,
|
||||
DEV_PROFILE_RGB,
|
||||
DEV_PROFILE_RGBW,
|
||||
DOMAIN,
|
||||
HIDDEN_DEV_VALUES,
|
||||
)
|
||||
@ -38,7 +48,10 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Setups an entity from a config entry (UI config flow)."""
|
||||
|
||||
entity = TwinklyLight(config_entry, hass)
|
||||
client = hass.data[DOMAIN][config_entry.data[CONF_ENTRY_ID]][DATA_CLIENT]
|
||||
device_info = hass.data[DOMAIN][config_entry.data[CONF_ENTRY_ID]][DATA_DEVICE_INFO]
|
||||
|
||||
entity = TwinklyLight(config_entry, client, device_info)
|
||||
|
||||
async_add_entities([entity], update_before_add=True)
|
||||
|
||||
@ -49,34 +62,38 @@ class TwinklyLight(LightEntity):
|
||||
def __init__(
|
||||
self,
|
||||
conf: ConfigEntry,
|
||||
hass: HomeAssistant,
|
||||
client: Twinkly,
|
||||
device_info,
|
||||
) -> None:
|
||||
"""Initialize a TwinklyLight entity."""
|
||||
self._id = conf.data[CONF_ENTRY_ID]
|
||||
self._hass = hass
|
||||
self._conf = conf
|
||||
|
||||
if device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGBW:
|
||||
self._attr_supported_color_modes = {COLOR_MODE_RGBW}
|
||||
self._attr_color_mode = COLOR_MODE_RGBW
|
||||
self._attr_rgbw_color = (255, 255, 255, 0)
|
||||
elif device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGB:
|
||||
self._attr_supported_color_modes = {COLOR_MODE_RGB}
|
||||
self._attr_color_mode = COLOR_MODE_RGB
|
||||
self._attr_rgb_color = (255, 255, 255)
|
||||
else:
|
||||
self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS}
|
||||
self._attr_color_mode = COLOR_MODE_BRIGHTNESS
|
||||
|
||||
# Those are saved in the config entry in order to have meaningful values even
|
||||
# if the device is currently offline.
|
||||
# They are expected to be updated using the device_info.
|
||||
self.__name = conf.data[CONF_ENTRY_NAME]
|
||||
self.__model = conf.data[CONF_ENTRY_MODEL]
|
||||
|
||||
self._client = hass.data.get(DOMAIN, {}).get(self._id)
|
||||
if self._client is None:
|
||||
raise ValueError(f"Client for {self._id} has not been configured.")
|
||||
self._client = client
|
||||
|
||||
# Set default state before any update
|
||||
self._is_on = False
|
||||
self._brightness = 0
|
||||
self._is_available = False
|
||||
self._attributes = {ATTR_HOST: self._client.host}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Get the features supported by this entity."""
|
||||
return SUPPORT_BRIGHTNESS
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Get a boolean which indicates if this entity should be polled."""
|
||||
@ -126,11 +143,6 @@ class TwinklyLight(LightEntity):
|
||||
"""Return true if light is on."""
|
||||
return self._is_on
|
||||
|
||||
@property
|
||||
def brightness(self) -> int | None:
|
||||
"""Return the brightness of the light."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict:
|
||||
"""Return device specific state attributes."""
|
||||
@ -139,7 +151,7 @@ class TwinklyLight(LightEntity):
|
||||
|
||||
# Make sure to update any normalized property
|
||||
attributes[ATTR_HOST] = self._client.host
|
||||
attributes[ATTR_BRIGHTNESS] = self._brightness
|
||||
attributes[ATTR_BRIGHTNESS] = self._attr_brightness
|
||||
|
||||
return attributes
|
||||
|
||||
@ -151,31 +163,62 @@ class TwinklyLight(LightEntity):
|
||||
# If brightness is 0, the twinkly will only "disable" the brightness,
|
||||
# which means that it will be 100%.
|
||||
if brightness == 0:
|
||||
await self._client.set_is_on(False)
|
||||
await self._client.turn_off()
|
||||
return
|
||||
|
||||
await self._client.set_brightness(brightness)
|
||||
|
||||
await self._client.set_is_on(True)
|
||||
if ATTR_RGBW_COLOR in kwargs:
|
||||
if kwargs[ATTR_RGBW_COLOR] != self._attr_rgbw_color:
|
||||
self._attr_rgbw_color = kwargs[ATTR_RGBW_COLOR]
|
||||
|
||||
if isinstance(self._attr_rgbw_color, tuple):
|
||||
|
||||
await self._client.interview()
|
||||
# Reagarrange from rgbw to wrgb
|
||||
await self._client.set_static_colour(
|
||||
(
|
||||
self._attr_rgbw_color[3],
|
||||
self._attr_rgbw_color[0],
|
||||
self._attr_rgbw_color[1],
|
||||
self._attr_rgbw_color[2],
|
||||
)
|
||||
)
|
||||
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
if kwargs[ATTR_RGB_COLOR] != self._attr_rgb_color:
|
||||
self._attr_rgb_color = kwargs[ATTR_RGB_COLOR]
|
||||
|
||||
if isinstance(self._attr_rgb_color, tuple):
|
||||
|
||||
await self._client.interview()
|
||||
# Reagarrange from rgbw to wrgb
|
||||
await self._client.set_static_colour(self._attr_rgb_color)
|
||||
|
||||
if not self._is_on:
|
||||
await self._client.turn_on()
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn device off."""
|
||||
await self._client.set_is_on(False)
|
||||
await self._client.turn_off()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Asynchronously updates the device properties."""
|
||||
_LOGGER.info("Updating '%s'", self._client.host)
|
||||
|
||||
try:
|
||||
self._is_on = await self._client.get_is_on()
|
||||
self._is_on = await self._client.is_on()
|
||||
|
||||
self._brightness = (
|
||||
int(round((await self._client.get_brightness()) * 2.55))
|
||||
if self._is_on
|
||||
else 0
|
||||
brightness = await self._client.get_brightness()
|
||||
brightness_value = (
|
||||
int(brightness["value"]) if brightness["mode"] == "enabled" else 100
|
||||
)
|
||||
|
||||
device_info = await self._client.get_device_info()
|
||||
self._attr_brightness = (
|
||||
int(round(brightness_value * 2.55)) if self._is_on else 0
|
||||
)
|
||||
|
||||
device_info = await self._client.get_details()
|
||||
|
||||
if (
|
||||
DEV_NAME in device_info
|
||||
@ -191,7 +234,7 @@ class TwinklyLight(LightEntity):
|
||||
if self._conf is not None:
|
||||
# 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.
|
||||
self._hass.config_entries.async_update_entry(
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._conf,
|
||||
data={
|
||||
CONF_ENTRY_HOST: self._client.host, # this cannot change
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "twinkly",
|
||||
"name": "Twinkly",
|
||||
"documentation": "https://www.home-assistant.io/integrations/twinkly",
|
||||
"requirements": ["twinkly-client==0.0.2"],
|
||||
"requirements": ["ttls==1.4.2"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@dr1rrb"],
|
||||
"config_flow": true,
|
||||
|
@ -2375,6 +2375,9 @@ tp-connected==0.0.4
|
||||
# homeassistant.components.transmission
|
||||
transmissionrpc==0.11
|
||||
|
||||
# homeassistant.components.twinkly
|
||||
ttls==1.4.2
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-iot-py-sdk==0.6.6
|
||||
|
||||
@ -2384,9 +2387,6 @@ twentemilieu==0.5.0
|
||||
# homeassistant.components.twilio
|
||||
twilio==6.32.0
|
||||
|
||||
# homeassistant.components.twinkly
|
||||
twinkly-client==0.0.2
|
||||
|
||||
# homeassistant.components.rainforest_eagle
|
||||
uEagle==0.0.2
|
||||
|
||||
|
@ -1433,6 +1433,9 @@ total_connect_client==2021.12
|
||||
# homeassistant.components.transmission
|
||||
transmissionrpc==0.11
|
||||
|
||||
# homeassistant.components.twinkly
|
||||
ttls==1.4.2
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-iot-py-sdk==0.6.6
|
||||
|
||||
@ -1442,9 +1445,6 @@ twentemilieu==0.5.0
|
||||
# homeassistant.components.twilio
|
||||
twilio==6.32.0
|
||||
|
||||
# homeassistant.components.twinkly
|
||||
twinkly-client==0.0.2
|
||||
|
||||
# homeassistant.components.rainforest_eagle
|
||||
uEagle==0.0.2
|
||||
|
||||
|
@ -14,13 +14,14 @@ TEST_MODEL = "twinkly_test_device_model"
|
||||
|
||||
|
||||
class ClientMock:
|
||||
"""A mock of the twinkly_client.TwinklyClient."""
|
||||
"""A mock of the ttls.client.Twinkly."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Create a mocked client."""
|
||||
self.is_offline = False
|
||||
self.is_on = True
|
||||
self.brightness = 10
|
||||
self.state = True
|
||||
self.brightness = {"mode": "enabled", "value": 10}
|
||||
self.color = None
|
||||
|
||||
self.id = str(uuid4())
|
||||
self.device_info = {
|
||||
@ -34,23 +35,29 @@ class ClientMock:
|
||||
"""Get the mocked host."""
|
||||
return TEST_HOST
|
||||
|
||||
async def get_device_info(self):
|
||||
async def get_details(self):
|
||||
"""Get the mocked device info."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError()
|
||||
return self.device_info
|
||||
|
||||
async def get_is_on(self) -> bool:
|
||||
async def is_on(self) -> bool:
|
||||
"""Get the mocked on/off state."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError()
|
||||
return self.is_on
|
||||
return self.state
|
||||
|
||||
async def set_is_on(self, is_on: bool) -> None:
|
||||
"""Set the mocked on/off state."""
|
||||
async def turn_on(self) -> None:
|
||||
"""Set the mocked on state."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError()
|
||||
self.is_on = is_on
|
||||
self.state = True
|
||||
|
||||
async def turn_off(self) -> None:
|
||||
"""Set the mocked off state."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError()
|
||||
self.state = False
|
||||
|
||||
async def get_brightness(self) -> int:
|
||||
"""Get the mocked brightness."""
|
||||
@ -62,8 +69,15 @@ class ClientMock:
|
||||
"""Set the mocked brightness."""
|
||||
if self.is_offline:
|
||||
raise ClientConnectionError()
|
||||
self.brightness = brightness
|
||||
self.brightness = {"mode": "enabled", "value": brightness}
|
||||
|
||||
def change_name(self, new_name: str) -> None:
|
||||
"""Change the name of this virtual device."""
|
||||
self.device_info[DEV_NAME] = new_name
|
||||
|
||||
async def set_static_colour(self, colour) -> None:
|
||||
"""Set static color."""
|
||||
self.color = colour
|
||||
|
||||
async def interview(self) -> None:
|
||||
"""Interview."""
|
||||
|
@ -20,7 +20,9 @@ async def test_invalid_host(hass):
|
||||
"""Test the failure when invalid host provided."""
|
||||
client = ClientMock()
|
||||
client.is_offline = True
|
||||
with patch("twinkly_client.TwinklyClient", return_value=client):
|
||||
with patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
@ -40,7 +42,9 @@ async def test_invalid_host(hass):
|
||||
async def test_success_flow(hass):
|
||||
"""Test that an entity is created when the flow completes."""
|
||||
client = ClientMock()
|
||||
with patch("twinkly_client.TwinklyClient", return_value=client):
|
||||
with patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
), patch("homeassistant.components.twinkly.async_setup_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
@ -67,7 +71,9 @@ async def test_success_flow(hass):
|
||||
async def test_dhcp_can_confirm(hass):
|
||||
"""Test DHCP discovery flow can confirm right away."""
|
||||
client = ClientMock()
|
||||
with patch("twinkly_client.TwinklyClient", return_value=client):
|
||||
with patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
@ -86,7 +92,9 @@ async def test_dhcp_can_confirm(hass):
|
||||
async def test_dhcp_success(hass):
|
||||
"""Test DHCP discovery flow success."""
|
||||
client = ClientMock()
|
||||
with patch("twinkly_client.TwinklyClient", return_value=client):
|
||||
with patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
), patch("homeassistant.components.twinkly.async_setup_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
@ -129,7 +137,9 @@ async def test_dhcp_already_exists(hass):
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch("twinkly_client.TwinklyClient", return_value=client):
|
||||
with patch(
|
||||
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
TWINKLY_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_DHCP},
|
||||
|
@ -11,14 +11,21 @@ from homeassistant.components.twinkly.const import (
|
||||
CONF_ENTRY_NAME,
|
||||
DOMAIN as TWINKLY_DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.twinkly import TEST_HOST, TEST_MODEL, TEST_NAME_ORIGINAL
|
||||
from tests.components.twinkly import (
|
||||
TEST_HOST,
|
||||
TEST_MODEL,
|
||||
TEST_NAME_ORIGINAL,
|
||||
ClientMock,
|
||||
)
|
||||
|
||||
|
||||
async def test_setup_entry(hass: HomeAssistant):
|
||||
"""Validate that setup entry also configure the client."""
|
||||
client = ClientMock()
|
||||
|
||||
id = str(uuid4())
|
||||
config_entry = MockConfigEntry(
|
||||
@ -38,7 +45,7 @@ async def test_setup_entry(hass: HomeAssistant):
|
||||
with patch(
|
||||
"homeassistant.config_entries.ConfigEntries.async_forward_entry_setup",
|
||||
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
|
||||
@ -65,3 +72,26 @@ async def test_unload_entry(hass: HomeAssistant):
|
||||
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):
|
||||
"""Validate that config entry is retried."""
|
||||
client = ClientMock()
|
||||
client.is_offline = True
|
||||
|
||||
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,
|
||||
},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
@ -10,7 +10,6 @@ from homeassistant.components.twinkly.const import (
|
||||
CONF_ENTRY_NAME,
|
||||
DOMAIN as TWINKLY_DOMAIN,
|
||||
)
|
||||
from homeassistant.components.twinkly.light import TwinklyLight
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
@ -19,31 +18,12 @@ from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.twinkly import (
|
||||
TEST_HOST,
|
||||
TEST_ID,
|
||||
TEST_MODEL,
|
||||
TEST_NAME_ORIGINAL,
|
||||
ClientMock,
|
||||
)
|
||||
|
||||
|
||||
async def test_missing_client(hass: HomeAssistant):
|
||||
"""Validate that if client has not been setup, it fails immediately in setup."""
|
||||
try:
|
||||
config_entry = MockConfigEntry(
|
||||
data={
|
||||
CONF_ENTRY_HOST: TEST_HOST,
|
||||
CONF_ENTRY_ID: TEST_ID,
|
||||
CONF_ENTRY_NAME: TEST_NAME_ORIGINAL,
|
||||
CONF_ENTRY_MODEL: TEST_MODEL,
|
||||
}
|
||||
)
|
||||
TwinklyLight(config_entry, hass)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
assert False
|
||||
|
||||
|
||||
async def test_initial_state(hass: HomeAssistant):
|
||||
"""Validate that entity and device states are updated on startup."""
|
||||
entity, device, _ = await _create_entries(hass)
|
||||
@ -69,32 +49,11 @@ async def test_initial_state(hass: HomeAssistant):
|
||||
assert device.manufacturer == "LEDWORKS"
|
||||
|
||||
|
||||
async def test_initial_state_offline(hass: HomeAssistant):
|
||||
"""Validate that entity and device are restored from config is offline on startup."""
|
||||
client = ClientMock()
|
||||
client.is_offline = True
|
||||
entity, device, _ = await _create_entries(hass, client)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.name == TEST_NAME_ORIGINAL
|
||||
assert state.state == "unavailable"
|
||||
assert state.attributes["friendly_name"] == TEST_NAME_ORIGINAL
|
||||
assert state.attributes["icon"] == "mdi:string-lights"
|
||||
|
||||
assert entity.original_name == TEST_NAME_ORIGINAL
|
||||
assert entity.original_icon == "mdi:string-lights"
|
||||
|
||||
assert device.name == TEST_NAME_ORIGINAL
|
||||
assert device.model == TEST_MODEL
|
||||
assert device.manufacturer == "LEDWORKS"
|
||||
|
||||
|
||||
async def test_turn_on(hass: HomeAssistant):
|
||||
async def test_turn_on_off(hass: HomeAssistant):
|
||||
"""Test support of the light.turn_on service."""
|
||||
client = ClientMock()
|
||||
client.is_on = False
|
||||
client.brightness = 20
|
||||
client.state = False
|
||||
client.brightness = {"mode": "enabled", "value": 20}
|
||||
entity, _, _ = await _create_entries(hass, client)
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
@ -113,8 +72,8 @@ async def test_turn_on(hass: HomeAssistant):
|
||||
async def test_turn_on_with_brightness(hass: HomeAssistant):
|
||||
"""Test support of the light.turn_on service with a brightness parameter."""
|
||||
client = ClientMock()
|
||||
client.is_on = False
|
||||
client.brightness = 20
|
||||
client.state = False
|
||||
client.brightness = {"mode": "enabled", "value": 20}
|
||||
entity, _, _ = await _create_entries(hass, client)
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
@ -131,6 +90,64 @@ async def test_turn_on_with_brightness(hass: HomeAssistant):
|
||||
assert state.state == "on"
|
||||
assert state.attributes["brightness"] == 255
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "brightness": 1},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "off"
|
||||
assert state.attributes["brightness"] == 0
|
||||
|
||||
|
||||
async def test_turn_on_with_color_rgbw(hass: HomeAssistant):
|
||||
"""Test support of the light.turn_on service with a brightness parameter."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGBW"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
entity, _, _ = await _create_entries(hass, client)
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "rgbw_color": (128, 64, 32, 0)},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.color == (0, 128, 64, 32)
|
||||
|
||||
|
||||
async def test_turn_on_with_color_rgb(hass: HomeAssistant):
|
||||
"""Test support of the light.turn_on service with a brightness parameter."""
|
||||
client = ClientMock()
|
||||
client.state = False
|
||||
client.device_info["led_profile"] = "RGB"
|
||||
client.brightness = {"mode": "enabled", "value": 255}
|
||||
entity, _, _ = await _create_entries(hass, client)
|
||||
|
||||
assert hass.states.get(entity.entity_id).state == "off"
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
service_data={"entity_id": entity.entity_id, "rgb_color": (128, 64, 32)},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
|
||||
assert state.state == "on"
|
||||
assert client.color == (128, 64, 32)
|
||||
|
||||
|
||||
async def test_turn_off(hass: HomeAssistant):
|
||||
"""Test support of the light.turn_off service."""
|
||||
@ -194,10 +211,7 @@ async def _create_entries(
|
||||
) -> tuple[RegistryEntry, DeviceEntry, ClientMock]:
|
||||
client = ClientMock() if client is None else client
|
||||
|
||||
def get_client_mock(client, _):
|
||||
return client
|
||||
|
||||
with patch("twinkly_client.TwinklyClient", side_effect=get_client_mock):
|
||||
with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
||||
config_entry = MockConfigEntry(
|
||||
domain=TWINKLY_DOMAIN,
|
||||
data={
|
||||
|
Loading…
x
Reference in New Issue
Block a user