mirror of
https://github.com/home-assistant/core.git
synced 2026-09-05 10:05:52 -05:00
Add color temperature support to Reolink light entity (#152546)
This commit is contained in:
@@ -7,9 +7,11 @@ from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from reolink_aio.api import Host
|
||||
from reolink_aio.const import MAX_COLOR_TEMP, MIN_COLOR_TEMP
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
LightEntityDescription,
|
||||
@@ -37,8 +39,10 @@ class ReolinkLightEntityDescription(
|
||||
"""A class that describes light entities."""
|
||||
|
||||
get_brightness_fn: Callable[[Host, int], int | None] | None = None
|
||||
get_color_temp_fn: Callable[[Host, int], int | None] | None = None
|
||||
is_on_fn: Callable[[Host, int], bool]
|
||||
set_brightness_fn: Callable[[Host, int, int], Any] | None = None
|
||||
set_color_temp_fn: Callable[[Host, int, int], Any] | None = None
|
||||
turn_on_off_fn: Callable[[Host, int, bool], Any]
|
||||
|
||||
|
||||
@@ -64,6 +68,10 @@ LIGHT_ENTITIES = (
|
||||
turn_on_off_fn=lambda api, ch, value: api.set_whiteled(ch, state=value),
|
||||
get_brightness_fn=lambda api, ch: api.whiteled_brightness(ch),
|
||||
set_brightness_fn=lambda api, ch, value: api.set_whiteled(ch, brightness=value),
|
||||
get_color_temp_fn=lambda api, ch: api.whiteled_color_temperature(ch),
|
||||
set_color_temp_fn=lambda api, ch, value: (
|
||||
api.baichuan.set_floodlight(ch, color_temp=value)
|
||||
),
|
||||
),
|
||||
ReolinkLightEntityDescription(
|
||||
key="status_led",
|
||||
@@ -127,12 +135,20 @@ class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity):
|
||||
self.entity_description = entity_description
|
||||
super().__init__(reolink_data, channel)
|
||||
|
||||
if entity_description.set_brightness_fn is None:
|
||||
self._attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
self._attr_color_mode = ColorMode.ONOFF
|
||||
else:
|
||||
if (
|
||||
entity_description.set_color_temp_fn is not None
|
||||
and self._host.api.supported(self._channel, "color_temp")
|
||||
):
|
||||
self._attr_supported_color_modes = {ColorMode.COLOR_TEMP}
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
self._attr_min_color_temp_kelvin = MIN_COLOR_TEMP
|
||||
self._attr_max_color_temp_kelvin = MAX_COLOR_TEMP
|
||||
elif entity_description.set_brightness_fn is not None:
|
||||
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||
self._attr_color_mode = ColorMode.BRIGHTNESS
|
||||
else:
|
||||
self._attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
self._attr_color_mode = ColorMode.ONOFF
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
@@ -152,6 +168,13 @@ class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity):
|
||||
|
||||
return round(255 * bright_pct / 100.0)
|
||||
|
||||
@property
|
||||
def color_temp_kelvin(self) -> int | None:
|
||||
"""Return the color temperature of this light in kelvin."""
|
||||
assert self.entity_description.get_color_temp_fn is not None
|
||||
|
||||
return self.entity_description.get_color_temp_fn(self._host.api, self._channel)
|
||||
|
||||
@raise_translated_error
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn light off."""
|
||||
@@ -171,6 +194,13 @@ class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity):
|
||||
self._host.api, self._channel, brightness_pct
|
||||
)
|
||||
|
||||
if (
|
||||
color_temp := kwargs.get(ATTR_COLOR_TEMP_KELVIN)
|
||||
) is not None and self.entity_description.set_color_temp_fn is not None:
|
||||
await self.entity_description.set_color_temp_fn(
|
||||
self._host.api, self._channel, color_temp
|
||||
)
|
||||
|
||||
await self.entity_description.turn_on_off_fn(
|
||||
self._host.api, self._channel, True
|
||||
)
|
||||
|
||||
@@ -166,6 +166,7 @@ def _init_host_mock(host_mock: MagicMock) -> None:
|
||||
host_mock.baichuan.get_privacy_mode = AsyncMock()
|
||||
host_mock.baichuan.set_privacy_mode = AsyncMock()
|
||||
host_mock.baichuan.set_scene = AsyncMock()
|
||||
host_mock.baichuan.set_floodlight = AsyncMock()
|
||||
host_mock.baichuan.mac_address.return_value = TEST_MAC_CAM
|
||||
host_mock.baichuan.privacy_mode.return_value = False
|
||||
host_mock.baichuan.day_night_state.return_value = "day"
|
||||
|
||||
@@ -5,7 +5,11 @@ from unittest.mock import MagicMock, call, patch
|
||||
import pytest
|
||||
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
DOMAIN as LIGHT_DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
@@ -23,10 +27,10 @@ from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("whiteled_brightness", "expected_brightness"),
|
||||
("whiteled_brightness", "expected_brightness", "color_temp"),
|
||||
[
|
||||
(100, 255),
|
||||
(None, None),
|
||||
(100, 255, 3000),
|
||||
(None, None, None),
|
||||
],
|
||||
)
|
||||
async def test_light_state(
|
||||
@@ -35,10 +39,19 @@ async def test_light_state(
|
||||
reolink_host: MagicMock,
|
||||
whiteled_brightness: int | None,
|
||||
expected_brightness: int | None,
|
||||
color_temp: int | None,
|
||||
) -> None:
|
||||
"""Test light entity state with floodlight."""
|
||||
|
||||
def mock_supported(ch, capability):
|
||||
if capability == "color_temp":
|
||||
return color_temp is not None
|
||||
return True
|
||||
|
||||
reolink_host.supported = mock_supported
|
||||
reolink_host.whiteled_state.return_value = True
|
||||
reolink_host.whiteled_brightness.return_value = whiteled_brightness
|
||||
reolink_host.whiteled_color_temperature.return_value = color_temp
|
||||
|
||||
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.LIGHT]):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
@@ -50,6 +63,8 @@ async def test_light_state(
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes["brightness"] == expected_brightness
|
||||
if color_temp is not None:
|
||||
assert state.attributes["color_temp_kelvin"] == color_temp
|
||||
|
||||
|
||||
async def test_light_turn_off(
|
||||
@@ -58,6 +73,8 @@ async def test_light_turn_off(
|
||||
reolink_host: MagicMock,
|
||||
) -> None:
|
||||
"""Test light turn off service."""
|
||||
reolink_host.whiteled_color_temperature.return_value = 3000
|
||||
|
||||
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.LIGHT]):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
@@ -89,6 +106,8 @@ async def test_light_turn_on(
|
||||
reolink_host: MagicMock,
|
||||
) -> None:
|
||||
"""Test light turn on service."""
|
||||
reolink_host.whiteled_color_temperature.return_value = 3000
|
||||
|
||||
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.LIGHT]):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
@@ -99,12 +118,13 @@ async def test_light_turn_on(
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 51},
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 51, ATTR_COLOR_TEMP_KELVIN: 4000},
|
||||
blocking=True,
|
||||
)
|
||||
reolink_host.set_whiteled.assert_has_calls(
|
||||
[call(0, brightness=20), call(0, state=True)]
|
||||
)
|
||||
reolink_host.baichuan.set_floodlight.assert_called_with(0, color_temp=4000)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
Reference in New Issue
Block a user