From 3efbd6a1c90fec5ded04394ad3b16c7af6c29c84 Mon Sep 17 00:00:00 2001 From: icemanch Date: Sun, 10 Oct 2021 15:18:15 -0400 Subject: [PATCH] Flux led color support (#57353) Co-authored-by: J. Nick Koston --- homeassistant/components/flux_led/__init__.py | 2 +- .../components/flux_led/config_flow.py | 18 - homeassistant/components/flux_led/const.py | 1 - homeassistant/components/flux_led/light.py | 303 +++++++------ .../components/flux_led/manifest.json | 3 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/flux_led/__init__.py | 19 +- tests/components/flux_led/test_config_flow.py | 2 - tests/components/flux_led/test_light.py | 405 ++++++++++++++---- 10 files changed, 511 insertions(+), 246 deletions(-) diff --git a/homeassistant/components/flux_led/__init__.py b/homeassistant/components/flux_led/__init__.py index fa3ad23a41c..99d75e884b5 100644 --- a/homeassistant/components/flux_led/__init__.py +++ b/homeassistant/components/flux_led/__init__.py @@ -28,7 +28,7 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS: Final = ["light"] DISCOVERY_INTERVAL: Final = timedelta(minutes=15) -REQUEST_REFRESH_DELAY: Final = 0.65 +REQUEST_REFRESH_DELAY: Final = 1.5 async def async_wifi_bulb_for_host(hass: HomeAssistant, host: str) -> WifiLedBulb: diff --git a/homeassistant/components/flux_led/config_flow.py b/homeassistant/components/flux_led/config_flow.py index 206c8f91433..02ddd4a1530 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -13,7 +13,6 @@ from homeassistant.const import CONF_HOST, CONF_MAC, CONF_MODE, CONF_NAME, CONF_ from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import device_registry as dr -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import DiscoveryInfoType from . import async_discover_devices, async_wifi_bulb_for_host @@ -28,10 +27,6 @@ from .const import ( FLUX_LED_EXCEPTIONS, FLUX_MAC, FLUX_MODEL, - MODE_AUTO, - MODE_RGB, - MODE_RGBW, - MODE_WHITE, TRANSITION_GRADUAL, TRANSITION_JUMP, TRANSITION_STROBE, @@ -233,19 +228,6 @@ class OptionsFlow(config_entries.OptionsFlow): options = self._config_entry.options options_schema = vol.Schema( { - vol.Required( - CONF_MODE, default=options.get(CONF_MODE, MODE_AUTO) - ): vol.All( - cv.string, - vol.In( - [ - MODE_AUTO, - MODE_RGBW, - MODE_RGB, - MODE_WHITE, - ] - ), - ), vol.Optional( CONF_CUSTOM_EFFECT_COLORS, default=options.get(CONF_CUSTOM_EFFECT_COLORS, ""), diff --git a/homeassistant/components/flux_led/const.py b/homeassistant/components/flux_led/const.py index 6b2c4a8dace..4c8a924df98 100644 --- a/homeassistant/components/flux_led/const.py +++ b/homeassistant/components/flux_led/const.py @@ -28,7 +28,6 @@ MODE_AUTO: Final = "auto" MODE_RGB: Final = "rgb" MODE_RGBW: Final = "rgbw" - # This mode enables white value to be controlled by brightness. # RGB value is ignored when this mode is specified. MODE_WHITE: Final = "w" diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 8c14a1c22b6..a9b8bd32e32 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -8,6 +8,21 @@ import random from typing import Any, Final, cast from flux_led import WifiLedBulb +from flux_led.const import ( + COLOR_MODE_CCT as FLUX_COLOR_MODE_CCT, + COLOR_MODE_DIM as FLUX_COLOR_MODE_DIM, + COLOR_MODE_RGB as FLUX_COLOR_MODE_RGB, + COLOR_MODE_RGBW as FLUX_COLOR_MODE_RGBW, + COLOR_MODE_RGBWW as FLUX_COLOR_MODE_RGBWW, +) +from flux_led.device import MAX_TEMP, MIN_TEMP +from flux_led.utils import ( + color_temp_to_white_levels, + rgbcw_brightness, + rgbcw_to_rgbwc, + rgbw_brightness, + rgbww_brightness, +) import voluptuous as vol from homeassistant import config_entries @@ -15,16 +30,22 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, - ATTR_HS_COLOR, - ATTR_WHITE_VALUE, + ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, + ATTR_WHITE, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_ONOFF, + COLOR_MODE_RGB, + COLOR_MODE_RGBW, + COLOR_MODE_RGBWW, + COLOR_MODE_WHITE, EFFECT_COLORLOOP, EFFECT_RANDOM, PLATFORM_SCHEMA, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, - SUPPORT_WHITE_VALUE, + SUPPORT_TRANSITION, LightEntity, ) from homeassistant.const import ( @@ -46,7 +67,10 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.update_coordinator import CoordinatorEntity -import homeassistant.util.color as color_util +from homeassistant.util.color import ( + color_temperature_kelvin_to_mired, + color_temperature_mired_to_kelvin, +) from . import FluxLedUpdateCoordinator from .const import ( @@ -74,7 +98,16 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -SUPPORT_FLUX_LED: Final = SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_COLOR +SUPPORT_FLUX_LED: Final = SUPPORT_EFFECT | SUPPORT_TRANSITION + + +FLUX_COLOR_MODE_TO_HASS: Final = { + FLUX_COLOR_MODE_RGB: COLOR_MODE_RGB, + FLUX_COLOR_MODE_RGBW: COLOR_MODE_RGBW, + FLUX_COLOR_MODE_RGBWW: COLOR_MODE_RGBWW, + FLUX_COLOR_MODE_CCT: COLOR_MODE_COLOR_TEMP, + FLUX_COLOR_MODE_DIM: COLOR_MODE_WHITE, +} # Constant color temp values for 2 flux_led special modes @@ -128,8 +161,6 @@ EFFECT_MAP: Final = { EFFECT_ID_NAME: Final = {v: k for k, v in EFFECT_MAP.items()} EFFECT_CUSTOM_CODE: Final = 0x60 -WHITE_MODES: Final = {MODE_RGBW} - FLUX_EFFECT_LIST: Final = sorted(EFFECT_MAP) + [EFFECT_RANDOM] SERVICE_CUSTOM_EFFECT: Final = "set_custom_effect" @@ -243,7 +274,6 @@ async def async_setup_entry( coordinator, entry.unique_id, entry.data[CONF_NAME], - options.get(CONF_MODE) or MODE_AUTO, list(custom_effect_colors), options.get(CONF_CUSTOM_EFFECT_SPEED_PCT, DEFAULT_EFFECT_SPEED), options.get(CONF_CUSTOM_EFFECT_TRANSITION, TRANSITION_GRADUAL), @@ -262,7 +292,6 @@ class FluxLight(CoordinatorEntity, LightEntity): coordinator: FluxLedUpdateCoordinator, unique_id: str | None, name: str, - mode: str, custom_effect_colors: list[tuple[int, int, int]], custom_effect_speed_pct: int, custom_effect_transition: str, @@ -272,18 +301,29 @@ class FluxLight(CoordinatorEntity, LightEntity): self._bulb: WifiLedBulb = coordinator.device self._attr_name = name self._attr_unique_id = unique_id - self._ip_address = coordinator.host - self._mode = mode + self._attr_supported_features = SUPPORT_FLUX_LED + self._attr_min_mireds = ( + color_temperature_kelvin_to_mired(MAX_TEMP) + 1 + ) # for rounding + self._attr_max_mireds = color_temperature_kelvin_to_mired(MIN_TEMP) + self._attr_supported_color_modes = { + FLUX_COLOR_MODE_TO_HASS.get(mode, COLOR_MODE_ONOFF) + for mode in self._bulb.color_modes + } + self._attr_effect_list = FLUX_EFFECT_LIST + if custom_effect_colors: + self._attr_effect_list = [*FLUX_EFFECT_LIST, EFFECT_CUSTOM] self._custom_effect_colors = custom_effect_colors self._custom_effect_speed_pct = custom_effect_speed_pct self._custom_effect_transition = custom_effect_transition - old_protocol = self._bulb.protocol == "LEDENET_ORIGINAL" if self.unique_id: + old_protocol = self._bulb.protocol == "LEDENET_ORIGINAL" + raw_state = self._bulb.raw_state self._attr_device_info = { "connections": {(dr.CONNECTION_NETWORK_MAC, self.unique_id)}, - ATTR_MODEL: f"0x{self._bulb.raw_state[1]:02X}", - ATTR_SW_VERSION: "1" if old_protocol else str(self._bulb.raw_state[10]), + ATTR_MODEL: f"0x{self._bulb.model_num:02X}", ATTR_NAME: self.name, + ATTR_SW_VERSION: "1" if old_protocol else str(raw_state.version_number), ATTR_MANUFACTURER: "FluxLED/Magic Home", } @@ -295,49 +335,53 @@ class FluxLight(CoordinatorEntity, LightEntity): @property def brightness(self) -> int: """Return the brightness of this light between 0..255.""" - if self._mode == MODE_WHITE: - return self.white_value return cast(int, self._bulb.brightness) @property - def hs_color(self) -> tuple[float, float] | None: - """Return the color property.""" - return color_util.color_RGB_to_hs(*self._bulb.getRgb()) + def color_temp(self) -> int: + """Return the kelvin value of this light in mired.""" + return color_temperature_kelvin_to_mired(self._bulb.getWhiteTemperature()[0]) @property - def supported_features(self) -> int: - """Flag supported features.""" - if self._mode == MODE_WHITE: - return SUPPORT_BRIGHTNESS - if self._mode in WHITE_MODES: - return SUPPORT_FLUX_LED | SUPPORT_WHITE_VALUE | SUPPORT_COLOR_TEMP - return SUPPORT_FLUX_LED + def rgb_color(self) -> tuple[int, int, int]: + """Return the rgb color value.""" + rgb: tuple[int, int, int] = self._bulb.rgb + return rgb @property - def white_value(self) -> int: - """Return the white value of this light between 0..255.""" - return cast(int, self._bulb.getRgbw()[3]) + def rgbw_color(self) -> tuple[int, int, int, int]: + """Return the rgbw color value.""" + rgbw: tuple[int, int, int, int] = self._bulb.rgbw + return rgbw @property - def effect_list(self) -> list[str]: - """Return the list of supported effects.""" - if self._custom_effect_colors: - return FLUX_EFFECT_LIST + [EFFECT_CUSTOM] - return FLUX_EFFECT_LIST + def rgbww_color(self) -> tuple[int, int, int, int, int]: + """Return the rgbww aka rgbcw color value.""" + rgbcw: tuple[int, int, int, int, int] = self._bulb.rgbcw + return rgbcw + + @property + def rgbwc_color(self) -> tuple[int, int, int, int, int]: + """Return the rgbwc color value.""" + rgbwc: tuple[int, int, int, int, int] = self._bulb.rgbww + return rgbwc + + @property + def color_mode(self) -> str: + """Return the color mode of the light.""" + return FLUX_COLOR_MODE_TO_HASS.get(self._bulb.color_mode, COLOR_MODE_ONOFF) @property def effect(self) -> str | None: """Return the current effect.""" - if (current_mode := self._bulb.raw_state[3]) == EFFECT_CUSTOM_CODE: + if (current_mode := self._bulb.raw_state.preset_pattern) == EFFECT_CUSTOM_CODE: return EFFECT_CUSTOM return EFFECT_ID_NAME.get(current_mode) @property def extra_state_attributes(self) -> dict[str, str]: """Return the attributes.""" - return { - "ip_address": self._ip_address, - } + return {"ip_address": self._bulb.ipaddr} async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified or all lights on.""" @@ -349,77 +393,101 @@ class FluxLight(CoordinatorEntity, LightEntity): """Turn the specified or all lights on.""" if not self.is_on: self._bulb.turnOn() + if not kwargs: + return - if hs_color := kwargs.get(ATTR_HS_COLOR): - rgb: tuple[int, int, int] | None = color_util.color_hs_to_RGB(*hs_color) - else: - rgb = None - - brightness = kwargs.get(ATTR_BRIGHTNESS) - # handle special modes - if (color_temp := kwargs.get(ATTR_COLOR_TEMP)) is not None: - if brightness is None: - brightness = self.brightness - if color_temp > COLOR_TEMP_WARM_VS_COLD_WHITE_CUT_OFF: - self._bulb.setRgbw(w=brightness) - else: - self._bulb.setRgbw(w2=brightness) - return - - white = kwargs.get(ATTR_WHITE_VALUE) - effect = kwargs.get(ATTR_EFFECT) - # Show warning if effect set with rgb, brightness, or white level - if effect and (brightness or white or rgb): - _LOGGER.warning( - "RGB, brightness and white level are ignored when" - " an effect is specified for a flux bulb" - ) - - # Random color effect - if effect == EFFECT_RANDOM: - self._bulb.setRgb( - random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) - ) - return - - # Custom effect - if effect == EFFECT_CUSTOM: - if self._custom_effect_colors: - self._bulb.setCustomPattern( - self._custom_effect_colors, - self._custom_effect_speed_pct, - self._custom_effect_transition, - ) - return - - # Effect selection - if effect in EFFECT_MAP: - self._bulb.setPresetPattern(EFFECT_MAP[effect], 50) - return - - # Preserve current brightness on color/white level change - if brightness is None: + if (brightness := kwargs.get(ATTR_BRIGHTNESS)) is None: brightness = self.brightness - # handle W only mode (use brightness instead of white value) - if self._mode == MODE_WHITE: - self._bulb.setRgbw(0, 0, 0, w=brightness) + # Handle switch to CCT Color Mode + if ATTR_COLOR_TEMP in kwargs: + color_temp_mired = kwargs[ATTR_COLOR_TEMP] + color_temp_kelvin = color_temperature_mired_to_kelvin(color_temp_mired) + if self.color_mode != COLOR_MODE_RGBWW: + self._bulb.setWhiteTemperature(color_temp_kelvin, brightness) + return + + # When switching to color temp from RGBWW mode, + # we do not want the overall brightness, we only + # want the brightness of the white channels + brightness = kwargs.get( + ATTR_BRIGHTNESS, self._bulb.getWhiteTemperature()[1] + ) + cold, warm = color_temp_to_white_levels(color_temp_kelvin, brightness) + self._bulb.set_levels(r=0, b=0, g=0, w=warm, w2=cold) return - - if white is None and self._mode in WHITE_MODES: - white = self.white_value - - # Preserve color on brightness/white level change - if rgb is None: - rgb = self._bulb.getRgb() - - # handle RGBW mode - if self._mode == MODE_RGBW: - self._bulb.setRgbw(*tuple(rgb), w=white, brightness=brightness) + # Handle switch to HS Color Mode + if ATTR_RGB_COLOR in kwargs: + self._bulb.set_levels(*kwargs[ATTR_RGB_COLOR], brightness=brightness) return - - # handle RGB mode - self._bulb.setRgb(*tuple(rgb), brightness=brightness) + # Handle switch to RGBW Color Mode + if ATTR_RGBW_COLOR in kwargs: + if ATTR_BRIGHTNESS in kwargs: + rgbw = rgbw_brightness(kwargs[ATTR_RGBW_COLOR], brightness) + else: + rgbw = kwargs[ATTR_RGBW_COLOR] + self._bulb.set_levels(*rgbw) + return + # Handle switch to RGBWW Color Mode + if ATTR_RGBWW_COLOR in kwargs: + if ATTR_BRIGHTNESS in kwargs: + rgbcw = rgbcw_brightness(kwargs[ATTR_RGBWW_COLOR], brightness) + else: + rgbcw = kwargs[ATTR_RGBWW_COLOR] + self._bulb.set_levels(*rgbcw_to_rgbwc(rgbcw)) + return + # Handle switch to White Color Mode + if ATTR_WHITE in kwargs: + self._bulb.set_levels(w=kwargs[ATTR_WHITE]) + return + if ATTR_EFFECT in kwargs: + effect = kwargs[ATTR_EFFECT] + # Random color effect + if effect == EFFECT_RANDOM: + self._bulb.set_levels( + random.randint(0, 255), + random.randint(0, 255), + random.randint(0, 255), + ) + return + # Custom effect + if effect == EFFECT_CUSTOM: + if self._custom_effect_colors: + self._bulb.setCustomPattern( + self._custom_effect_colors, + self._custom_effect_speed_pct, + self._custom_effect_transition, + ) + return + # Effect selection + if effect in EFFECT_MAP: + self._bulb.setPresetPattern(EFFECT_MAP[effect], DEFAULT_EFFECT_SPEED) + return + raise ValueError(f"Unknown effect {effect}") + # Handle brightness adjustment in CCT Color Mode + if self.color_mode == COLOR_MODE_COLOR_TEMP: + self._bulb.setWhiteTemperature( + self._bulb.getWhiteTemperature()[0], brightness + ) + return + # Handle brightness adjustment in RGB Color Mode + if self.color_mode == COLOR_MODE_RGB: + self._bulb.set_levels(*self.rgb_color, brightness=brightness) + return + # Handle brightness adjustment in RGBW Color Mode + if self.color_mode == COLOR_MODE_RGBW: + self._bulb.set_levels(*rgbw_brightness(self.rgbw_color, brightness)) + return + # Handle brightness adjustment in RGBWW Color Mode + if self.color_mode == COLOR_MODE_RGBWW: + rgbwc = self.rgbwc_color + self._bulb.set_levels(*rgbww_brightness(rgbwc, brightness)) + return + # Handle White Color Mode and Brightness Only Color Mode + if self.color_mode in (COLOR_MODE_WHITE, COLOR_MODE_BRIGHTNESS): + self._bulb.set_levels(w=brightness) + return + raise ValueError(f"Unsupported color mode {self.color_mode}") def set_custom_effect( self, colors: list[tuple[int, int, int]], speed_pct: int, transition: str @@ -436,24 +504,3 @@ class FluxLight(CoordinatorEntity, LightEntity): await self.hass.async_add_executor_job(self._bulb.turnOff) self.async_write_ha_state() await self.coordinator.async_request_refresh() - - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - await super().async_added_to_hass() - if self._mode and self._mode != MODE_AUTO: - return - - if self._bulb.mode == "ww": - self._mode = MODE_WHITE - elif self._bulb.rgbwcapable: - self._mode = MODE_RGBW - else: - self._mode = MODE_RGB - _LOGGER.debug( - "Detected mode for %s (%s) with raw_state=%s rgbwcapable=%s is %s", - self.name, - self.unique_id, - self._bulb.raw_state, - self._bulb.rgbwcapable, - self._mode, - ) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index c6f06cb20ab..28d4ecd772c 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -3,7 +3,7 @@ "name": "Flux LED/MagicHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.22"], + "requirements": ["flux_led==0.24.3"], "codeowners": ["@icemanch"], "iot_class": "local_polling", "dhcp": [ @@ -30,4 +30,3 @@ ] } - diff --git a/requirements_all.txt b/requirements_all.txt index 481b8d5e220..ec5374661c6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -649,7 +649,7 @@ fjaraskupan==1.0.1 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.22 +flux_led==0.24.3 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a40976d974d..25a22280f71 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -378,7 +378,7 @@ fjaraskupan==1.0.1 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.22 +flux_led==0.24.3 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/tests/components/flux_led/__init__.py b/tests/components/flux_led/__init__.py index 5ca6244655a..1eccf9bfcd6 100644 --- a/tests/components/flux_led/__init__.py +++ b/tests/components/flux_led/__init__.py @@ -5,6 +5,11 @@ import socket from unittest.mock import MagicMock, patch from flux_led import WifiLedBulb +from flux_led.const import ( + COLOR_MODE_CCT as FLUX_COLOR_MODE_CCT, + COLOR_MODE_RGB as FLUX_COLOR_MODE_RGB, +) +from flux_led.protocol import LEDENETRawState from homeassistant.components.dhcp import ( HOSTNAME as DHCP_HOSTNAME, @@ -34,9 +39,21 @@ def _mocked_bulb() -> WifiLedBulb: bulb = MagicMock(auto_spec=WifiLedBulb) bulb.getRgb = MagicMock(return_value=[255, 0, 0]) bulb.getRgbw = MagicMock(return_value=[255, 0, 0, 50]) + bulb.getRgbww = MagicMock(return_value=[255, 0, 0, 50, 0]) + bulb.getRgbcw = MagicMock(return_value=[255, 0, 0, 0, 50]) + bulb.rgb = (255, 0, 0) + bulb.rgbw = (255, 0, 0, 50) + bulb.rgbww = (255, 0, 0, 50, 0) + bulb.rgbcw = (255, 0, 0, 0, 50) + bulb.getWhiteTemperature = MagicMock(return_value=(2700, 128)) bulb.brightness = 128 + bulb.model_num = 0x35 bulb.rgbwcapable = True - bulb.raw_state = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + bulb.color_modes = {FLUX_COLOR_MODE_RGB, FLUX_COLOR_MODE_CCT} + bulb.color_mode = FLUX_COLOR_MODE_RGB + bulb.raw_state = LEDENETRawState( + 0, 0x35, 0, 0x61, 0x5, 50, 255, 0, 0, 50, 8, 0, 0, 0 + ) return bulb diff --git a/tests/components/flux_led/test_config_flow.py b/tests/components/flux_led/test_config_flow.py index 029ba0f972b..13e427c3331 100644 --- a/tests/components/flux_led/test_config_flow.py +++ b/tests/components/flux_led/test_config_flow.py @@ -11,7 +11,6 @@ from homeassistant.components.flux_led.const import ( CONF_CUSTOM_EFFECT_SPEED_PCT, CONF_CUSTOM_EFFECT_TRANSITION, DOMAIN, - MODE_AUTO, MODE_RGB, TRANSITION_JUMP, TRANSITION_STROBE, @@ -436,7 +435,6 @@ async def test_options(hass: HomeAssistant): assert result["step_id"] == "init" user_input = { - CONF_MODE: MODE_AUTO, CONF_CUSTOM_EFFECT_COLORS: "[0,0,255], [255,0,0]", CONF_CUSTOM_EFFECT_SPEED_PCT: 50, CONF_CUSTOM_EFFECT_TRANSITION: TRANSITION_JUMP, diff --git a/tests/components/flux_led/test_light.py b/tests/components/flux_led/test_light.py index 4ddf9a7d04d..56f53f97bf4 100644 --- a/tests/components/flux_led/test_light.py +++ b/tests/components/flux_led/test_light.py @@ -1,6 +1,15 @@ """Tests for light platform.""" from datetime import timedelta +from unittest.mock import Mock +from flux_led.const import ( + COLOR_MODE_ADDRESSABLE as FLUX_COLOR_MODE_ADDRESSABLE, + COLOR_MODE_CCT as FLUX_COLOR_MODE_CCT, + COLOR_MODE_DIM as FLUX_COLOR_MODE_DIM, + COLOR_MODE_RGB as FLUX_COLOR_MODE_RGB, + COLOR_MODE_RGBW as FLUX_COLOR_MODE_RGBW, + COLOR_MODE_RGBWW as FLUX_COLOR_MODE_RGBWW, +) import pytest from homeassistant.components import flux_led @@ -25,7 +34,11 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_EFFECT_LIST, ATTR_HS_COLOR, + ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, ATTR_SUPPORTED_COLOR_MODES, + ATTR_WHITE, DOMAIN as LIGHT_DOMAIN, ) from homeassistant.const import ( @@ -108,8 +121,9 @@ async def test_light_device_registry( config_entry.add_to_hass(hass) bulb = _mocked_bulb() bulb.protocol = protocol - bulb.raw_state[1] = model - bulb.raw_state[10] = sw_version + bulb.raw_state = bulb.raw_state._replace(model_num=model, version_number=sw_version) + bulb.model_num = model + with _patch_discovery(no_device=True), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -131,8 +145,9 @@ async def test_rgb_light(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() - bulb.rgbwcapable = False - bulb.protocol = None + bulb.raw_state = bulb.raw_state._replace(model_num=0x33) # RGB only model + bulb.color_modes = {FLUX_COLOR_MODE_RGB} + bulb.color_mode = FLUX_COLOR_MODE_RGB with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -143,9 +158,9 @@ async def test_rgb_light(hass: HomeAssistant) -> None: assert state.state == STATE_ON attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 128 - assert attributes[ATTR_COLOR_MODE] == "hs" + assert attributes[ATTR_COLOR_MODE] == "rgb" assert attributes[ATTR_EFFECT_LIST] == FLUX_EFFECT_LIST - assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"] + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgb"] assert attributes[ATTR_HS_COLOR] == (0, 100) await hass.services.async_call( @@ -170,8 +185,8 @@ async def test_rgb_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) - bulb.setRgb.assert_called_with(255, 0, 0, brightness=100) - bulb.setRgb.reset_mock() + bulb.set_levels.assert_called_with(255, 0, 0, brightness=100) + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, @@ -179,8 +194,8 @@ async def test_rgb_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, blocking=True, ) - bulb.setRgb.assert_called_with(255, 191, 178, brightness=128) - bulb.setRgb.reset_mock() + bulb.set_levels.assert_called_with(255, 191, 178, brightness=128) + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, @@ -188,8 +203,8 @@ async def test_rgb_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "random"}, blocking=True, ) - bulb.setRgb.assert_called_once() - bulb.setRgb.reset_mock() + bulb.set_levels.assert_called_once() + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, @@ -200,6 +215,137 @@ async def test_rgb_light(hass: HomeAssistant) -> None: bulb.setPresetPattern.assert_called_with(43, 50) bulb.setPresetPattern.reset_mock() + with pytest.raises(ValueError): + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "does not exist"}, + blocking=True, + ) + + +async def test_rgb_cct_light(hass: HomeAssistant) -> None: + """Test an rgb cct light.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + unique_id=MAC_ADDRESS, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_bulb() + bulb.raw_state = bulb.raw_state._replace(model_num=0x35) # RGB & CCT model + bulb.color_modes = {FLUX_COLOR_MODE_RGB, FLUX_COLOR_MODE_CCT} + bulb.color_mode = FLUX_COLOR_MODE_RGB + with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "light.az120444_aabbccddeeff" + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + attributes = state.attributes + assert attributes[ATTR_BRIGHTNESS] == 128 + assert attributes[ATTR_COLOR_MODE] == "rgb" + assert attributes[ATTR_EFFECT_LIST] == FLUX_EFFECT_LIST + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "rgb"] + assert attributes[ATTR_HS_COLOR] == (0, 100) + + await hass.services.async_call( + LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + bulb.turnOff.assert_called_once() + + bulb.is_on = False + async_fire_time_changed(hass, utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_OFF + + await hass.services.async_call( + LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + bulb.turnOn.assert_called_once() + bulb.turnOn.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, + blocking=True, + ) + bulb.set_levels.assert_called_with(255, 0, 0, brightness=100) + bulb.set_levels.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, + blocking=True, + ) + bulb.set_levels.assert_called_with(255, 191, 178, brightness=128) + bulb.set_levels.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "random"}, + blocking=True, + ) + bulb.set_levels.assert_called_once() + bulb.set_levels.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "purple_fade"}, + blocking=True, + ) + bulb.setPresetPattern.assert_called_with(43, 50) + bulb.setPresetPattern.reset_mock() + + bulb.is_on = True + bulb.color_mode = FLUX_COLOR_MODE_CCT + bulb.getWhiteTemperature = Mock(return_value=(5000, 128)) + bulb.raw_state = bulb.raw_state._replace( + red=0, green=0, blue=0, warm_white=1, cool_white=2 + ) + async_fire_time_changed(hass, utcnow() + timedelta(seconds=60)) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_ON + attributes = state.attributes + assert attributes[ATTR_BRIGHTNESS] == 128 + assert attributes[ATTR_COLOR_MODE] == "color_temp" + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "rgb"] + assert attributes[ATTR_COLOR_TEMP] == 200 + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 370}, + blocking=True, + ) + bulb.setWhiteTemperature.assert_called_with(2702, 128) + bulb.setWhiteTemperature.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 255}, + blocking=True, + ) + bulb.setWhiteTemperature.assert_called_with(5000, 255) + bulb.setWhiteTemperature.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128}, + blocking=True, + ) + bulb.setWhiteTemperature.assert_called_with(5000, 128) + bulb.setWhiteTemperature.reset_mock() + async def test_rgbw_light(hass: HomeAssistant) -> None: """Test an rgbw light.""" @@ -210,6 +356,8 @@ async def test_rgbw_light(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() + bulb.color_modes = {FLUX_COLOR_MODE_RGBW} + bulb.color_mode = FLUX_COLOR_MODE_RGBW with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -222,8 +370,8 @@ async def test_rgbw_light(hass: HomeAssistant) -> None: assert attributes[ATTR_BRIGHTNESS] == 128 assert attributes[ATTR_COLOR_MODE] == "rgbw" assert attributes[ATTR_EFFECT_LIST] == FLUX_EFFECT_LIST - assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs", "rgbw"] - assert attributes[ATTR_HS_COLOR] == (0, 100) + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgbw"] + assert attributes[ATTR_RGB_COLOR] == (255, 42, 42) await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True @@ -240,6 +388,7 @@ async def test_rgbw_light(hass: HomeAssistant) -> None: ) bulb.turnOn.assert_called_once() bulb.turnOn.reset_mock() + bulb.is_on = True await hass.services.async_call( LIGHT_DOMAIN, @@ -247,35 +396,41 @@ async def test_rgbw_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) - bulb.setRgbw.assert_called_with(255, 0, 0, w=50, brightness=100) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(168, 0, 0, 33) + bulb.set_levels.reset_mock() + state = hass.states.get(entity_id) + assert state.state == STATE_ON await hass.services.async_call( LIGHT_DOMAIN, "turn_on", - {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 150}, + { + ATTR_ENTITY_ID: entity_id, + ATTR_RGBW_COLOR: (255, 255, 255, 255), + ATTR_BRIGHTNESS: 128, + }, blocking=True, ) - bulb.setRgbw.assert_called_with(w2=128) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(128, 128, 128, 128) + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", - {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 290}, + {ATTR_ENTITY_ID: entity_id, ATTR_RGBW_COLOR: (255, 255, 255, 255)}, blocking=True, ) - bulb.setRgbw.assert_called_with(w=128) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(255, 255, 255, 255) + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", - {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, + {ATTR_ENTITY_ID: entity_id, ATTR_RGBW_COLOR: (255, 191, 178, 0)}, blocking=True, ) - bulb.setRgbw.assert_called_with(255, 191, 178, w=50, brightness=128) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(255, 191, 178, 0) + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, @@ -283,8 +438,8 @@ async def test_rgbw_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "random"}, blocking=True, ) - bulb.setRgb.assert_called_once() - bulb.setRgb.reset_mock() + bulb.set_levels.assert_called_once() + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, @@ -305,9 +460,9 @@ async def test_rgbcw_light(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() - bulb.raw_state[9] = 1 - bulb.raw_state[11] = 2 - + bulb.raw_state = bulb.raw_state._replace(warm_white=1, cool_white=2) + bulb.color_modes = {FLUX_COLOR_MODE_RGBWW, FLUX_COLOR_MODE_CCT} + bulb.color_mode = FLUX_COLOR_MODE_RGBWW with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -318,10 +473,10 @@ async def test_rgbcw_light(hass: HomeAssistant) -> None: assert state.state == STATE_ON attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 128 - assert attributes[ATTR_COLOR_MODE] == "rgbw" + assert attributes[ATTR_COLOR_MODE] == "rgbww" assert attributes[ATTR_EFFECT_LIST] == FLUX_EFFECT_LIST - assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs", "rgbw"] - assert attributes[ATTR_HS_COLOR] == (0, 100) + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "rgbww"] + assert attributes[ATTR_HS_COLOR] == (3.237, 94.51) await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True @@ -345,17 +500,49 @@ async def test_rgbcw_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) - bulb.setRgbw.assert_called_with(255, 0, 0, w=50, brightness=100) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(250, 0, 0, 49, 0) + bulb.set_levels.reset_mock() + bulb.is_on = True await hass.services.async_call( LIGHT_DOMAIN, "turn_on", - {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 150}, + { + ATTR_ENTITY_ID: entity_id, + ATTR_RGBWW_COLOR: (255, 255, 255, 0, 255), + ATTR_BRIGHTNESS: 128, + }, blocking=True, ) - bulb.setRgbw.assert_called_with(w2=128) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(192, 192, 192, 192, 0) + bulb.set_levels.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_RGBWW_COLOR: (255, 255, 255, 255, 50)}, + blocking=True, + ) + bulb.set_levels.assert_called_with(255, 255, 255, 50, 255) + bulb.set_levels.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 154}, + blocking=True, + ) + bulb.set_levels.assert_called_with(r=0, b=0, g=0, w=0, w2=127) + bulb.set_levels.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 154, ATTR_BRIGHTNESS: 255}, + blocking=True, + ) + bulb.set_levels.assert_called_with(r=0, b=0, g=0, w=0, w2=255) + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, @@ -363,17 +550,17 @@ async def test_rgbcw_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 290}, blocking=True, ) - bulb.setRgbw.assert_called_with(w=128) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(r=0, b=0, g=0, w=102, w2=25) + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", - {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, + {ATTR_ENTITY_ID: entity_id, ATTR_RGBWW_COLOR: (255, 191, 178, 0, 0)}, blocking=True, ) - bulb.setRgbw.assert_called_with(255, 191, 178, w=50, brightness=128) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(255, 191, 178, 0, 0) + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, @@ -381,8 +568,8 @@ async def test_rgbcw_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "random"}, blocking=True, ) - bulb.setRgb.assert_called_once() - bulb.setRgb.reset_mock() + bulb.set_levels.assert_called_once() + bulb.set_levels.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, @@ -405,6 +592,8 @@ async def test_white_light(hass: HomeAssistant) -> None: bulb = _mocked_bulb() bulb.mode = "ww" bulb.protocol = None + bulb.color_modes = {FLUX_COLOR_MODE_DIM} + bulb.color_mode = FLUX_COLOR_MODE_DIM with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -414,9 +603,9 @@ async def test_white_light(hass: HomeAssistant) -> None: state = hass.states.get(entity_id) assert state.state == STATE_ON attributes = state.attributes - assert attributes[ATTR_BRIGHTNESS] == 50 - assert attributes[ATTR_COLOR_MODE] == "brightness" - assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["brightness"] + assert attributes[ATTR_BRIGHTNESS] == 128 + assert attributes[ATTR_COLOR_MODE] == "white" + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["white"] await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True @@ -440,13 +629,20 @@ async def test_white_light(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) - bulb.setRgbw.assert_called_with(0, 0, 0, w=100) - bulb.setRgbw.reset_mock() + bulb.set_levels.assert_called_with(w=100) + bulb.set_levels.reset_mock() + + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_WHITE: 100}, + blocking=True, + ) + bulb.set_levels.assert_called_with(w=100) + bulb.set_levels.reset_mock() -async def test_rgb_light_custom_effects( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture -) -> None: +async def test_rgb_light_custom_effects(hass: HomeAssistant) -> None: """Test an rgb light with a custom effect.""" config_entry = MockConfigEntry( domain=DOMAIN, @@ -461,6 +657,8 @@ async def test_rgb_light_custom_effects( ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() + bulb.color_modes = {FLUX_COLOR_MODE_RGB} + bulb.color_mode = FLUX_COLOR_MODE_RGB with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -471,9 +669,9 @@ async def test_rgb_light_custom_effects( assert state.state == STATE_ON attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 128 - assert attributes[ATTR_COLOR_MODE] == "rgbw" + assert attributes[ATTR_COLOR_MODE] == "rgb" assert attributes[ATTR_EFFECT_LIST] == [*FLUX_EFFECT_LIST, "custom"] - assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs", "rgbw"] + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgb"] assert attributes[ATTR_HS_COLOR] == (0, 100) await hass.services.async_call( @@ -494,7 +692,7 @@ async def test_rgb_light_custom_effects( ) bulb.setCustomPattern.assert_called_with([[0, 0, 255], [255, 0, 0]], 88, "jump") bulb.setCustomPattern.reset_mock() - bulb.raw_state[3] = EFFECT_CUSTOM_CODE + bulb.raw_state = bulb.raw_state._replace(preset_pattern=EFFECT_CUSTOM_CODE) bulb.is_on = True async_fire_time_changed(hass, utcnow() + timedelta(seconds=20)) await hass.async_block_till_done() @@ -503,7 +701,6 @@ async def test_rgb_light_custom_effects( attributes = state.attributes assert attributes[ATTR_EFFECT] == "custom" - caplog.clear() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", @@ -512,7 +709,7 @@ async def test_rgb_light_custom_effects( ) bulb.setCustomPattern.assert_called_with([[0, 0, 255], [255, 0, 0]], 88, "jump") bulb.setCustomPattern.reset_mock() - bulb.raw_state[3] = EFFECT_CUSTOM_CODE + bulb.raw_state = bulb.raw_state._replace(preset_pattern=EFFECT_CUSTOM_CODE) bulb.is_on = True async_fire_time_changed(hass, utcnow() + timedelta(seconds=20)) await hass.async_block_till_done() @@ -520,7 +717,6 @@ async def test_rgb_light_custom_effects( assert state.state == STATE_ON attributes = state.attributes assert attributes[ATTR_EFFECT] == "custom" - assert "RGB, brightness and white level are ignored when" in caplog.text async def test_rgb_light_custom_effects_invalid_colors(hass: HomeAssistant) -> None: @@ -538,6 +734,8 @@ async def test_rgb_light_custom_effects_invalid_colors(hass: HomeAssistant) -> N ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() + bulb.color_modes = {FLUX_COLOR_MODE_RGB} + bulb.color_mode = FLUX_COLOR_MODE_RGB with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -548,9 +746,9 @@ async def test_rgb_light_custom_effects_invalid_colors(hass: HomeAssistant) -> N assert state.state == STATE_ON attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 128 - assert attributes[ATTR_COLOR_MODE] == "rgbw" + assert attributes[ATTR_COLOR_MODE] == "rgb" assert attributes[ATTR_EFFECT_LIST] == FLUX_EFFECT_LIST - assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs", "rgbw"] + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgb"] assert attributes[ATTR_HS_COLOR] == (0, 100) @@ -565,6 +763,8 @@ async def test_rgb_light_custom_effect_via_service( ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() + bulb.color_modes = {FLUX_COLOR_MODE_RGB} + bulb.color_mode = FLUX_COLOR_MODE_RGB with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -575,9 +775,9 @@ async def test_rgb_light_custom_effect_via_service( assert state.state == STATE_ON attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 128 - assert attributes[ATTR_COLOR_MODE] == "rgbw" + assert attributes[ATTR_COLOR_MODE] == "rgb" assert attributes[ATTR_EFFECT_LIST] == [*FLUX_EFFECT_LIST] - assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs", "rgbw"] + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgb"] assert attributes[ATTR_HS_COLOR] == (0, 100) await hass.services.async_call( @@ -605,34 +805,6 @@ async def test_rgb_light_custom_effect_via_service( bulb.setCustomPattern.reset_mock() -async def test_rgbw_detection_without_protocol(hass: HomeAssistant) -> None: - """Test an rgbw detection without protocol.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, - unique_id=MAC_ADDRESS, - ) - config_entry.add_to_hass(hass) - bulb = _mocked_bulb() - bulb.protocol = None - bulb.rgbwprotocol = None - bulb.rgbwcapable = True - with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): - await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) - await hass.async_block_till_done() - - entity_id = "light.az120444_aabbccddeeff" - - state = hass.states.get(entity_id) - assert state.state == STATE_ON - attributes = state.attributes - assert attributes[ATTR_BRIGHTNESS] == 128 - assert attributes[ATTR_COLOR_MODE] == "rgbw" - assert attributes[ATTR_EFFECT_LIST] == FLUX_EFFECT_LIST - assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs", "rgbw"] - assert attributes[ATTR_HS_COLOR] == (0, 100) - - async def test_migrate_from_yaml(hass: HomeAssistant) -> None: """Test migrate from yaml.""" config = { @@ -680,3 +852,54 @@ async def test_migrate_from_yaml(hass: HomeAssistant) -> None: CONF_CUSTOM_EFFECT_SPEED_PCT: 30, CONF_CUSTOM_EFFECT_TRANSITION: "strobe", } + + +async def test_addressable_light(hass: HomeAssistant) -> None: + """Test an addressable light.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE}, + unique_id=MAC_ADDRESS, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_bulb() + bulb.raw_state = bulb.raw_state._replace(model_num=0x33) # RGB only model + bulb.color_modes = {FLUX_COLOR_MODE_ADDRESSABLE} + bulb.color_mode = FLUX_COLOR_MODE_ADDRESSABLE + with _patch_discovery(device=bulb), _patch_wifibulb(device=bulb): + await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "light.az120444_aabbccddeeff" + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + attributes = state.attributes + assert attributes[ATTR_COLOR_MODE] == "onoff" + assert attributes[ATTR_EFFECT_LIST] == FLUX_EFFECT_LIST + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == ["onoff"] + + await hass.services.async_call( + LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + bulb.turnOff.assert_called_once() + + bulb.is_on = False + async_fire_time_changed(hass, utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert hass.states.get(entity_id).state == STATE_OFF + + await hass.services.async_call( + LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + bulb.turnOn.assert_called_once() + bulb.turnOn.reset_mock() + bulb.is_on = True + + with pytest.raises(ValueError): + await hass.services.async_call( + LIGHT_DOMAIN, + "turn_on", + {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, + blocking=True, + )