diff --git a/homeassistant/components/twinkly/__init__.py b/homeassistant/components/twinkly/__init__.py index ee52d5473de..bd9e8d85591 100644 --- a/homeassistant/components/twinkly/__init__.py +++ b/homeassistant/components/twinkly/__init__.py @@ -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) diff --git a/homeassistant/components/twinkly/config_flow.py b/homeassistant/components/twinkly/config_flow.py index a1bc8332caa..019f2d9ac53 100644 --- a/homeassistant/components/twinkly/config_flow.py +++ b/homeassistant/components/twinkly/config_flow.py @@ -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} diff --git a/homeassistant/components/twinkly/const.py b/homeassistant/components/twinkly/const.py index 2e7d4d2fd0e..b2754592454 100644 --- a/homeassistant/components/twinkly/const.py +++ b/homeassistant/components/twinkly/const.py @@ -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 diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 9736f81277d..65592a72b60 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -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 diff --git a/homeassistant/components/twinkly/manifest.json b/homeassistant/components/twinkly/manifest.json index 9cc9ce08254..bde8e799a55 100644 --- a/homeassistant/components/twinkly/manifest.json +++ b/homeassistant/components/twinkly/manifest.json @@ -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, diff --git a/requirements_all.txt b/requirements_all.txt index 1247ba4b56f..0e2035bda1f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index efea8a032f4..4dfc215f0b3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -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 diff --git a/tests/components/twinkly/__init__.py b/tests/components/twinkly/__init__.py index 96f9f450b8a..d5440ddb74a 100644 --- a/tests/components/twinkly/__init__.py +++ b/tests/components/twinkly/__init__.py @@ -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.""" diff --git a/tests/components/twinkly/test_config_flow.py b/tests/components/twinkly/test_config_flow.py index 5c4d3bfb098..02a2831d25d 100644 --- a/tests/components/twinkly/test_config_flow.py +++ b/tests/components/twinkly/test_config_flow.py @@ -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}, diff --git a/tests/components/twinkly/test_init.py b/tests/components/twinkly/test_init.py index 3f55d2ffdf0..415b897f488 100644 --- a/tests/components/twinkly/test_init.py +++ b/tests/components/twinkly/test_init.py @@ -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 diff --git a/tests/components/twinkly/test_twinkly.py b/tests/components/twinkly/test_twinkly.py index fcbbdb035c7..c1e12523865 100644 --- a/tests/components/twinkly/test_twinkly.py +++ b/tests/components/twinkly/test_twinkly.py @@ -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={