Implement color_mode support for kulersky (#52080)
parent
f1b40b683d
commit
8a00c3a2f5
|
@ -8,11 +8,8 @@ import pykulersky
|
|||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_WHITE_VALUE,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
ATTR_RGBW_COLOR,
|
||||
COLOR_MODE_RGBW,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -20,14 +17,11 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORT_KULERSKY = SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE
|
||||
|
||||
DISCOVERY_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
|
||||
|
@ -71,10 +65,9 @@ class KulerskyLight(LightEntity):
|
|||
def __init__(self, light: pykulersky.Light) -> None:
|
||||
"""Initialize a Kuler Sky light."""
|
||||
self._light = light
|
||||
self._hs_color = None
|
||||
self._brightness = None
|
||||
self._white_value = None
|
||||
self._available = None
|
||||
self._attr_supported_color_modes = {COLOR_MODE_RGBW}
|
||||
self._attr_color_mode = COLOR_MODE_RGBW
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Run when entity about to be added to hass."""
|
||||
|
@ -112,30 +105,10 @@ class KulerskyLight(LightEntity):
|
|||
"manufacturer": "Brightech",
|
||||
}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_KULERSKY
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of the light."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the hs color."""
|
||||
return self._hs_color
|
||||
|
||||
@property
|
||||
def white_value(self):
|
||||
"""Return the white value of this light between 0..255."""
|
||||
return self._white_value
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if light is on."""
|
||||
return self._brightness > 0 or self._white_value > 0
|
||||
return self.brightness > 0
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
@ -144,24 +117,21 @@ class KulerskyLight(LightEntity):
|
|||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Instruct the light to turn on."""
|
||||
default_hs = (0, 0) if self._hs_color is None else self._hs_color
|
||||
hue_sat = kwargs.get(ATTR_HS_COLOR, default_hs)
|
||||
default_rgbw = (255,) * 4 if self.rgbw_color is None else self.rgbw_color
|
||||
rgbw = kwargs.get(ATTR_RGBW_COLOR, default_rgbw)
|
||||
|
||||
default_brightness = 0 if self._brightness is None else self._brightness
|
||||
default_brightness = 0 if self.brightness is None else self.brightness
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS, default_brightness)
|
||||
|
||||
default_white_value = 255 if self._white_value is None else self._white_value
|
||||
white_value = kwargs.get(ATTR_WHITE_VALUE, default_white_value)
|
||||
|
||||
if brightness == 0 and white_value == 0 and not kwargs:
|
||||
if brightness == 0 and not kwargs:
|
||||
# If the light would be off, and no additional parameters were
|
||||
# passed, just turn the light on full brightness.
|
||||
brightness = 255
|
||||
white_value = 255
|
||||
rgbw = (255,) * 4
|
||||
|
||||
rgb = color_util.color_hsv_to_RGB(*hue_sat, brightness / 255 * 100)
|
||||
rgbw_scaled = [round(x * brightness / 255) for x in rgbw]
|
||||
|
||||
await self._light.set_color(*rgb, white_value)
|
||||
await self._light.set_color(*rgbw_scaled)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Instruct the light to turn off."""
|
||||
|
@ -173,7 +143,7 @@ class KulerskyLight(LightEntity):
|
|||
if not self._available:
|
||||
await self._light.connect()
|
||||
# pylint: disable=invalid-name
|
||||
r, g, b, w = await self._light.get_color()
|
||||
rgbw = await self._light.get_color()
|
||||
except pykulersky.PykulerskyException as exc:
|
||||
if self._available:
|
||||
_LOGGER.warning("Unable to connect to %s: %s", self._light.address, exc)
|
||||
|
@ -183,7 +153,10 @@ class KulerskyLight(LightEntity):
|
|||
_LOGGER.info("Reconnected to %s", self._light.address)
|
||||
|
||||
self._available = True
|
||||
hsv = color_util.color_RGB_to_hsv(r, g, b)
|
||||
self._hs_color = hsv[:2]
|
||||
self._brightness = int(round((hsv[2] / 100) * 255))
|
||||
self._white_value = w
|
||||
brightness = max(rgbw)
|
||||
if not brightness:
|
||||
rgbw_normalized = [0, 0, 0, 0]
|
||||
else:
|
||||
rgbw_normalized = [round(x * 255 / brightness) for x in rgbw]
|
||||
self._attr_brightness = brightness
|
||||
self._attr_rgbw_color = tuple(rgbw_normalized)
|
||||
|
|
|
@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch
|
|||
|
||||
import pykulersky
|
||||
import pytest
|
||||
from pytest import approx
|
||||
|
||||
from homeassistant import setup
|
||||
from homeassistant.components.kulersky.const import (
|
||||
|
@ -17,14 +18,9 @@ from homeassistant.components.light import (
|
|||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_WHITE_VALUE,
|
||||
ATTR_XY_COLOR,
|
||||
COLOR_MODE_HS,
|
||||
COLOR_MODE_RGBW,
|
||||
SCAN_INTERVAL,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
|
@ -72,12 +68,10 @@ async def test_init(hass, mock_light):
|
|||
"""Test platform setup."""
|
||||
state = hass.states.get("light.bedroom")
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes == {
|
||||
assert dict(state.attributes) == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_WHITE_VALUE,
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: 0,
|
||||
}
|
||||
|
||||
with patch.object(hass.loop, "stop"):
|
||||
|
@ -129,7 +123,7 @@ async def test_light_turn_on(hass, mock_light):
|
|||
await hass.async_block_till_done()
|
||||
mock_light.set_color.assert_called_with(255, 255, 255, 255)
|
||||
|
||||
mock_light.get_color.return_value = (50, 50, 50, 255)
|
||||
mock_light.get_color.return_value = (50, 50, 50, 50)
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
|
@ -137,9 +131,33 @@ async def test_light_turn_on(hass, mock_light):
|
|||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_light.set_color.assert_called_with(50, 50, 50, 255)
|
||||
mock_light.set_color.assert_called_with(50, 50, 50, 50)
|
||||
|
||||
mock_light.get_color.return_value = (50, 45, 25, 255)
|
||||
mock_light.get_color.return_value = (50, 25, 13, 6)
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{ATTR_ENTITY_ID: "light.bedroom", ATTR_RGBW_COLOR: (255, 128, 64, 32)},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_light.set_color.assert_called_with(50, 25, 13, 6)
|
||||
|
||||
# RGB color is converted to RGBW by assigning the white component to the white
|
||||
# channel, see color_rgb_to_rgbw
|
||||
mock_light.get_color.return_value = (0, 17, 50, 17)
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{ATTR_ENTITY_ID: "light.bedroom", ATTR_RGB_COLOR: (64, 128, 255)},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_light.set_color.assert_called_with(0, 17, 50, 17)
|
||||
|
||||
# HS color is converted to RGBW by assigning the white component to the white
|
||||
# channel, see color_rgb_to_rgbw
|
||||
mock_light.get_color.return_value = (50, 41, 0, 50)
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
|
@ -147,18 +165,7 @@ async def test_light_turn_on(hass, mock_light):
|
|||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_light.set_color.assert_called_with(50, 45, 25, 255)
|
||||
|
||||
mock_light.get_color.return_value = (220, 201, 110, 180)
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{ATTR_ENTITY_ID: "light.bedroom", ATTR_WHITE_VALUE: 180},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_light.set_color.assert_called_with(50, 45, 25, 180)
|
||||
mock_light.set_color.assert_called_with(50, 41, 0, 50)
|
||||
|
||||
|
||||
async def test_light_turn_off(hass, mock_light):
|
||||
|
@ -180,12 +187,10 @@ async def test_light_update(hass, mock_light):
|
|||
|
||||
state = hass.states.get("light.bedroom")
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes == {
|
||||
assert dict(state.attributes) == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_WHITE_VALUE,
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: 0,
|
||||
}
|
||||
|
||||
# Test an exception during discovery
|
||||
|
@ -196,12 +201,50 @@ async def test_light_update(hass, mock_light):
|
|||
|
||||
state = hass.states.get("light.bedroom")
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert state.attributes == {
|
||||
assert dict(state.attributes) == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_WHITE_VALUE,
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: 0,
|
||||
}
|
||||
|
||||
mock_light.get_color.side_effect = None
|
||||
mock_light.get_color.return_value = (80, 160, 255, 0)
|
||||
utcnow = utcnow + SCAN_INTERVAL
|
||||
async_fire_time_changed(hass, utcnow)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.bedroom")
|
||||
assert state.state == STATE_ON
|
||||
assert dict(state.attributes) == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: 0,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBW,
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_HS_COLOR: (approx(212.571), approx(68.627)),
|
||||
ATTR_RGB_COLOR: (80, 160, 255),
|
||||
ATTR_RGBW_COLOR: (80, 160, 255, 0),
|
||||
ATTR_XY_COLOR: (approx(0.17), approx(0.193)),
|
||||
}
|
||||
|
||||
mock_light.get_color.side_effect = None
|
||||
mock_light.get_color.return_value = (80, 160, 200, 255)
|
||||
utcnow = utcnow + SCAN_INTERVAL
|
||||
async_fire_time_changed(hass, utcnow)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.bedroom")
|
||||
assert state.state == STATE_ON
|
||||
assert dict(state.attributes) == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: 0,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBW,
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_HS_COLOR: (approx(199.701), approx(26.275)),
|
||||
ATTR_RGB_COLOR: (188, 233, 255),
|
||||
ATTR_RGBW_COLOR: (80, 160, 200, 255),
|
||||
ATTR_XY_COLOR: (approx(0.259), approx(0.306)),
|
||||
}
|
||||
|
||||
mock_light.get_color.side_effect = None
|
||||
|
@ -212,17 +255,14 @@ async def test_light_update(hass, mock_light):
|
|||
|
||||
state = hass.states.get("light.bedroom")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes == {
|
||||
assert dict(state.attributes) == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_WHITE_VALUE,
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: 0,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBW,
|
||||
ATTR_BRIGHTNESS: 200,
|
||||
ATTR_HS_COLOR: (200, 60),
|
||||
ATTR_RGB_COLOR: (102, 203, 255),
|
||||
ATTR_RGBW_COLOR: (102, 203, 255, 240),
|
||||
ATTR_WHITE_VALUE: 240,
|
||||
ATTR_XY_COLOR: (0.184, 0.261),
|
||||
ATTR_BRIGHTNESS: 240,
|
||||
ATTR_HS_COLOR: (approx(200.0), approx(27.059)),
|
||||
ATTR_RGB_COLOR: (186, 232, 255),
|
||||
ATTR_RGBW_COLOR: (85, 170, 212, 255),
|
||||
ATTR_XY_COLOR: (approx(0.257), approx(0.305)),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue