"""Tests for the lifx integration light platform.""" from datetime import timedelta from unittest.mock import patch import aiolifx_effects import pytest from homeassistant.components import lifx from homeassistant.components.lifx import DOMAIN from homeassistant.components.lifx.const import ATTR_POWER from homeassistant.components.lifx.light import ATTR_INFRARED, ATTR_ZONES from homeassistant.components.lifx.manager import ( ATTR_DIRECTION, ATTR_PALETTE, ATTR_SPEED, ATTR_THEME, SERVICE_EFFECT_COLORLOOP, SERVICE_EFFECT_MORPH, SERVICE_EFFECT_MOVE, ) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_MODE, ATTR_COLOR_TEMP, ATTR_COLOR_TEMP_KELVIN, ATTR_EFFECT, ATTR_HS_COLOR, ATTR_RGB_COLOR, ATTR_SUPPORTED_COLOR_MODES, ATTR_TRANSITION, ATTR_XY_COLOR, DOMAIN as LIGHT_DOMAIN, ColorMode, ) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from . import ( IP_ADDRESS, MAC_ADDRESS, SERIAL, MockFailingLifxCommand, MockLifxCommand, MockMessage, _mocked_brightness_bulb, _mocked_bulb, _mocked_bulb_new_firmware, _mocked_clean_bulb, _mocked_light_strip, _mocked_tile, _mocked_white_bulb, _patch_config_flow_try_connect, _patch_device, _patch_discovery, ) from tests.common import MockConfigEntry, async_fire_time_changed @pytest.fixture(autouse=True) def patch_lifx_state_settle_delay(): """Set asyncio.sleep for state settles to zero.""" with patch("homeassistant.components.lifx.light.LIFX_STATE_SETTLE_DELAY", 0): yield async def test_light_unique_id(hass: HomeAssistant) -> None: """Test a light unique id.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "1.2.3.4"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" entity_registry = er.async_get(hass) assert entity_registry.async_get(entity_id).unique_id == SERIAL device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers=set(), connections={(dr.CONNECTION_NETWORK_MAC, SERIAL)} ) assert device.identifiers == {(DOMAIN, SERIAL)} async def test_light_unique_id_new_firmware(hass: HomeAssistant) -> None: """Test a light unique id with newer firmware.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "1.2.3.4"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb_new_firmware() with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" entity_registry = er.async_get(hass) assert entity_registry.async_get(entity_id).unique_id == SERIAL device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers=set(), connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)}, ) assert device.identifiers == {(DOMAIN, SERIAL)} async def test_light_strip(hass: HomeAssistant) -> None: """Test a light strip.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_light_strip() bulb.power_level = 65535 bulb.color = [65535, 65535, 65535, 65535] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 255 assert attributes[ATTR_COLOR_MODE] == ColorMode.HS assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ ColorMode.COLOR_TEMP, ColorMode.HS, ] assert attributes[ATTR_HS_COLOR] == (360.0, 100.0) assert attributes[ATTR_RGB_COLOR] == (255, 0, 0) assert attributes[ATTR_XY_COLOR] == (0.701, 0.299) await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is False bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is True bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) call_dict = bulb.set_color_zones.calls[0][1] call_dict.pop("callb") assert call_dict == { "apply": 0, "color": [], "duration": 0, "end_index": 0, "start_index": 0, } bulb.set_color_zones.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, blocking=True, ) call_dict = bulb.set_color_zones.calls[0][1] call_dict.pop("callb") assert call_dict == { "apply": 0, "color": [], "duration": 0, "end_index": 0, "start_index": 0, } bulb.set_color_zones.reset_mock() bulb.color_zones = [ (0, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, blocking=True, ) # Single color uses the fast path assert bulb.set_color.calls[0][0][0] == [1820, 19660, 65535, 3500] bulb.set_color.reset_mock() assert len(bulb.set_color_zones.calls) == 0 bulb.color_zones = [ (0, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] await hass.services.async_call( DOMAIN, "set_state", {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 10, 30)}, blocking=True, ) # Single color uses the fast path assert bulb.set_color.calls[0][0][0] == [64643, 62964, 65535, 3500] bulb.set_color.reset_mock() assert len(bulb.set_color_zones.calls) == 0 bulb.color_zones = [ (0, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] await hass.services.async_call( DOMAIN, "set_state", {ATTR_ENTITY_ID: entity_id, ATTR_XY_COLOR: (0.3, 0.7)}, blocking=True, ) # Single color uses the fast path assert bulb.set_color.calls[0][0][0] == [15848, 65535, 65535, 3500] bulb.set_color.reset_mock() assert len(bulb.set_color_zones.calls) == 0 bulb.color_zones = [ (0, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] await hass.services.async_call( DOMAIN, "set_state", {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128}, blocking=True, ) # multiple zones in effect and we are changing the brightness # we need to do each zone individually assert len(bulb.set_color.calls) == 0 call_dict = bulb.set_color_zones.calls[0][1] call_dict.pop("callb") assert call_dict == { "apply": 0, "color": [0, 65535, 32896, 3500], "duration": 0, "end_index": 0, "start_index": 0, } call_dict = bulb.set_color_zones.calls[1][1] call_dict.pop("callb") assert call_dict == { "apply": 0, "color": [54612, 65535, 32896, 3500], "duration": 0, "end_index": 1, "start_index": 1, } call_dict = bulb.set_color_zones.calls[7][1] call_dict.pop("callb") assert call_dict == { "apply": 1, "color": [46420, 65535, 32896, 3500], "duration": 0, "end_index": 7, "start_index": 7, } bulb.set_color_zones.reset_mock() await hass.services.async_call( DOMAIN, "set_state", { ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [0, 2], }, blocking=True, ) # set a two zones assert len(bulb.set_color.calls) == 0 call_dict = bulb.set_color_zones.calls[0][1] call_dict.pop("callb") assert call_dict == { "apply": 0, "color": [0, 0, 65535, 3500], "duration": 0, "end_index": 0, "start_index": 0, } call_dict = bulb.set_color_zones.calls[1][1] call_dict.pop("callb") assert call_dict == { "apply": 1, "color": [0, 0, 65535, 3500], "duration": 0, "end_index": 2, "start_index": 2, } bulb.set_color_zones.reset_mock() bulb.get_color_zones.reset_mock() bulb.set_power.reset_mock() bulb.power_level = 0 await hass.services.async_call( DOMAIN, "set_state", {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3]}, blocking=True, ) # set a one zone assert len(bulb.set_power.calls) == 2 assert len(bulb.get_color_zones.calls) == 2 assert len(bulb.set_color.calls) == 0 call_dict = bulb.set_color_zones.calls[0][1] call_dict.pop("callb") assert call_dict == { "apply": 1, "color": [0, 0, 65535, 3500], "duration": 0, "end_index": 3, "start_index": 3, } bulb.get_color_zones.reset_mock() bulb.set_power.reset_mock() bulb.set_color_zones.reset_mock() bulb.set_color_zones = MockFailingLifxCommand(bulb) with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, "set_state", { ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3], }, blocking=True, ) bulb.set_color_zones = MockLifxCommand(bulb) bulb.get_color_zones = MockFailingLifxCommand(bulb) with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, "set_state", { ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3], }, blocking=True, ) bulb.get_color_zones = MockLifxCommand(bulb) bulb.get_color = MockFailingLifxCommand(bulb) with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, "set_state", { ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3], }, blocking=True, ) async def test_extended_multizone_messages(hass: HomeAssistant) -> None: """Test a light strip that supports extended multizone.""" config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) config_entry.add_to_hass(hass) bulb = _mocked_light_strip() bulb.product = 38 # LIFX Beam bulb.power_level = 65535 bulb.color = [65535, 65535, 65535, 3500] bulb.color_zones = [(65535, 65535, 65535, 3500)] * 8 bulb.zones_count = 8 with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 255 assert attributes[ATTR_COLOR_MODE] == ColorMode.HS assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ ColorMode.COLOR_TEMP, ColorMode.HS, ] assert attributes[ATTR_HS_COLOR] == (360.0, 100.0) assert attributes[ATTR_RGB_COLOR] == (255, 0, 0) assert attributes[ATTR_XY_COLOR] == (0.701, 0.299) await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is False bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is True bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) assert len(bulb.set_color_zones.calls) == 0 assert len(bulb.set_extended_color_zones.calls) == 1 bulb.set_color_zones.reset_mock() bulb.set_extended_color_zones.reset_mock() bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, blocking=True, ) assert len(bulb.set_color.calls) == 0 assert len(bulb.set_color_zones.calls) == 0 assert len(bulb.set_extended_color_zones.calls) == 1 bulb.set_color.reset_mock() bulb.set_color_zones.reset_mock() bulb.set_extended_color_zones.reset_mock() bulb.color_zones = [ (0, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, blocking=True, ) assert len(bulb.set_color.calls) == 0 assert len(bulb.set_color_zones.calls) == 0 assert len(bulb.set_extended_color_zones.calls) == 1 bulb.set_color.reset_mock() bulb.set_color_zones.reset_mock() bulb.set_extended_color_zones.reset_mock() bulb.color_zones = [ (0, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] await hass.services.async_call( DOMAIN, "set_state", {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 10, 30)}, blocking=True, ) # always use a set_extended_color_zones assert len(bulb.set_color.calls) == 0 assert len(bulb.set_color_zones.calls) == 0 assert len(bulb.set_extended_color_zones.calls) == 1 bulb.set_color.reset_mock() bulb.set_color_zones.reset_mock() bulb.set_extended_color_zones.reset_mock() bulb.color_zones = [ (0, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] await hass.services.async_call( DOMAIN, "set_state", {ATTR_ENTITY_ID: entity_id, ATTR_XY_COLOR: (0.3, 0.7)}, blocking=True, ) # Single color uses the fast path assert len(bulb.set_color.calls) == 0 assert len(bulb.set_color_zones.calls) == 0 assert len(bulb.set_extended_color_zones.calls) == 1 bulb.set_color.reset_mock() bulb.set_color_zones.reset_mock() bulb.set_extended_color_zones.reset_mock() bulb.color_zones = [ (0, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (54612, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] await hass.services.async_call( DOMAIN, "set_state", {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128}, blocking=True, ) # always use set_extended_color_zones assert len(bulb.set_color.calls) == 0 assert len(bulb.set_color_zones.calls) == 0 assert len(bulb.set_extended_color_zones.calls) == 1 bulb.set_color.reset_mock() bulb.set_color_zones.reset_mock() bulb.set_extended_color_zones.reset_mock() await hass.services.async_call( DOMAIN, "set_state", { ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [0, 2], }, blocking=True, ) # set a two zones assert len(bulb.set_color.calls) == 0 assert len(bulb.set_color_zones.calls) == 0 assert len(bulb.set_extended_color_zones.calls) == 1 bulb.set_color.reset_mock() bulb.set_color_zones.reset_mock() bulb.set_extended_color_zones.reset_mock() bulb.power_level = 0 await hass.services.async_call( DOMAIN, "set_state", {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3]}, blocking=True, ) # set a one zone assert len(bulb.set_power.calls) == 2 assert len(bulb.get_color_zones.calls) == 0 assert len(bulb.set_color.calls) == 0 assert len(bulb.set_color_zones.calls) == 0 bulb.get_color_zones.reset_mock() bulb.set_power.reset_mock() bulb.set_color_zones.reset_mock() bulb.set_extended_color_zones = MockFailingLifxCommand(bulb) with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, "set_state", { ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3], }, blocking=True, ) bulb.set_extended_color_zones = MockLifxCommand(bulb) bulb.get_extended_color_zones = MockFailingLifxCommand(bulb) with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, "set_state", { ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 255, 255), ATTR_ZONES: [3], }, blocking=True, ) async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None: """Test the firmware flame and morph effects on a matrix device.""" config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) config_entry.add_to_hass(hass) bulb = _mocked_tile() bulb.power_level = 0 bulb.color = [65535, 65535, 65535, 65535] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_flame"}, blocking=True, ) assert len(bulb.set_power.calls) == 1 assert len(bulb.set_tile_effect.calls) == 1 call_dict = bulb.set_tile_effect.calls[0][1] call_dict.pop("callb") assert call_dict == { "effect": 3, "speed": 3, "palette": [], } bulb.get_tile_effect.reset_mock() bulb.set_tile_effect.reset_mock() bulb.set_power.reset_mock() bulb.power_level = 0 await hass.services.async_call( DOMAIN, SERVICE_EFFECT_MORPH, {ATTR_ENTITY_ID: entity_id, ATTR_SPEED: 4, ATTR_THEME: "autumn"}, blocking=True, ) bulb.power_level = 65535 bulb.effect = { "effect": "MORPH", "speed": 4.0, "palette": [ (5643, 65535, 32768, 3500), (15109, 65535, 32768, 3500), (8920, 65535, 32768, 3500), (10558, 65535, 32768, 3500), ], } async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON assert len(bulb.set_power.calls) == 1 assert len(bulb.set_tile_effect.calls) == 1 call_dict = bulb.set_tile_effect.calls[0][1] call_dict.pop("callb") assert call_dict == { "effect": 2, "speed": 4, "palette": [ (5643, 65535, 32768, 3500), (15109, 65535, 32768, 3500), (8920, 65535, 32768, 3500), (10558, 65535, 32768, 3500), ], } bulb.get_tile_effect.reset_mock() bulb.set_tile_effect.reset_mock() bulb.set_power.reset_mock() bulb.power_level = 0 await hass.services.async_call( DOMAIN, SERVICE_EFFECT_MORPH, { ATTR_ENTITY_ID: entity_id, ATTR_SPEED: 6, ATTR_PALETTE: [ (0, 100, 255, 3500), (60, 100, 255, 3500), (120, 100, 255, 3500), (180, 100, 255, 3500), (240, 100, 255, 3500), (300, 100, 255, 3500), ], }, blocking=True, ) bulb.power_level = 65535 bulb.effect = { "effect": "MORPH", "speed": 6, "palette": [ (0, 65535, 65535, 3500), (10922, 65535, 65535, 3500), (21845, 65535, 65535, 3500), (32768, 65535, 65535, 3500), (43690, 65535, 65535, 3500), (54612, 65535, 65535, 3500), ], } async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON assert len(bulb.set_power.calls) == 1 assert len(bulb.set_tile_effect.calls) == 1 call_dict = bulb.set_tile_effect.calls[0][1] call_dict.pop("callb") assert call_dict == { "effect": 2, "speed": 6, "palette": [ (0, 65535, 65535, 3500), (10922, 65535, 65535, 3500), (21845, 65535, 65535, 3500), (32768, 65535, 65535, 3500), (43690, 65535, 65535, 3500), (54613, 65535, 65535, 3500), ], } bulb.get_tile_effect.reset_mock() bulb.set_tile_effect.reset_mock() bulb.set_power.reset_mock() async def test_lightstrip_move_effect(hass: HomeAssistant) -> None: """Test the firmware move effect on a light strip.""" config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) config_entry.add_to_hass(hass) bulb = _mocked_light_strip() bulb.power_level = 0 bulb.color = [65535, 65535, 65535, 65535] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_move"}, blocking=True, ) assert len(bulb.set_power.calls) == 1 assert len(bulb.set_multizone_effect.calls) == 1 call_dict = bulb.set_multizone_effect.calls[0][1] call_dict.pop("callb") assert call_dict == { "effect": 1, "speed": 3.0, "direction": 0, } bulb.get_multizone_effect.reset_mock() bulb.set_multizone_effect.reset_mock() bulb.set_power.reset_mock() bulb.power_level = 0 await hass.services.async_call( DOMAIN, SERVICE_EFFECT_MOVE, {ATTR_ENTITY_ID: entity_id, ATTR_SPEED: 4.5, ATTR_DIRECTION: "left"}, blocking=True, ) bulb.power_level = 65535 bulb.effect = {"name": "MOVE", "speed": 4.5, "direction": "Left"} async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON assert len(bulb.set_power.calls) == 1 assert len(bulb.set_multizone_effect.calls) == 1 call_dict = bulb.set_multizone_effect.calls[0][1] call_dict.pop("callb") assert call_dict == { "effect": 1, "speed": 4.5, "direction": 1, } bulb.get_multizone_effect.reset_mock() bulb.set_multizone_effect.reset_mock() bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_stop"}, blocking=True, ) assert len(bulb.set_power.calls) == 0 assert len(bulb.set_multizone_effect.calls) == 1 call_dict = bulb.set_multizone_effect.calls[0][1] call_dict.pop("callb") assert call_dict == { "effect": 0, "speed": 3.0, "direction": 0, } bulb.get_multizone_effect.reset_mock() bulb.set_multizone_effect.reset_mock() bulb.set_power.reset_mock() async def test_color_light_with_temp( hass: HomeAssistant, mock_effect_conductor ) -> None: """Test a color light with temp.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() bulb.power_level = 65535 bulb.color = [65535, 65535, 65535, 65535] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 255 assert attributes[ATTR_COLOR_MODE] == ColorMode.HS assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ ColorMode.COLOR_TEMP, ColorMode.HS, ] assert attributes[ATTR_HS_COLOR] == (360.0, 100.0) assert attributes[ATTR_RGB_COLOR] == (255, 0, 0) assert attributes[ATTR_XY_COLOR] == (0.701, 0.299) bulb.color = [32000, None, 32000, 6000] await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is True bulb.set_power.reset_mock() state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 125 assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ ColorMode.COLOR_TEMP, ColorMode.HS, ] assert attributes[ATTR_HS_COLOR] == (30.754, 7.122) assert attributes[ATTR_RGB_COLOR] == (255, 246, 236) assert attributes[ATTR_XY_COLOR] == (0.34, 0.339) bulb.color = [65535, 65535, 65535, 65535] await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is False bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is True bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [65535, 65535, 25700, 65535] bulb.set_color.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)}, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [1820, 19660, 65535, 3500] bulb.set_color.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (255, 30, 80)}, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [63107, 57824, 65535, 3500] bulb.set_color.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_XY_COLOR: (0.46, 0.376)}, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [4956, 30583, 65535, 3500] bulb.set_color.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_colorloop"}, blocking=True, ) start_call = mock_effect_conductor.start.mock_calls first_call = start_call[0][1] assert isinstance(first_call[0], aiolifx_effects.EffectColorloop) assert first_call[1][0] == bulb mock_effect_conductor.start.reset_mock() mock_effect_conductor.stop.reset_mock() await hass.services.async_call( DOMAIN, SERVICE_EFFECT_COLORLOOP, {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 128}, blocking=True, ) start_call = mock_effect_conductor.start.mock_calls first_call = start_call[0][1] assert isinstance(first_call[0], aiolifx_effects.EffectColorloop) assert first_call[1][0] == bulb mock_effect_conductor.start.reset_mock() mock_effect_conductor.stop.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_pulse"}, blocking=True, ) assert len(mock_effect_conductor.stop.mock_calls) == 1 start_call = mock_effect_conductor.start.mock_calls first_call = start_call[0][1] assert isinstance(first_call[0], aiolifx_effects.EffectPulse) assert first_call[1][0] == bulb mock_effect_conductor.start.reset_mock() mock_effect_conductor.stop.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_stop"}, blocking=True, ) assert len(mock_effect_conductor.stop.mock_calls) == 2 async def test_white_bulb(hass: HomeAssistant) -> None: """Test a white bulb.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_white_bulb() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 125 assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ ColorMode.COLOR_TEMP, ] assert attributes[ATTR_COLOR_TEMP_KELVIN] == 6000 await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is False bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is True bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [32000, None, 25700, 6000] bulb.set_color.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 400}, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [32000, 0, 32000, 2500] bulb.set_color.reset_mock() async def test_config_zoned_light_strip_fails(hass): """Test we handle failure to update zones.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) light_strip = _mocked_light_strip() entity_id = "light.my_bulb" class MockFailingLifxCommand: """Mock a lifx command that fails on the 3rd try.""" def __init__(self, bulb, **kwargs): """Init command.""" self.bulb = bulb self.call_count = 0 def __call__(self, callb=None, *args, **kwargs): """Call command.""" self.call_count += 1 response = None if self.call_count >= 3 else MockMessage() if callb: callb(self.bulb, response) light_strip.get_color_zones = MockFailingLifxCommand(light_strip) with _patch_discovery(device=light_strip), _patch_device(device=light_strip): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_registry = er.async_get(hass) assert entity_registry.async_get(entity_id).unique_id == SERIAL assert hass.states.get(entity_id).state == STATE_OFF async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() assert hass.states.get(entity_id).state == STATE_UNAVAILABLE async def test_white_light_fails(hass): """Test we handle failure to power on off.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_white_bulb() entity_id = "light.my_bulb" bulb.set_power = MockFailingLifxCommand(bulb) with _patch_discovery(device=bulb), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_registry = er.async_get(hass) assert entity_registry.async_get(entity_id).unique_id == SERIAL assert hass.states.get(entity_id).state == STATE_OFF with pytest.raises(HomeAssistantError): await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is True bulb.set_power.reset_mock() bulb.set_power = MockLifxCommand(bulb) bulb.set_color = MockFailingLifxCommand(bulb) with pytest.raises(HomeAssistantError): await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP_KELVIN: 6000}, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [1, 0, 3, 6000] bulb.set_color.reset_mock() async def test_brightness_bulb(hass: HomeAssistant) -> None: """Test a brightness only bulb.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_brightness_bulb() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 125 assert attributes[ATTR_COLOR_MODE] == ColorMode.BRIGHTNESS assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ ColorMode.BRIGHTNESS, ] await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is False bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is True bulb.set_power.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100}, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [32000, None, 25700, 6000] bulb.set_color.reset_mock() async def test_transitions_brightness_only(hass: HomeAssistant) -> None: """Test transitions with a brightness only device.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_brightness_bulb() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 125 assert attributes[ATTR_COLOR_MODE] == ColorMode.BRIGHTNESS assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ ColorMode.BRIGHTNESS, ] await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is False bulb.set_power.reset_mock() bulb.power_level = 0 await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 5, ATTR_BRIGHTNESS: 100}, blocking=True, ) assert bulb.set_power.calls[0][0][0] is True call_dict = bulb.set_power.calls[0][1] call_dict.pop("callb") assert call_dict == {"duration": 5000} bulb.set_power.reset_mock() bulb.power_level = 0 await hass.services.async_call( LIGHT_DOMAIN, "turn_on", {ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 5, ATTR_BRIGHTNESS: 200}, blocking=True, ) assert bulb.set_power.calls[0][0][0] is True call_dict = bulb.set_power.calls[0][1] call_dict.pop("callb") assert call_dict == {"duration": 5000} bulb.set_power.reset_mock() await hass.async_block_till_done() bulb.get_color.reset_mock() # Ensure we force an update after the transition async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5)) await hass.async_block_till_done() assert len(bulb.get_color.calls) == 2 async def test_transitions_color_bulb(hass: HomeAssistant) -> None: """Test transitions with a color bulb.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb_new_firmware() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 125 assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is False bulb.set_power.reset_mock() bulb.power_level = 0 await hass.services.async_call( LIGHT_DOMAIN, "turn_off", { ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 5, }, blocking=True, ) assert bulb.set_power.calls[0][0][0] is False call_dict = bulb.set_power.calls[0][1] call_dict.pop("callb") assert call_dict == {"duration": 0} # already off bulb.set_power.reset_mock() bulb.set_color.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_on", { ATTR_RGB_COLOR: (255, 5, 10), ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 5, ATTR_BRIGHTNESS: 100, }, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [65316, 64249, 25700, 3500] assert bulb.set_power.calls[0][0][0] is True call_dict = bulb.set_power.calls[0][1] call_dict.pop("callb") assert call_dict == {"duration": 5000} bulb.set_power.reset_mock() bulb.set_color.reset_mock() bulb.power_level = 12800 await hass.services.async_call( LIGHT_DOMAIN, "turn_on", { ATTR_RGB_COLOR: (5, 5, 10), ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 5, ATTR_BRIGHTNESS: 200, }, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [43690, 32767, 51400, 3500] call_dict = bulb.set_color.calls[0][1] call_dict.pop("callb") assert call_dict == {"duration": 5000} bulb.set_power.reset_mock() bulb.set_color.reset_mock() await hass.async_block_till_done() bulb.get_color.reset_mock() # Ensure we force an update after the transition async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5)) await hass.async_block_till_done() assert len(bulb.get_color.calls) == 2 bulb.set_power.reset_mock() bulb.set_color.reset_mock() await hass.services.async_call( LIGHT_DOMAIN, "turn_off", { ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 5, }, blocking=True, ) assert bulb.set_power.calls[0][0][0] is False call_dict = bulb.set_power.calls[0][1] call_dict.pop("callb") assert call_dict == {"duration": 5000} bulb.set_power.reset_mock() bulb.set_color.reset_mock() async def test_infrared_color_bulb(hass: HomeAssistant) -> None: """Test setting infrared with a color bulb.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb_new_firmware() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" attributes = state.attributes assert attributes[ATTR_BRIGHTNESS] == 125 assert attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP await hass.services.async_call( LIGHT_DOMAIN, "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) assert bulb.set_power.calls[0][0][0] is False bulb.set_power.reset_mock() await hass.services.async_call( DOMAIN, "set_state", { ATTR_INFRARED: 100, ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100, }, blocking=True, ) assert bulb.set_infrared.calls[0][0][0] == 25700 async def test_color_bulb_is_actually_off(hass: HomeAssistant) -> None: """Test setting a color when we think a bulb is on but its actually off.""" already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb_new_firmware() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "on" class MockLifxCommandActuallyOff: """Mock a lifx command that will update our power level state.""" def __init__(self, bulb, **kwargs): """Init command.""" self.bulb = bulb self.calls = [] def __call__(self, *args, **kwargs): """Call command.""" bulb.power_level = 0 if callb := kwargs.get("callb"): callb(self.bulb, MockMessage()) self.calls.append([args, kwargs]) bulb.set_color = MockLifxCommandActuallyOff(bulb) await hass.services.async_call( LIGHT_DOMAIN, "turn_on", { ATTR_RGB_COLOR: (100, 100, 100), ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100, }, blocking=True, ) assert bulb.set_color.calls[0][0][0] == [0, 0, 25700, 3500] assert len(bulb.set_power.calls) == 1 async def test_clean_bulb(hass: HomeAssistant) -> None: """Test setting HEV cycle state on Clean bulbs.""" config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) config_entry.add_to_hass(hass) bulb = _mocked_clean_bulb() bulb.power_level = 0 bulb.hev_cycle = {"duration": 7200, "remaining": 0, "last_power": False} with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "off" await hass.services.async_call( DOMAIN, "set_hev_cycle_state", {ATTR_ENTITY_ID: entity_id, ATTR_POWER: True}, blocking=True, ) call_dict = bulb.set_hev_cycle.calls[0][1] call_dict.pop("callb") assert call_dict == {"duration": 0, "enable": True} bulb.set_hev_cycle.reset_mock() async def test_set_hev_cycle_state_fails_for_color_bulb(hass: HomeAssistant) -> None: """Test that set_hev_cycle_state fails for a non-Clean bulb.""" config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() bulb.power_level = 0 with _patch_discovery(device=bulb), _patch_config_flow_try_connect( device=bulb ), _patch_device(device=bulb): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() entity_id = "light.my_bulb" state = hass.states.get(entity_id) assert state.state == "off" with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, "set_hev_cycle_state", {ATTR_ENTITY_ID: entity_id, ATTR_POWER: True}, blocking=True, )