diff --git a/homeassistant/components/reolink/light.py b/homeassistant/components/reolink/light.py index 1e2c6d49528f..a5826e9bb8c0 100644 --- a/homeassistant/components/reolink/light.py +++ b/homeassistant/components/reolink/light.py @@ -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 ) diff --git a/tests/components/reolink/conftest.py b/tests/components/reolink/conftest.py index f8134a515e0c..d3658067940b 100644 --- a/tests/components/reolink/conftest.py +++ b/tests/components/reolink/conftest.py @@ -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" diff --git a/tests/components/reolink/test_light.py b/tests/components/reolink/test_light.py index 80a0a7abeab6..a9c2d8cc1bfa 100644 --- a/tests/components/reolink/test_light.py +++ b/tests/components/reolink/test_light.py @@ -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(