mirror of https://github.com/home-assistant/core
2017 lines
63 KiB
Python
2017 lines
63 KiB
Python
"""Tests for the lifx integration light platform."""
|
|
|
|
from datetime import timedelta
|
|
from typing import Any
|
|
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_CLOUD_SATURATION_MAX,
|
|
ATTR_CLOUD_SATURATION_MIN,
|
|
ATTR_DIRECTION,
|
|
ATTR_PALETTE,
|
|
ATTR_SATURATION_MAX,
|
|
ATTR_SATURATION_MIN,
|
|
ATTR_SKY_TYPE,
|
|
ATTR_SPEED,
|
|
ATTR_THEME,
|
|
SERVICE_EFFECT_COLORLOOP,
|
|
SERVICE_EFFECT_MORPH,
|
|
SERVICE_EFFECT_MOVE,
|
|
SERVICE_EFFECT_SKY,
|
|
)
|
|
from homeassistant.components.light import (
|
|
ATTR_BRIGHTNESS,
|
|
ATTR_BRIGHTNESS_PCT,
|
|
ATTR_COLOR_MODE,
|
|
ATTR_COLOR_NAME,
|
|
ATTR_COLOR_TEMP,
|
|
ATTR_COLOR_TEMP_KELVIN,
|
|
ATTR_EFFECT,
|
|
ATTR_HS_COLOR,
|
|
ATTR_KELVIN,
|
|
ATTR_RGB_COLOR,
|
|
ATTR_SUPPORTED_COLOR_MODES,
|
|
ATTR_TRANSITION,
|
|
ATTR_XY_COLOR,
|
|
DOMAIN as LIGHT_DOMAIN,
|
|
SERVICE_TURN_ON,
|
|
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_ceiling,
|
|
_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,
|
|
device_registry: dr.DeviceRegistry,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> 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"
|
|
assert entity_registry.async_get(entity_id).unique_id == SERIAL
|
|
|
|
device = device_registry.async_get_device(
|
|
connections={(dr.CONNECTION_NETWORK_MAC, SERIAL)}
|
|
)
|
|
assert device.identifiers == {(DOMAIN, SERIAL)}
|
|
|
|
|
|
async def test_light_unique_id_new_firmware(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> 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"
|
|
assert entity_registry.async_get(entity_id).unique_id == SERIAL
|
|
device = device_registry.async_get_device(
|
|
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,
|
|
)
|
|
assert len(bulb.set_color_zones.calls) == 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,
|
|
)
|
|
assert len(bulb.set_color_zones.calls) == 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[1][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) == 1
|
|
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, msg_seq_num=0, msg_color=[0, 0, 65535, 3500] * 3, msg_index=0, msg_count=3
|
|
)
|
|
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,
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_discovery")
|
|
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"
|
|
|
|
# FLAME effect test
|
|
await hass.services.async_call(
|
|
LIGHT_DOMAIN,
|
|
"turn_on",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_EFFECT: "effect_flame"},
|
|
blocking=True,
|
|
)
|
|
await hass.async_block_till_done()
|
|
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": [],
|
|
"sky_type": None,
|
|
"cloud_saturation_min": None,
|
|
"cloud_saturation_max": None,
|
|
}
|
|
bulb.get_tile_effect.reset_mock()
|
|
bulb.set_tile_effect.reset_mock()
|
|
bulb.set_power.reset_mock()
|
|
|
|
# MORPH effect tests
|
|
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(wait_background_tasks=True)
|
|
|
|
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),
|
|
],
|
|
"sky_type": None,
|
|
"cloud_saturation_min": None,
|
|
"cloud_saturation_max": None,
|
|
}
|
|
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(wait_background_tasks=True)
|
|
|
|
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),
|
|
],
|
|
"sky_type": None,
|
|
"cloud_saturation_min": None,
|
|
"cloud_saturation_max": None,
|
|
}
|
|
bulb.get_tile_effect.reset_mock()
|
|
bulb.set_tile_effect.reset_mock()
|
|
bulb.set_power.reset_mock()
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_discovery")
|
|
async def test_sky_effect(hass: HomeAssistant) -> None:
|
|
"""Test the firmware sky effect on a ceiling device."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
bulb = _mocked_ceiling()
|
|
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"
|
|
|
|
# SKY effect test
|
|
bulb.power_level = 0
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_EFFECT_SKY,
|
|
{
|
|
ATTR_ENTITY_ID: entity_id,
|
|
ATTR_PALETTE: [],
|
|
ATTR_SKY_TYPE: "Clouds",
|
|
ATTR_CLOUD_SATURATION_MAX: 180,
|
|
ATTR_CLOUD_SATURATION_MIN: 50,
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
bulb.power_level = 65535
|
|
bulb.effect = {
|
|
"effect": "SKY",
|
|
"palette": [],
|
|
"sky_type": 2,
|
|
"cloud_saturation_min": 50,
|
|
"cloud_saturation_max": 180,
|
|
}
|
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
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": 5,
|
|
"speed": 50,
|
|
"palette": [],
|
|
"sky_type": 2,
|
|
"cloud_saturation_min": 50,
|
|
"cloud_saturation_max": 180,
|
|
}
|
|
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_SKY,
|
|
{
|
|
ATTR_ENTITY_ID: entity_id,
|
|
ATTR_PALETTE: [
|
|
(200, 100, 1, 3500),
|
|
(241, 100, 1, 3500),
|
|
(189, 100, 8, 3500),
|
|
(40, 100, 100, 3500),
|
|
(40, 50, 100, 3500),
|
|
(0, 0, 100, 6500),
|
|
],
|
|
ATTR_SKY_TYPE: "Sunrise",
|
|
ATTR_CLOUD_SATURATION_MAX: 180,
|
|
ATTR_CLOUD_SATURATION_MIN: 50,
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
bulb.power_level = 65535
|
|
bulb.effect = {
|
|
"effect": "SKY",
|
|
"palette": [
|
|
(200, 100, 1, 3500),
|
|
(241, 100, 1, 3500),
|
|
(189, 100, 8, 3500),
|
|
(40, 100, 100, 3500),
|
|
(40, 50, 100, 3500),
|
|
(0, 0, 100, 6500),
|
|
],
|
|
"sky_type": 0,
|
|
"cloud_saturation_min": 50,
|
|
"cloud_saturation_max": 180,
|
|
}
|
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
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": 5,
|
|
"speed": 50,
|
|
"palette": [
|
|
(36408, 65535, 65535, 3500),
|
|
(43872, 65535, 65535, 3500),
|
|
(34406, 65535, 5243, 3500),
|
|
(7281, 65535, 65535, 3500),
|
|
(7281, 32768, 65535, 3500),
|
|
(0, 0, 65535, 6500),
|
|
],
|
|
"sky_type": 0,
|
|
"cloud_saturation_min": 50,
|
|
"cloud_saturation_max": 180,
|
|
}
|
|
bulb.get_tile_effect.reset_mock()
|
|
bulb.set_tile_effect.reset_mock()
|
|
bulb.set_power.reset_mock()
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_discovery")
|
|
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.product = 38
|
|
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,
|
|
)
|
|
await hass.async_block_till_done()
|
|
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",
|
|
ATTR_THEME: "sports",
|
|
},
|
|
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(wait_background_tasks=True)
|
|
|
|
state = hass.states.get(entity_id)
|
|
assert state.state == STATE_ON
|
|
|
|
assert len(bulb.set_power.calls) == 1
|
|
assert len(bulb.set_extended_color_zones.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,
|
|
)
|
|
await hass.async_block_till_done()
|
|
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, 237)
|
|
assert attributes[ATTR_XY_COLOR] == (0.339, 0.338)
|
|
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_PCT: 50, ATTR_SATURATION_MAX: 90},
|
|
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, ATTR_SATURATION_MIN: 90},
|
|
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()
|
|
|
|
|
|
@pytest.mark.usefixtures("mock_discovery")
|
|
async def test_config_zoned_light_strip_fails(
|
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
|
) -> None:
|
|
"""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 2nd try."""
|
|
|
|
def __init__(self, bulb, **kwargs: Any) -> None:
|
|
"""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 >= 2
|
|
else MockMessage(seq_num=0, color=[], index=0, count=0)
|
|
)
|
|
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()
|
|
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(wait_background_tasks=True)
|
|
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
|
|
|
|
|
async def test_legacy_zoned_light_strip(
|
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
|
) -> None:
|
|
"""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 MockPopulateLifxZonesCommand:
|
|
"""Mock populating the number of zones."""
|
|
|
|
def __init__(self, bulb, **kwargs: Any) -> None:
|
|
"""Init command."""
|
|
self.bulb = bulb
|
|
self.call_count = 0
|
|
|
|
def __call__(self, callb=None, *args, **kwargs):
|
|
"""Call command."""
|
|
self.call_count += 1
|
|
self.bulb.color_zones = [None] * 12
|
|
if callb:
|
|
callb(
|
|
self.bulb,
|
|
MockMessage(
|
|
seq_num=0,
|
|
index=0,
|
|
count=self.bulb.zones_count,
|
|
color=self.bulb.color_zones,
|
|
),
|
|
)
|
|
|
|
get_color_zones_mock = MockPopulateLifxZonesCommand(light_strip)
|
|
light_strip.get_color_zones = get_color_zones_mock
|
|
|
|
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()
|
|
assert entity_registry.async_get(entity_id).unique_id == SERIAL
|
|
assert hass.states.get(entity_id).state == STATE_OFF
|
|
# 1 to get the number of zones
|
|
# 2 get populate the zones
|
|
assert get_color_zones_mock.call_count == 3
|
|
|
|
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_OFF
|
|
# 2 get populate the zones
|
|
assert get_color_zones_mock.call_count == 5
|
|
|
|
|
|
async def test_white_light_fails(
|
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
|
) -> None:
|
|
"""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()
|
|
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_lifx_set_state_color(hass: HomeAssistant) -> None:
|
|
"""Test lifx.set_state works with color names and RGB."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
|
|
)
|
|
config_entry.add_to_hass(hass)
|
|
bulb = _mocked_bulb_new_firmware()
|
|
bulb.power_level = 65535
|
|
bulb.color = [32000, None, 32000, 2700]
|
|
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"
|
|
|
|
# brightness should convert from 8 to 16 bits
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
"set_state",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 255},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [32000, None, 65535, 2700]
|
|
bulb.set_color.reset_mock()
|
|
|
|
# brightness_pct should convert into 16 bit
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
"set_state",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_PCT: 90},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [32000, None, 59110, 2700]
|
|
bulb.set_color.reset_mock()
|
|
|
|
# color name should turn into hue, saturation
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
"set_state",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_COLOR_NAME: "red", ATTR_BRIGHTNESS_PCT: 100},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [0, 65535, 65535, 3500]
|
|
bulb.set_color.reset_mock()
|
|
|
|
# unknown color name should reset back to neutral white, i.e. 3500K
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
"set_state",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_COLOR_NAME: "deepblack"},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [0, 0, 32000, 3500]
|
|
bulb.set_color.reset_mock()
|
|
|
|
# RGB should convert to hue, saturation
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
"set_state",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: (0, 255, 0)},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [21845, 65535, 32000, 3500]
|
|
bulb.set_color.reset_mock()
|
|
|
|
# XY should convert to hue, saturation
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
"set_state",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_XY_COLOR: (0.34, 0.339)},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [5461, 5139, 32000, 3500]
|
|
bulb.set_color.reset_mock()
|
|
|
|
|
|
async def test_lifx_set_state_kelvin(hass: HomeAssistant) -> None:
|
|
"""Test set_state works with old and new kelvin parameter names."""
|
|
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_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 255, ATTR_KELVIN: 3500},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [32000, 0, 65535, 3500]
|
|
bulb.set_color.reset_mock()
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
"set_state",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100, ATTR_COLOR_TEMP_KELVIN: 2700},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [32000, 0, 25700, 2700]
|
|
bulb.set_color.reset_mock()
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
"set_state",
|
|
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 255, ATTR_COLOR_TEMP: 400},
|
|
blocking=True,
|
|
)
|
|
assert bulb.set_color.calls[0][0][0] == [32000, 0, 65535, 2500]
|
|
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: Any) -> None:
|
|
"""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,
|
|
)
|
|
|
|
|
|
async def test_light_strip_zones_not_populated_yet(hass: HomeAssistant) -> None:
|
|
"""Test a light strip were zones are not populated initially."""
|
|
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_zones = None
|
|
bulb.color = [65535, 65535, 65535, 65535]
|
|
bulb.get_color_zones = next(
|
|
iter(
|
|
[
|
|
MockLifxCommand(
|
|
bulb,
|
|
msg_seq_num=0,
|
|
msg_color=[0, 0, 65535, 3500] * 8,
|
|
msg_index=0,
|
|
msg_count=16,
|
|
),
|
|
MockLifxCommand(
|
|
bulb,
|
|
msg_seq_num=1,
|
|
msg_color=[0, 0, 65535, 3500] * 8,
|
|
msg_index=0,
|
|
msg_count=16,
|
|
),
|
|
MockLifxCommand(
|
|
bulb,
|
|
msg_seq_num=2,
|
|
msg_color=[0, 0, 65535, 3500] * 8,
|
|
msg_index=8,
|
|
msg_count=16,
|
|
),
|
|
]
|
|
)
|
|
)
|
|
assert bulb.get_color_zones.calls == []
|
|
|
|
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"
|
|
# Make sure we at least try to fetch the first zone
|
|
# to ensure we populate the zones from the 503 response
|
|
assert len(bulb.get_color_zones.calls) == 3
|
|
# Once to populate the number of zones
|
|
assert bulb.get_color_zones.calls[0][1]["start_index"] == 0
|
|
# Again once we know the number of zones
|
|
assert bulb.get_color_zones.calls[1][1]["start_index"] == 0
|
|
assert bulb.get_color_zones.calls[2][1]["start_index"] == 8
|
|
|
|
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, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
|
)
|
|
assert bulb.set_power.calls[0][0][0] is True
|
|
bulb.set_power.reset_mock()
|
|
|
|
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
|