2018-06-13 05:17:52 +00:00
|
|
|
"""Tradfri lights platform tests."""
|
|
|
|
from copy import deepcopy
|
2021-01-01 21:31:56 +00:00
|
|
|
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
import pytest
|
2019-09-19 18:49:47 +00:00
|
|
|
from pytradfri.device import Device
|
|
|
|
from pytradfri.device.light import Light
|
|
|
|
from pytradfri.device.light_control import LightControl
|
2018-06-13 05:17:52 +00:00
|
|
|
|
2023-02-17 15:40:46 +00:00
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
|
2022-01-14 18:40:14 +00:00
|
|
|
from .common import setup_integration
|
2018-06-13 05:17:52 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DEFAULT_TEST_FEATURES = {
|
|
|
|
"can_set_dimmer": False,
|
|
|
|
"can_set_color": False,
|
|
|
|
"can_set_temp": False,
|
|
|
|
}
|
2018-06-13 05:17:52 +00:00
|
|
|
# [
|
|
|
|
# {bulb features},
|
|
|
|
# {turn_on arguments},
|
|
|
|
# {expected result}
|
|
|
|
# ]
|
|
|
|
TURN_ON_TEST_CASES = [
|
|
|
|
# Turn On
|
2019-07-31 19:25:30 +00:00
|
|
|
[{}, {}, {"state": "on"}],
|
2018-06-13 05:17:52 +00:00
|
|
|
# Brightness > 0
|
2019-07-31 19:25:30 +00:00
|
|
|
[{"can_set_dimmer": True}, {"brightness": 100}, {"state": "on", "brightness": 100}],
|
2019-03-31 04:04:32 +00:00
|
|
|
# Brightness == 1
|
2019-07-31 19:25:30 +00:00
|
|
|
[{"can_set_dimmer": True}, {"brightness": 1}, {"brightness": 1}],
|
2018-06-13 05:17:52 +00:00
|
|
|
# Brightness > 254
|
2019-07-31 19:25:30 +00:00
|
|
|
[{"can_set_dimmer": True}, {"brightness": 1000}, {"brightness": 254}],
|
2018-06-13 05:17:52 +00:00
|
|
|
# color_temp
|
2019-07-31 19:25:30 +00:00
|
|
|
[{"can_set_temp": True}, {"color_temp": 250}, {"color_temp": 250}],
|
2018-06-13 05:17:52 +00:00
|
|
|
# color_temp < 250
|
2019-07-31 19:25:30 +00:00
|
|
|
[{"can_set_temp": True}, {"color_temp": 1}, {"color_temp": 250}],
|
2018-06-13 05:17:52 +00:00
|
|
|
# color_temp > 454
|
2019-07-31 19:25:30 +00:00
|
|
|
[{"can_set_temp": True}, {"color_temp": 1000}, {"color_temp": 454}],
|
2018-06-13 05:17:52 +00:00
|
|
|
# hs color
|
|
|
|
[
|
2019-07-31 19:25:30 +00:00
|
|
|
{"can_set_color": True},
|
|
|
|
{"hs_color": [300, 100]},
|
|
|
|
{"state": "on", "hs_color": [300, 100]},
|
2018-06-13 05:17:52 +00:00
|
|
|
],
|
|
|
|
# ct + brightness
|
|
|
|
[
|
2019-07-31 19:25:30 +00:00
|
|
|
{"can_set_dimmer": True, "can_set_temp": True},
|
|
|
|
{"color_temp": 250, "brightness": 200},
|
|
|
|
{"state": "on", "color_temp": 250, "brightness": 200},
|
2018-06-13 05:17:52 +00:00
|
|
|
],
|
|
|
|
# ct + brightness (no temp support)
|
|
|
|
[
|
2019-07-31 19:25:30 +00:00
|
|
|
{"can_set_dimmer": True, "can_set_temp": False, "can_set_color": True},
|
|
|
|
{"color_temp": 250, "brightness": 200},
|
|
|
|
{"state": "on", "hs_color": [26.807, 34.869], "brightness": 200},
|
2018-06-13 05:17:52 +00:00
|
|
|
],
|
|
|
|
# ct + brightness (no temp or color support)
|
|
|
|
[
|
2019-07-31 19:25:30 +00:00
|
|
|
{"can_set_dimmer": True, "can_set_temp": False, "can_set_color": False},
|
|
|
|
{"color_temp": 250, "brightness": 200},
|
|
|
|
{"state": "on", "brightness": 200},
|
2018-06-13 05:17:52 +00:00
|
|
|
],
|
|
|
|
# hs + brightness
|
|
|
|
[
|
2019-07-31 19:25:30 +00:00
|
|
|
{"can_set_dimmer": True, "can_set_color": True},
|
|
|
|
{"hs_color": [300, 100], "brightness": 200},
|
|
|
|
{"state": "on", "hs_color": [300, 100], "brightness": 200},
|
|
|
|
],
|
2018-06-13 05:17:52 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
# Result of transition is not tested, but data is passed to turn on service.
|
|
|
|
TRANSITION_CASES_FOR_TESTS = [None, 0, 1]
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
@pytest.fixture(autouse=True, scope="module")
|
2023-01-27 16:14:04 +00:00
|
|
|
def setup():
|
2018-06-13 05:17:52 +00:00
|
|
|
"""Set up patches for pytradfri methods."""
|
2019-07-31 19:25:30 +00:00
|
|
|
p_1 = patch(
|
|
|
|
"pytradfri.device.LightControl.raw",
|
|
|
|
new_callable=PropertyMock,
|
|
|
|
return_value=[{"mock": "mock"}],
|
|
|
|
)
|
|
|
|
p_2 = patch("pytradfri.device.LightControl.lights")
|
2018-06-13 05:17:52 +00:00
|
|
|
p_1.start()
|
|
|
|
p_2.start()
|
|
|
|
|
2023-01-27 16:14:04 +00:00
|
|
|
yield
|
2018-06-13 05:17:52 +00:00
|
|
|
|
2023-01-27 16:14:04 +00:00
|
|
|
p_1.stop()
|
|
|
|
p_2.stop()
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def generate_psk(self, code):
|
|
|
|
"""Mock psk."""
|
|
|
|
return "mock"
|
|
|
|
|
|
|
|
|
2020-09-05 21:02:32 +00:00
|
|
|
def mock_light(test_features=None, test_state=None, light_number=0):
|
2018-06-13 05:17:52 +00:00
|
|
|
"""Mock a tradfri light."""
|
2020-09-05 21:02:32 +00:00
|
|
|
if test_features is None:
|
|
|
|
test_features = {}
|
|
|
|
if test_state is None:
|
|
|
|
test_state = {}
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_light_data = Mock(**test_state)
|
2018-06-13 05:17:52 +00:00
|
|
|
|
2020-04-02 22:55:04 +00:00
|
|
|
dev_info_mock = MagicMock()
|
|
|
|
dev_info_mock.manufacturer = "manufacturer"
|
|
|
|
dev_info_mock.model_number = "model"
|
|
|
|
dev_info_mock.firmware_version = "1.2.3"
|
2020-09-05 21:02:32 +00:00
|
|
|
_mock_light = Mock(
|
|
|
|
id=f"mock-light-id-{light_number}",
|
2018-06-13 05:17:52 +00:00
|
|
|
reachable=True,
|
|
|
|
observe=Mock(),
|
2020-04-02 22:55:04 +00:00
|
|
|
device_info=dev_info_mock,
|
2020-09-06 09:54:08 +00:00
|
|
|
has_light_control=True,
|
|
|
|
has_socket_control=False,
|
|
|
|
has_blind_control=False,
|
|
|
|
has_signal_repeater_control=False,
|
2021-10-25 06:15:46 +00:00
|
|
|
has_air_purifier_control=False,
|
2018-06-13 05:17:52 +00:00
|
|
|
)
|
2020-09-05 21:02:32 +00:00
|
|
|
_mock_light.name = f"tradfri_light_{light_number}"
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
# Set supported features for the light.
|
|
|
|
features = {**DEFAULT_TEST_FEATURES, **test_features}
|
2020-09-05 21:02:32 +00:00
|
|
|
light_control = LightControl(_mock_light)
|
|
|
|
for attr, value in features.items():
|
|
|
|
setattr(light_control, attr, value)
|
2018-06-13 05:17:52 +00:00
|
|
|
# Store the initial state.
|
2020-09-05 21:02:32 +00:00
|
|
|
setattr(light_control, "lights", [mock_light_data])
|
|
|
|
_mock_light.light_control = light_control
|
|
|
|
return _mock_light
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
|
2023-02-17 15:40:46 +00:00
|
|
|
async def test_light(hass: HomeAssistant, mock_gateway, mock_api_factory) -> None:
|
2018-06-13 05:17:52 +00:00
|
|
|
"""Test that lights are correctly added."""
|
2019-07-31 19:25:30 +00:00
|
|
|
features = {"can_set_dimmer": True, "can_set_color": True, "can_set_temp": True}
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
state = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"state": True,
|
|
|
|
"dimmer": 100,
|
|
|
|
"color_temp": 250,
|
|
|
|
"hsb_xy_color": (100, 100, 100, 100, 100),
|
2018-06-13 05:17:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mock_gateway.mock_devices.append(
|
|
|
|
mock_light(test_features=features, test_state=state)
|
|
|
|
)
|
2020-09-05 21:02:32 +00:00
|
|
|
await setup_integration(hass)
|
2018-06-13 05:17:52 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
lamp_1 = hass.states.get("light.tradfri_light_0")
|
2018-06-13 05:17:52 +00:00
|
|
|
assert lamp_1 is not None
|
2019-07-31 19:25:30 +00:00
|
|
|
assert lamp_1.state == "on"
|
|
|
|
assert lamp_1.attributes["brightness"] == 100
|
|
|
|
assert lamp_1.attributes["hs_color"] == (0.549, 0.153)
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
|
2023-02-17 15:40:46 +00:00
|
|
|
async def test_light_observed(
|
|
|
|
hass: HomeAssistant, mock_gateway, mock_api_factory
|
|
|
|
) -> None:
|
2018-06-13 05:17:52 +00:00
|
|
|
"""Test that lights are correctly observed."""
|
|
|
|
light = mock_light()
|
|
|
|
mock_gateway.mock_devices.append(light)
|
2020-09-05 21:02:32 +00:00
|
|
|
await setup_integration(hass)
|
2018-06-13 05:17:52 +00:00
|
|
|
assert len(light.observe.mock_calls) > 0
|
|
|
|
|
|
|
|
|
2023-02-17 15:40:46 +00:00
|
|
|
async def test_light_available(
|
|
|
|
hass: HomeAssistant, mock_gateway, mock_api_factory
|
|
|
|
) -> None:
|
2018-06-13 05:17:52 +00:00
|
|
|
"""Test light available property."""
|
2020-09-05 21:02:32 +00:00
|
|
|
light = mock_light({"state": True}, light_number=1)
|
2018-06-13 05:17:52 +00:00
|
|
|
light.reachable = True
|
|
|
|
|
2020-09-05 21:02:32 +00:00
|
|
|
light2 = mock_light({"state": True}, light_number=2)
|
2018-06-13 05:17:52 +00:00
|
|
|
light2.reachable = False
|
|
|
|
|
|
|
|
mock_gateway.mock_devices.append(light)
|
|
|
|
mock_gateway.mock_devices.append(light2)
|
2020-09-05 21:02:32 +00:00
|
|
|
await setup_integration(hass)
|
2018-06-13 05:17:52 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.states.get("light.tradfri_light_1").state == "on"
|
2018-06-13 05:17:52 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.states.get("light.tradfri_light_2").state == "unavailable"
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
|
2020-09-05 21:02:32 +00:00
|
|
|
def create_all_turn_on_cases():
|
|
|
|
"""Create all turn on test cases."""
|
|
|
|
# Combine TURN_ON_TEST_CASES and TRANSITION_CASES_FOR_TESTS
|
|
|
|
all_turn_on_test_cases = [
|
|
|
|
["test_features", "test_data", "expected_result", "device_id"],
|
|
|
|
[],
|
|
|
|
]
|
|
|
|
index = 1
|
|
|
|
for test_case in TURN_ON_TEST_CASES:
|
|
|
|
for trans in TRANSITION_CASES_FOR_TESTS:
|
|
|
|
case = deepcopy(test_case)
|
|
|
|
if trans is not None:
|
|
|
|
case[1]["transition"] = trans
|
|
|
|
case.append(index)
|
|
|
|
index += 1
|
|
|
|
all_turn_on_test_cases[1].append(case)
|
2018-06-13 05:17:52 +00:00
|
|
|
|
2020-09-05 21:02:32 +00:00
|
|
|
return all_turn_on_test_cases
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
|
2020-09-05 21:02:32 +00:00
|
|
|
@pytest.mark.parametrize(*create_all_turn_on_cases())
|
2019-07-31 19:25:30 +00:00
|
|
|
async def test_turn_on(
|
2023-02-17 15:40:46 +00:00
|
|
|
hass: HomeAssistant,
|
2020-09-05 21:02:32 +00:00
|
|
|
mock_gateway,
|
2021-10-20 11:36:02 +00:00
|
|
|
mock_api_factory,
|
2020-09-05 21:02:32 +00:00
|
|
|
test_features,
|
|
|
|
test_data,
|
|
|
|
expected_result,
|
|
|
|
device_id,
|
2023-02-17 15:40:46 +00:00
|
|
|
) -> None:
|
2018-06-13 05:17:52 +00:00
|
|
|
"""Test turning on a light."""
|
|
|
|
# Note pytradfri style, not hass. Values not really important.
|
|
|
|
initial_state = {
|
2019-07-31 19:25:30 +00:00
|
|
|
"state": False,
|
|
|
|
"dimmer": 0,
|
|
|
|
"color_temp": 250,
|
|
|
|
"hsb_xy_color": (100, 100, 100, 100, 100),
|
2018-06-13 05:17:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Setup the gateway with a mock light.
|
2020-09-05 21:02:32 +00:00
|
|
|
light = mock_light(
|
|
|
|
test_features=test_features, test_state=initial_state, light_number=device_id
|
|
|
|
)
|
2018-06-13 05:17:52 +00:00
|
|
|
mock_gateway.mock_devices.append(light)
|
2020-09-05 21:02:32 +00:00
|
|
|
await setup_integration(hass)
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
# Use the turn_on service call to change the light state.
|
2019-07-31 19:25:30 +00:00
|
|
|
await hass.services.async_call(
|
|
|
|
"light",
|
|
|
|
"turn_on",
|
2020-09-05 21:02:32 +00:00
|
|
|
{"entity_id": f"light.tradfri_light_{device_id}", **test_data},
|
2019-07-31 19:25:30 +00:00
|
|
|
blocking=True,
|
|
|
|
)
|
2018-06-13 05:17:52 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that the light is observed.
|
|
|
|
mock_func = light.observe
|
|
|
|
assert len(mock_func.mock_calls) > 0
|
|
|
|
_, callkwargs = mock_func.call_args
|
2019-07-31 19:25:30 +00:00
|
|
|
assert "callback" in callkwargs
|
2018-06-13 05:17:52 +00:00
|
|
|
# Callback function to refresh light state.
|
2020-09-05 21:02:32 +00:00
|
|
|
callback = callkwargs["callback"]
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
responses = mock_gateway.mock_responses
|
|
|
|
# State on command data.
|
2019-07-31 19:25:30 +00:00
|
|
|
data = {"3311": [{"5850": 1}]}
|
2018-06-13 05:17:52 +00:00
|
|
|
# Add data for all sent commands.
|
2020-09-05 21:02:32 +00:00
|
|
|
for resp in responses:
|
|
|
|
data["3311"][0] = {**data["3311"][0], **resp["3311"][0]}
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
# Use the callback function to update the light state.
|
|
|
|
dev = Device(data)
|
|
|
|
light_data = Light(dev, 0)
|
|
|
|
light.light_control.lights[0] = light_data
|
2020-09-05 21:02:32 +00:00
|
|
|
callback(light)
|
2018-06-13 05:17:52 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that the state is correct.
|
2020-09-05 21:02:32 +00:00
|
|
|
states = hass.states.get(f"light.tradfri_light_{device_id}")
|
|
|
|
for result, value in expected_result.items():
|
|
|
|
if result == "state":
|
|
|
|
assert states.state == value
|
2018-06-13 05:17:52 +00:00
|
|
|
else:
|
|
|
|
# Allow some rounding error in color conversions.
|
2020-09-05 21:02:32 +00:00
|
|
|
assert states.attributes[result] == pytest.approx(value, abs=0.01)
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
|
2023-02-17 15:40:46 +00:00
|
|
|
async def test_turn_off(hass: HomeAssistant, mock_gateway, mock_api_factory) -> None:
|
2018-06-13 05:17:52 +00:00
|
|
|
"""Test turning off a light."""
|
2019-07-31 19:25:30 +00:00
|
|
|
state = {"state": True, "dimmer": 100}
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
light = mock_light(test_state=state)
|
|
|
|
mock_gateway.mock_devices.append(light)
|
2020-09-05 21:02:32 +00:00
|
|
|
await setup_integration(hass)
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
# Use the turn_off service call to change the light state.
|
2019-07-31 19:25:30 +00:00
|
|
|
await hass.services.async_call(
|
|
|
|
"light", "turn_off", {"entity_id": "light.tradfri_light_0"}, blocking=True
|
|
|
|
)
|
2018-06-13 05:17:52 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that the light is observed.
|
|
|
|
mock_func = light.observe
|
|
|
|
assert len(mock_func.mock_calls) > 0
|
|
|
|
_, callkwargs = mock_func.call_args
|
2019-07-31 19:25:30 +00:00
|
|
|
assert "callback" in callkwargs
|
2018-06-13 05:17:52 +00:00
|
|
|
# Callback function to refresh light state.
|
2020-09-05 21:02:32 +00:00
|
|
|
callback = callkwargs["callback"]
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
responses = mock_gateway.mock_responses
|
2019-07-31 19:25:30 +00:00
|
|
|
data = {"3311": [{}]}
|
2018-06-13 05:17:52 +00:00
|
|
|
# Add data for all sent commands.
|
2020-09-05 21:02:32 +00:00
|
|
|
for resp in responses:
|
|
|
|
data["3311"][0] = {**data["3311"][0], **resp["3311"][0]}
|
2018-06-13 05:17:52 +00:00
|
|
|
|
|
|
|
# Use the callback function to update the light state.
|
|
|
|
dev = Device(data)
|
|
|
|
light_data = Light(dev, 0)
|
|
|
|
light.light_control.lights[0] = light_data
|
2020-09-05 21:02:32 +00:00
|
|
|
callback(light)
|
2018-06-13 05:17:52 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that the state is correct.
|
2019-07-31 19:25:30 +00:00
|
|
|
states = hass.states.get("light.tradfri_light_0")
|
|
|
|
assert states.state == "off"
|