Fix loss of ability to control white channel in HomeKit on RGB&W lights (#65864)
* Fix loss of ability to control white channel in HomeKit on RGB&W lights - Fix white channel missing from RGB/W lights - Fix temp missing from RGB/CW lights - Fixes #65529 * cover the missing case * bright fix * force brightness notify on color mode change as wellpull/65956/head
parent
b1dcf7e0d8
commit
41f602c3df
|
@ -1,4 +1,6 @@
|
|||
"""Class to hold all light accessories."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import math
|
||||
|
||||
|
@ -12,12 +14,13 @@ from homeassistant.components.light import (
|
|||
ATTR_HS_COLOR,
|
||||
ATTR_MAX_MIREDS,
|
||||
ATTR_MIN_MIREDS,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_WHITE,
|
||||
COLOR_MODE_RGBW,
|
||||
COLOR_MODE_RGBWW,
|
||||
COLOR_MODE_WHITE,
|
||||
DOMAIN,
|
||||
brightness_supported,
|
||||
color_supported,
|
||||
|
@ -32,9 +35,9 @@ from homeassistant.const import (
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.util.color import (
|
||||
color_hsv_to_RGB,
|
||||
color_temperature_mired_to_kelvin,
|
||||
color_temperature_to_hs,
|
||||
color_temperature_to_rgbww,
|
||||
)
|
||||
|
||||
from .accessories import TYPES, HomeAccessory
|
||||
|
@ -51,12 +54,13 @@ from .const import (
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
RGB_COLOR = "rgb_color"
|
||||
|
||||
CHANGE_COALESCE_TIME_WINDOW = 0.01
|
||||
|
||||
DEFAULT_MIN_MIREDS = 153
|
||||
DEFAULT_MAX_MIREDS = 500
|
||||
|
||||
COLOR_MODES_WITH_WHITES = {COLOR_MODE_RGBW, COLOR_MODE_RGBWW}
|
||||
COLOR_MODES_WITH_WHITES = {COLOR_MODE_RGBW, COLOR_MODE_RGBWW, COLOR_MODE_WHITE}
|
||||
|
||||
|
||||
@TYPES.register("Light")
|
||||
|
@ -79,8 +83,12 @@ class Light(HomeAccessory):
|
|||
self.color_modes = color_modes = (
|
||||
attributes.get(ATTR_SUPPORTED_COLOR_MODES) or []
|
||||
)
|
||||
self._previous_color_mode = attributes.get(ATTR_COLOR_MODE)
|
||||
self.color_supported = color_supported(color_modes)
|
||||
self.color_temp_supported = color_temp_supported(color_modes)
|
||||
self.rgbw_supported = COLOR_MODE_RGBW in color_modes
|
||||
self.rgbww_supported = COLOR_MODE_RGBWW in color_modes
|
||||
self.white_supported = COLOR_MODE_WHITE in color_modes
|
||||
self.brightness_supported = brightness_supported(color_modes)
|
||||
|
||||
if self.brightness_supported:
|
||||
|
@ -89,7 +97,9 @@ class Light(HomeAccessory):
|
|||
if self.color_supported:
|
||||
self.chars.extend([CHAR_HUE, CHAR_SATURATION])
|
||||
|
||||
if self.color_temp_supported:
|
||||
if self.color_temp_supported or COLOR_MODES_WITH_WHITES.intersection(
|
||||
self.color_modes
|
||||
):
|
||||
self.chars.append(CHAR_COLOR_TEMPERATURE)
|
||||
|
||||
serv_light = self.add_preload_service(SERV_LIGHTBULB, self.chars)
|
||||
|
@ -101,13 +111,22 @@ class Light(HomeAccessory):
|
|||
# to set to the correct initial value.
|
||||
self.char_brightness = serv_light.configure_char(CHAR_BRIGHTNESS, value=100)
|
||||
|
||||
if self.color_temp_supported:
|
||||
min_mireds = math.floor(attributes.get(ATTR_MIN_MIREDS, 153))
|
||||
max_mireds = math.ceil(attributes.get(ATTR_MAX_MIREDS, 500))
|
||||
if CHAR_COLOR_TEMPERATURE in self.chars:
|
||||
self.min_mireds = math.floor(
|
||||
attributes.get(ATTR_MIN_MIREDS, DEFAULT_MIN_MIREDS)
|
||||
)
|
||||
self.max_mireds = math.ceil(
|
||||
attributes.get(ATTR_MAX_MIREDS, DEFAULT_MAX_MIREDS)
|
||||
)
|
||||
if not self.color_temp_supported and not self.rgbww_supported:
|
||||
self.max_mireds = self.min_mireds
|
||||
self.char_color_temp = serv_light.configure_char(
|
||||
CHAR_COLOR_TEMPERATURE,
|
||||
value=min_mireds,
|
||||
properties={PROP_MIN_VALUE: min_mireds, PROP_MAX_VALUE: max_mireds},
|
||||
value=self.min_mireds,
|
||||
properties={
|
||||
PROP_MIN_VALUE: self.min_mireds,
|
||||
PROP_MAX_VALUE: self.max_mireds,
|
||||
},
|
||||
)
|
||||
|
||||
if self.color_supported:
|
||||
|
@ -165,33 +184,32 @@ class Light(HomeAccessory):
|
|||
)
|
||||
return
|
||||
|
||||
# Handle white channels
|
||||
if CHAR_COLOR_TEMPERATURE in char_values:
|
||||
params[ATTR_COLOR_TEMP] = char_values[CHAR_COLOR_TEMPERATURE]
|
||||
events.append(f"color temperature at {params[ATTR_COLOR_TEMP]}")
|
||||
temp = char_values[CHAR_COLOR_TEMPERATURE]
|
||||
events.append(f"color temperature at {temp}")
|
||||
bright_val = round(
|
||||
((brightness_pct or self.char_brightness.value) * 255) / 100
|
||||
)
|
||||
if self.color_temp_supported:
|
||||
params[ATTR_COLOR_TEMP] = temp
|
||||
elif self.rgbww_supported:
|
||||
params[ATTR_RGBWW_COLOR] = color_temperature_to_rgbww(
|
||||
temp, bright_val, self.min_mireds, self.max_mireds
|
||||
)
|
||||
elif self.rgbw_supported:
|
||||
params[ATTR_RGBW_COLOR] = (*(0,) * 3, bright_val)
|
||||
elif self.white_supported:
|
||||
params[ATTR_WHITE] = bright_val
|
||||
|
||||
elif (
|
||||
CHAR_HUE in char_values
|
||||
or CHAR_SATURATION in char_values
|
||||
# If we are adjusting brightness we need to send the full RGBW/RGBWW values
|
||||
# since HomeKit does not support RGBW/RGBWW
|
||||
or brightness_pct
|
||||
and COLOR_MODES_WITH_WHITES.intersection(self.color_modes)
|
||||
):
|
||||
elif CHAR_HUE in char_values or CHAR_SATURATION in char_values:
|
||||
hue_sat = (
|
||||
char_values.get(CHAR_HUE, self.char_hue.value),
|
||||
char_values.get(CHAR_SATURATION, self.char_saturation.value),
|
||||
)
|
||||
_LOGGER.debug("%s: Set hs_color to %s", self.entity_id, hue_sat)
|
||||
events.append(f"set color at {hue_sat}")
|
||||
# HomeKit doesn't support RGBW/RGBWW so we need to remove any white values
|
||||
if COLOR_MODE_RGBWW in self.color_modes:
|
||||
val = brightness_pct or self.char_brightness.value
|
||||
params[ATTR_RGBWW_COLOR] = (*color_hsv_to_RGB(*hue_sat, val), 0, 0)
|
||||
elif COLOR_MODE_RGBW in self.color_modes:
|
||||
val = brightness_pct or self.char_brightness.value
|
||||
params[ATTR_RGBW_COLOR] = (*color_hsv_to_RGB(*hue_sat, val), 0)
|
||||
else:
|
||||
params[ATTR_HS_COLOR] = hue_sat
|
||||
params[ATTR_HS_COLOR] = hue_sat
|
||||
|
||||
if (
|
||||
brightness_pct
|
||||
|
@ -200,6 +218,9 @@ class Light(HomeAccessory):
|
|||
):
|
||||
params[ATTR_BRIGHTNESS_PCT] = brightness_pct
|
||||
|
||||
_LOGGER.debug(
|
||||
"Calling light service with params: %s -> %s", char_values, params
|
||||
)
|
||||
self.async_call_service(DOMAIN, service, params, ", ".join(events))
|
||||
|
||||
@callback
|
||||
|
@ -210,52 +231,59 @@ class Light(HomeAccessory):
|
|||
attributes = new_state.attributes
|
||||
color_mode = attributes.get(ATTR_COLOR_MODE)
|
||||
self.char_on.set_value(int(state == STATE_ON))
|
||||
color_mode_changed = self._previous_color_mode != color_mode
|
||||
self._previous_color_mode = color_mode
|
||||
|
||||
# Handle Brightness
|
||||
if self.brightness_supported:
|
||||
if (
|
||||
color_mode
|
||||
and COLOR_MODES_WITH_WHITES.intersection({color_mode})
|
||||
and (rgb_color := attributes.get(ATTR_RGB_COLOR))
|
||||
):
|
||||
# HomeKit doesn't support RGBW/RGBWW so we need to
|
||||
# give it the color brightness only
|
||||
brightness = max(rgb_color)
|
||||
else:
|
||||
brightness = attributes.get(ATTR_BRIGHTNESS)
|
||||
if isinstance(brightness, (int, float)):
|
||||
brightness = round(brightness / 255 * 100, 0)
|
||||
# The homeassistant component might report its brightness as 0 but is
|
||||
# not off. But 0 is a special value in homekit. When you turn on a
|
||||
# homekit accessory it will try to restore the last brightness state
|
||||
# which will be the last value saved by char_brightness.set_value.
|
||||
# But if it is set to 0, HomeKit will update the brightness to 100 as
|
||||
# it thinks 0 is off.
|
||||
#
|
||||
# Therefore, if the the brightness is 0 and the device is still on,
|
||||
# the brightness is mapped to 1 otherwise the update is ignored in
|
||||
# order to avoid this incorrect behavior.
|
||||
if brightness == 0 and state == STATE_ON:
|
||||
brightness = 1
|
||||
self.char_brightness.set_value(brightness)
|
||||
if (
|
||||
self.brightness_supported
|
||||
and (brightness := attributes.get(ATTR_BRIGHTNESS)) is not None
|
||||
and isinstance(brightness, (int, float))
|
||||
):
|
||||
brightness = round(brightness / 255 * 100, 0)
|
||||
# The homeassistant component might report its brightness as 0 but is
|
||||
# not off. But 0 is a special value in homekit. When you turn on a
|
||||
# homekit accessory it will try to restore the last brightness state
|
||||
# which will be the last value saved by char_brightness.set_value.
|
||||
# But if it is set to 0, HomeKit will update the brightness to 100 as
|
||||
# it thinks 0 is off.
|
||||
#
|
||||
# Therefore, if the the brightness is 0 and the device is still on,
|
||||
# the brightness is mapped to 1 otherwise the update is ignored in
|
||||
# order to avoid this incorrect behavior.
|
||||
if brightness == 0 and state == STATE_ON:
|
||||
brightness = 1
|
||||
self.char_brightness.set_value(brightness)
|
||||
if color_mode_changed:
|
||||
self.char_brightness.notify()
|
||||
|
||||
# Handle Color - color must always be set before color temperature
|
||||
# or the iOS UI will not display it correctly.
|
||||
if self.color_supported:
|
||||
if ATTR_COLOR_TEMP in attributes:
|
||||
if color_temp := attributes.get(ATTR_COLOR_TEMP):
|
||||
hue, saturation = color_temperature_to_hs(
|
||||
color_temperature_mired_to_kelvin(
|
||||
new_state.attributes[ATTR_COLOR_TEMP]
|
||||
)
|
||||
color_temperature_mired_to_kelvin(color_temp)
|
||||
)
|
||||
elif color_mode == COLOR_MODE_WHITE:
|
||||
hue, saturation = 0, 0
|
||||
else:
|
||||
hue, saturation = attributes.get(ATTR_HS_COLOR, (None, None))
|
||||
if isinstance(hue, (int, float)) and isinstance(saturation, (int, float)):
|
||||
self.char_hue.set_value(round(hue, 0))
|
||||
self.char_saturation.set_value(round(saturation, 0))
|
||||
if color_mode_changed:
|
||||
# If the color temp changed, be sure to force the color to update
|
||||
self.char_hue.notify()
|
||||
self.char_saturation.notify()
|
||||
|
||||
# Handle color temperature
|
||||
if self.color_temp_supported:
|
||||
color_temp = attributes.get(ATTR_COLOR_TEMP)
|
||||
# Handle white channels
|
||||
if CHAR_COLOR_TEMPERATURE in self.chars:
|
||||
color_temp = None
|
||||
if self.color_temp_supported:
|
||||
color_temp = attributes.get(ATTR_COLOR_TEMP)
|
||||
elif color_mode == COLOR_MODE_WHITE:
|
||||
color_temp = self.min_mireds
|
||||
if isinstance(color_temp, (int, float)):
|
||||
self.char_color_temp.set_value(round(color_temp, 0))
|
||||
if color_mode_changed:
|
||||
self.char_color_temp.notify()
|
||||
|
|
|
@ -5,7 +5,11 @@ from datetime import timedelta
|
|||
from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.homekit.const import ATTR_VALUE
|
||||
from homeassistant.components.homekit.const import (
|
||||
ATTR_VALUE,
|
||||
PROP_MAX_VALUE,
|
||||
PROP_MIN_VALUE,
|
||||
)
|
||||
from homeassistant.components.homekit.type_lights import (
|
||||
CHANGE_COALESCE_TIME_WINDOW,
|
||||
Light,
|
||||
|
@ -22,9 +26,12 @@ from homeassistant.components.light import (
|
|||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_WHITE,
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
COLOR_MODE_RGB,
|
||||
COLOR_MODE_RGBW,
|
||||
COLOR_MODE_RGBWW,
|
||||
COLOR_MODE_WHITE,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
|
@ -573,7 +580,7 @@ async def test_light_restore(hass, hk_driver, events):
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"supported_color_modes, state_props, turn_on_props, turn_on_props_with_brightness",
|
||||
"supported_color_modes, state_props, turn_on_props_with_brightness",
|
||||
[
|
||||
[
|
||||
[COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBW],
|
||||
|
@ -584,8 +591,7 @@ async def test_light_restore(hass, hk_driver, events):
|
|||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBW,
|
||||
},
|
||||
{ATTR_RGBW_COLOR: (31, 127, 71, 0)},
|
||||
{ATTR_RGBW_COLOR: (15, 63, 35, 0)},
|
||||
{ATTR_HS_COLOR: (145, 75), ATTR_BRIGHTNESS_PCT: 25},
|
||||
],
|
||||
[
|
||||
[COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBWW],
|
||||
|
@ -596,21 +602,19 @@ async def test_light_restore(hass, hk_driver, events):
|
|||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBWW,
|
||||
},
|
||||
{ATTR_RGBWW_COLOR: (31, 127, 71, 0, 0)},
|
||||
{ATTR_RGBWW_COLOR: (15, 63, 35, 0, 0)},
|
||||
{ATTR_HS_COLOR: (145, 75), ATTR_BRIGHTNESS_PCT: 25},
|
||||
],
|
||||
],
|
||||
)
|
||||
async def test_light_rgb_with_white(
|
||||
async def test_light_rgb_with_color_temp(
|
||||
hass,
|
||||
hk_driver,
|
||||
events,
|
||||
supported_color_modes,
|
||||
state_props,
|
||||
turn_on_props,
|
||||
turn_on_props_with_brightness,
|
||||
):
|
||||
"""Test lights with RGBW/RGBWW."""
|
||||
"""Test lights with RGBW/RGBWW with color temp support."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
hass.states.async_set(
|
||||
|
@ -629,7 +633,7 @@ async def test_light_rgb_with_white(
|
|||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
assert acc.char_brightness.value == 50
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
@ -658,11 +662,10 @@ async def test_light_rgb_with_white(
|
|||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
for k, v in turn_on_props.items():
|
||||
assert call_turn_on[-1].data[k] == v
|
||||
assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75)
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)"
|
||||
assert acc.char_brightness.value == 50
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
|
@ -697,7 +700,204 @@ async def test_light_rgb_with_white(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"supported_color_modes, state_props, turn_on_props, turn_on_props_with_brightness",
|
||||
"supported_color_modes, state_props, turn_on_props_with_brightness",
|
||||
[
|
||||
[
|
||||
[COLOR_MODE_RGBW],
|
||||
{
|
||||
ATTR_RGBW_COLOR: (128, 50, 0, 255),
|
||||
ATTR_RGB_COLOR: (128, 50, 0),
|
||||
ATTR_HS_COLOR: (23.438, 100.0),
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBW,
|
||||
},
|
||||
{ATTR_RGBW_COLOR: (0, 0, 0, 191)},
|
||||
],
|
||||
[
|
||||
[COLOR_MODE_RGBWW],
|
||||
{
|
||||
ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255),
|
||||
ATTR_RGB_COLOR: (128, 50, 0),
|
||||
ATTR_HS_COLOR: (23.438, 100.0),
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBWW,
|
||||
},
|
||||
{ATTR_RGBWW_COLOR: (0, 0, 0, 165, 26)},
|
||||
],
|
||||
],
|
||||
)
|
||||
async def test_light_rgbwx_with_color_temp_and_brightness(
|
||||
hass,
|
||||
hk_driver,
|
||||
events,
|
||||
supported_color_modes,
|
||||
state_props,
|
||||
turn_on_props_with_brightness,
|
||||
):
|
||||
"""Test lights with RGBW/RGBWW with color temp support and setting brightness."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{ATTR_SUPPORTED_COLOR_MODES: supported_color_modes, **state_props},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
hk_driver.add_accessory(acc)
|
||||
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
||||
char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID]
|
||||
char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_color_temp_iid,
|
||||
HAP_REPR_VALUE: 200,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_brightness_iid,
|
||||
HAP_REPR_VALUE: 75,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
for k, v in turn_on_props_with_brightness.items():
|
||||
assert call_turn_on[-1].data[k] == v
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "brightness at 75%, color temperature at 200"
|
||||
assert acc.char_brightness.value == 75
|
||||
|
||||
|
||||
async def test_light_rgb_or_w_lights(
|
||||
hass,
|
||||
hk_driver,
|
||||
events,
|
||||
):
|
||||
"""Test lights with RGB or W lights."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGB, COLOR_MODE_WHITE],
|
||||
ATTR_RGBW_COLOR: (128, 50, 0, 255),
|
||||
ATTR_RGB_COLOR: (128, 50, 0),
|
||||
ATTR_HS_COLOR: (23.438, 100.0),
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGB,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
hk_driver.add_accessory(acc)
|
||||
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
assert acc.char_brightness.value == 100
|
||||
assert acc.char_color_temp.value == 153
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
||||
char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID]
|
||||
char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID]
|
||||
char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID]
|
||||
char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_hue_iid,
|
||||
HAP_REPR_VALUE: 145,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_saturation_iid,
|
||||
HAP_REPR_VALUE: 75,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75)
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)"
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_color_temp_iid,
|
||||
HAP_REPR_VALUE: acc.min_mireds,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_brightness_iid,
|
||||
HAP_REPR_VALUE: 25,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[-1].data[ATTR_WHITE] == round(25 * 255 / 100)
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == "brightness at 25%, color temperature at 153"
|
||||
assert acc.char_brightness.value == 25
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGB, COLOR_MODE_WHITE],
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_WHITE,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 0
|
||||
assert acc.char_saturation.value == 0
|
||||
assert acc.char_brightness.value == 100
|
||||
assert acc.char_color_temp.value == 153
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"supported_color_modes, state_props",
|
||||
[
|
||||
[
|
||||
[COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBW],
|
||||
|
@ -708,8 +908,6 @@ async def test_light_rgb_with_white(
|
|||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBW,
|
||||
},
|
||||
{ATTR_RGBW_COLOR: (31, 127, 71, 0)},
|
||||
{ATTR_COLOR_TEMP: 2700},
|
||||
],
|
||||
[
|
||||
[COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBWW],
|
||||
|
@ -720,8 +918,6 @@ async def test_light_rgb_with_white(
|
|||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBWW,
|
||||
},
|
||||
{ATTR_RGBWW_COLOR: (31, 127, 71, 0, 0)},
|
||||
{ATTR_COLOR_TEMP: 2700},
|
||||
],
|
||||
],
|
||||
)
|
||||
|
@ -731,8 +927,6 @@ async def test_light_rgb_with_white_switch_to_temp(
|
|||
events,
|
||||
supported_color_modes,
|
||||
state_props,
|
||||
turn_on_props,
|
||||
turn_on_props_with_brightness,
|
||||
):
|
||||
"""Test lights with RGBW/RGBWW that preserves brightness when switching to color temp."""
|
||||
entity_id = "light.demo"
|
||||
|
@ -753,7 +947,7 @@ async def test_light_rgb_with_white_switch_to_temp(
|
|||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
assert acc.char_brightness.value == 50
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
@ -782,19 +976,17 @@ async def test_light_rgb_with_white_switch_to_temp(
|
|||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
for k, v in turn_on_props.items():
|
||||
assert call_turn_on[-1].data[k] == v
|
||||
assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75)
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)"
|
||||
assert acc.char_brightness.value == 50
|
||||
|
||||
assert acc.char_brightness.value == 100
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_color_temp_iid,
|
||||
HAP_REPR_VALUE: 2700,
|
||||
HAP_REPR_VALUE: 500,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -803,11 +995,221 @@ async def test_light_rgb_with_white_switch_to_temp(
|
|||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
for k, v in turn_on_props_with_brightness.items():
|
||||
assert call_turn_on[-1].data[k] == v
|
||||
assert call_turn_on[-1].data[ATTR_COLOR_TEMP] == 500
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == "color temperature at 2700"
|
||||
assert acc.char_brightness.value == 50
|
||||
assert events[-1].data[ATTR_VALUE] == "color temperature at 500"
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
|
||||
async def test_light_rgbww_with_color_temp_conversion(
|
||||
hass,
|
||||
hk_driver,
|
||||
events,
|
||||
):
|
||||
"""Test lights with RGBWW convert color temp as expected."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBWW],
|
||||
ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255),
|
||||
ATTR_RGB_COLOR: (128, 50, 0),
|
||||
ATTR_HS_COLOR: (23.438, 100.0),
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBWW,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
hk_driver.add_accessory(acc)
|
||||
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
||||
char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID]
|
||||
char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID]
|
||||
char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID]
|
||||
char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_hue_iid,
|
||||
HAP_REPR_VALUE: 145,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_saturation_iid,
|
||||
HAP_REPR_VALUE: 75,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75)
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)"
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_color_temp_iid,
|
||||
HAP_REPR_VALUE: 200,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[-1].data[ATTR_RGBWW_COLOR] == (0, 0, 0, 220, 35)
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == "color temperature at 200"
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBWW],
|
||||
ATTR_RGBWW_COLOR: (0, 0, 0, 128, 255),
|
||||
ATTR_RGB_COLOR: (255, 163, 79),
|
||||
ATTR_HS_COLOR: (28.636, 69.02),
|
||||
ATTR_BRIGHTNESS: 180,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBWW,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_brightness_iid,
|
||||
HAP_REPR_VALUE: 100,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[-1].data[ATTR_BRIGHTNESS_PCT] == 100
|
||||
assert len(events) == 3
|
||||
assert events[-1].data[ATTR_VALUE] == "brightness at 100%"
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
|
||||
async def test_light_rgbw_with_color_temp_conversion(
|
||||
hass,
|
||||
hk_driver,
|
||||
events,
|
||||
):
|
||||
"""Test lights with RGBW convert color temp as expected."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW],
|
||||
ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255),
|
||||
ATTR_RGB_COLOR: (128, 50, 0),
|
||||
ATTR_HS_COLOR: (23.438, 100.0),
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBW,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = Light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
hk_driver.add_accessory(acc)
|
||||
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 23
|
||||
assert acc.char_saturation.value == 100
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
||||
char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID]
|
||||
char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID]
|
||||
char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID]
|
||||
assert (
|
||||
acc.char_color_temp.properties[PROP_MIN_VALUE]
|
||||
== acc.char_color_temp.properties[PROP_MAX_VALUE]
|
||||
)
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_hue_iid,
|
||||
HAP_REPR_VALUE: 145,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_saturation_iid,
|
||||
HAP_REPR_VALUE: 75,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75)
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)"
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_color_temp_iid,
|
||||
HAP_REPR_VALUE: 153,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await _wait_for_light_coalesce(hass)
|
||||
assert call_turn_on
|
||||
assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[-1].data[ATTR_RGBW_COLOR] == (0, 0, 0, 255)
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == "color temperature at 153"
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
|
||||
async def test_light_set_brightness_and_color(hass, hk_driver, events):
|
||||
|
|
Loading…
Reference in New Issue