Add color support to matter lights (#87366)
* Added the correct attributes to ColorTemperatureLight and ExtendedColorLight and added CurrentY to codespell ignore word list * Added enums for matter color modes * Added support for reading color and color temperature settings from matter api * Added away of getting the ColorControl featureMap while the get_cluster(ColorControl) function is fixed * Initial working implementation of color and color temperature * Full supports for lights with both hs and xy * Added checks to make sure color features are supported before making matter call * Changed how color mode is figured out * Moved the logic that gets the brightness to its own function * Adds matter light tests for hass triggered events * Adds full test coverage for matter all types of matter lights * Adds full test coverage for matter all types of matter lights * Moved light convertion logic to util.py * Reorderd codespell ignore list and removed unused code * Adds brightness state test
This commit is contained in:
parent
096f6eb554
commit
e84a11963e
5 changed files with 2606 additions and 57 deletions
|
@ -31,7 +31,7 @@ repos:
|
|||
hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
- --ignore-words-list=additionals,alle,alot,ba,bre,bund,datas,dof,dur,ether,farenheit,falsy,fo,haa,hass,hist,iam,iff,iif,incomfort,ines,ist,lightsensor,mut,nam,nd,pres,pullrequests,referer,resset,rime,ser,serie,sur,te,technik,ue,uint,unsecure,visability,wan,wanna,withing,zar
|
||||
- --ignore-words-list=additionals,alle,alot,ba,bre,bund,currenty,datas,dof,dur,ether,farenheit,falsy,fo,haa,hass,hist,iam,iff,iif,incomfort,ines,ist,lightsensor,mut,nam,nd,pres,pullrequests,referer,resset,rime,ser,serie,sur,te,technik,ue,uint,unsecure,visability,wan,wanna,withing,zar
|
||||
- --skip="./.*,*.csv,*.json"
|
||||
- --quiet-level=2
|
||||
exclude_types: [csv, json]
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
|
@ -10,6 +11,9 @@ from matter_server.common.models import device_types
|
|||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_XY_COLOR,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
LightEntityDescription,
|
||||
|
@ -19,9 +23,41 @@ from homeassistant.const import Platform
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import LOGGER
|
||||
from .entity import MatterEntity, MatterEntityDescriptionBaseClass
|
||||
from .helpers import get_matter
|
||||
from .util import renormalize
|
||||
from .util import (
|
||||
convert_to_hass_hs,
|
||||
convert_to_hass_xy,
|
||||
convert_to_matter_hs,
|
||||
convert_to_matter_xy,
|
||||
renormalize,
|
||||
)
|
||||
|
||||
|
||||
class MatterColorMode(Enum):
|
||||
"""Matter color mode."""
|
||||
|
||||
HS = 0
|
||||
XY = 1
|
||||
COLOR_TEMP = 2
|
||||
|
||||
|
||||
COLOR_MODE_MAP = {
|
||||
MatterColorMode.HS: ColorMode.HS,
|
||||
MatterColorMode.XY: ColorMode.XY,
|
||||
MatterColorMode.COLOR_TEMP: ColorMode.COLOR_TEMP,
|
||||
}
|
||||
|
||||
|
||||
class MatterColorControlFeatures(Enum):
|
||||
"""Matter color control features."""
|
||||
|
||||
HS = 0 # Hue and saturation (Optional if device is color capable)
|
||||
EHUE = 1 # Enhanced hue and saturation (Optional if device is color capable)
|
||||
COLOR_LOOP = 2 # Color loop (Optional if device is color capable)
|
||||
XY = 3 # XY (Mandatory if device is color capable)
|
||||
COLOR_TEMP = 4 # Color temperature (Mandatory if device is color capable)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -39,73 +75,195 @@ class MatterLight(MatterEntity, LightEntity):
|
|||
|
||||
entity_description: MatterLightEntityDescription
|
||||
|
||||
def _supports_feature(
|
||||
self, feature_map: int, feature: MatterColorControlFeatures
|
||||
) -> bool:
|
||||
"""Return if device supports given feature."""
|
||||
|
||||
return (feature_map & (1 << feature.value)) != 0
|
||||
|
||||
def _supports_color_mode(self, color_feature: MatterColorControlFeatures) -> bool:
|
||||
"""Return if device supports given color mode."""
|
||||
|
||||
feature_map = self._device_type_instance.node.get_attribute(
|
||||
self._device_type_instance.endpoint,
|
||||
clusters.ColorControl,
|
||||
clusters.ColorControl.Attributes.FeatureMap,
|
||||
)
|
||||
|
||||
assert isinstance(feature_map.value, int)
|
||||
|
||||
return self._supports_feature(feature_map.value, color_feature)
|
||||
|
||||
def _supports_hs_color(self) -> bool:
|
||||
"""Return if device supports hs color."""
|
||||
|
||||
return self._supports_color_mode(MatterColorControlFeatures.HS)
|
||||
|
||||
def _supports_xy_color(self) -> bool:
|
||||
"""Return if device supports xy color."""
|
||||
|
||||
return self._supports_color_mode(MatterColorControlFeatures.XY)
|
||||
|
||||
def _supports_color_temperature(self) -> bool:
|
||||
"""Return if device supports color temperature."""
|
||||
|
||||
return self._supports_color_mode(MatterColorControlFeatures.COLOR_TEMP)
|
||||
|
||||
def _supports_brightness(self) -> bool:
|
||||
"""Return if device supports brightness."""
|
||||
|
||||
return (
|
||||
clusters.LevelControl.Attributes.CurrentLevel
|
||||
in self.entity_description.subscribe_attributes
|
||||
)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn light on."""
|
||||
if ATTR_BRIGHTNESS not in kwargs or not self._supports_brightness():
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._device_type_instance.node.node_id,
|
||||
endpoint=self._device_type_instance.endpoint,
|
||||
command=clusters.OnOff.Commands.On(),
|
||||
)
|
||||
return
|
||||
def _supports_color(self) -> bool:
|
||||
"""Return if device supports color."""
|
||||
|
||||
return (
|
||||
clusters.ColorControl.Attributes.ColorMode
|
||||
in self.entity_description.subscribe_attributes
|
||||
)
|
||||
|
||||
async def _set_xy_color(self, xy_color: tuple[float, float]) -> None:
|
||||
"""Set xy color."""
|
||||
|
||||
matter_xy = convert_to_matter_xy(xy_color)
|
||||
|
||||
LOGGER.debug("Setting xy color to %s", matter_xy)
|
||||
await self.send_device_command(
|
||||
clusters.ColorControl.Commands.MoveToColor(
|
||||
colorX=int(matter_xy[0]),
|
||||
colorY=int(matter_xy[1]),
|
||||
# It's required in TLV. We don't implement transition time yet.
|
||||
transitionTime=0,
|
||||
)
|
||||
)
|
||||
|
||||
async def _set_hs_color(self, hs_color: tuple[float, float]) -> None:
|
||||
"""Set hs color."""
|
||||
|
||||
matter_hs = convert_to_matter_hs(hs_color)
|
||||
|
||||
LOGGER.debug("Setting hs color to %s", matter_hs)
|
||||
await self.send_device_command(
|
||||
clusters.ColorControl.Commands.MoveToHueAndSaturation(
|
||||
hue=int(matter_hs[0]),
|
||||
saturation=int(matter_hs[1]),
|
||||
# It's required in TLV. We don't implement transition time yet.
|
||||
transitionTime=0,
|
||||
)
|
||||
)
|
||||
|
||||
async def _set_color_temp(self, color_temp: int) -> None:
|
||||
"""Set color temperature."""
|
||||
|
||||
LOGGER.debug("Setting color temperature to %s", color_temp)
|
||||
await self.send_device_command(
|
||||
clusters.ColorControl.Commands.MoveToColorTemperature(
|
||||
colorTemperature=color_temp,
|
||||
# It's required in TLV. We don't implement transition time yet.
|
||||
transitionTime=0,
|
||||
)
|
||||
)
|
||||
|
||||
async def _set_brightness(self, brightness: int) -> None:
|
||||
"""Set brightness."""
|
||||
|
||||
LOGGER.debug("Setting brightness to %s", brightness)
|
||||
level_control = self._device_type_instance.get_cluster(clusters.LevelControl)
|
||||
# We check above that the device supports brightness, ie level control.
|
||||
|
||||
assert level_control is not None
|
||||
|
||||
level = round(
|
||||
renormalize(
|
||||
kwargs[ATTR_BRIGHTNESS],
|
||||
brightness,
|
||||
(0, 255),
|
||||
(level_control.minLevel, level_control.maxLevel),
|
||||
)
|
||||
)
|
||||
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._device_type_instance.node.node_id,
|
||||
endpoint=self._device_type_instance.endpoint,
|
||||
command=clusters.LevelControl.Commands.MoveToLevelWithOnOff(
|
||||
await self.send_device_command(
|
||||
clusters.LevelControl.Commands.MoveToLevelWithOnOff(
|
||||
level=level,
|
||||
# It's required in TLV. We don't implement transition time yet.
|
||||
transitionTime=0,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn light off."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._device_type_instance.node.node_id,
|
||||
endpoint=self._device_type_instance.endpoint,
|
||||
command=clusters.OnOff.Commands.Off(),
|
||||
def _get_xy_color(self) -> tuple[float, float]:
|
||||
"""Get xy color from matter."""
|
||||
|
||||
x_color = self.get_matter_attribute(clusters.ColorControl.Attributes.CurrentX)
|
||||
y_color = self.get_matter_attribute(clusters.ColorControl.Attributes.CurrentY)
|
||||
|
||||
assert x_color is not None
|
||||
assert y_color is not None
|
||||
|
||||
xy_color = convert_to_hass_xy((x_color.value, y_color.value))
|
||||
LOGGER.debug(
|
||||
"Got xy color %s for %s",
|
||||
xy_color,
|
||||
self._device_type_instance,
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
supports_brigthness = self._supports_brightness()
|
||||
return xy_color
|
||||
|
||||
if self._attr_supported_color_modes is None and supports_brigthness:
|
||||
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||
def _get_hs_color(self) -> tuple[float, float]:
|
||||
"""Get hs color from matter."""
|
||||
|
||||
if attr := self.get_matter_attribute(clusters.OnOff.Attributes.OnOff):
|
||||
self._attr_is_on = attr.value
|
||||
hue = self.get_matter_attribute(clusters.ColorControl.Attributes.CurrentHue)
|
||||
|
||||
if supports_brigthness:
|
||||
level_control = self._device_type_instance.get_cluster(
|
||||
clusters.LevelControl
|
||||
saturation = self.get_matter_attribute(
|
||||
clusters.ColorControl.Attributes.CurrentSaturation
|
||||
)
|
||||
# We check above that the device supports brightness, ie level control.
|
||||
|
||||
assert hue is not None
|
||||
assert saturation is not None
|
||||
|
||||
hs_color = convert_to_hass_hs((hue.value, saturation.value))
|
||||
|
||||
LOGGER.debug(
|
||||
"Got hs color %s for %s",
|
||||
hs_color,
|
||||
self._device_type_instance,
|
||||
)
|
||||
|
||||
return hs_color
|
||||
|
||||
def _get_color_temperature(self) -> int:
|
||||
"""Get color temperature from matter."""
|
||||
|
||||
color_temp = self.get_matter_attribute(
|
||||
clusters.ColorControl.Attributes.ColorTemperatureMireds
|
||||
)
|
||||
|
||||
assert color_temp is not None
|
||||
|
||||
LOGGER.debug(
|
||||
"Got color temperature %s for %s",
|
||||
color_temp.value,
|
||||
self._device_type_instance,
|
||||
)
|
||||
|
||||
return int(color_temp.value)
|
||||
|
||||
def _get_brightness(self) -> int:
|
||||
"""Get brightness from matter."""
|
||||
|
||||
level_control = self._device_type_instance.get_cluster(clusters.LevelControl)
|
||||
|
||||
# We should not get here if brightness is not supported.
|
||||
assert level_control is not None
|
||||
|
||||
# Convert brightness to Home Assistant = 0..255
|
||||
self._attr_brightness = round(
|
||||
LOGGER.debug(
|
||||
"Got brightness %s for %s",
|
||||
level_control.currentLevel,
|
||||
self._device_type_instance,
|
||||
)
|
||||
|
||||
return round(
|
||||
renormalize(
|
||||
level_control.currentLevel,
|
||||
(level_control.minLevel, level_control.maxLevel),
|
||||
|
@ -113,6 +271,110 @@ class MatterLight(MatterEntity, LightEntity):
|
|||
)
|
||||
)
|
||||
|
||||
def _get_color_mode(self) -> ColorMode:
|
||||
"""Get color mode from matter."""
|
||||
|
||||
color_mode = self.get_matter_attribute(
|
||||
clusters.ColorControl.Attributes.ColorMode
|
||||
)
|
||||
|
||||
assert color_mode is not None
|
||||
|
||||
ha_color_mode = COLOR_MODE_MAP[MatterColorMode(color_mode.value)]
|
||||
|
||||
LOGGER.debug(
|
||||
"Got color mode (%s) for %s", ha_color_mode, self._device_type_instance
|
||||
)
|
||||
|
||||
return ha_color_mode
|
||||
|
||||
async def send_device_command(self, command: Any) -> None:
|
||||
"""Send device command."""
|
||||
await self.matter_client.send_device_command(
|
||||
node_id=self._device_type_instance.node.node_id,
|
||||
endpoint=self._device_type_instance.endpoint,
|
||||
command=command,
|
||||
)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn light on."""
|
||||
|
||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
||||
xy_color = kwargs.get(ATTR_XY_COLOR)
|
||||
color_temp = kwargs.get(ATTR_COLOR_TEMP)
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||
|
||||
if self._supports_color():
|
||||
if hs_color is not None and self._supports_hs_color():
|
||||
await self._set_hs_color(hs_color)
|
||||
elif xy_color is not None and self._supports_xy_color():
|
||||
await self._set_xy_color(xy_color)
|
||||
elif color_temp is not None and self._supports_color_temperature():
|
||||
await self._set_color_temp(color_temp)
|
||||
|
||||
if brightness is not None and self._supports_brightness():
|
||||
await self._set_brightness(brightness)
|
||||
return
|
||||
|
||||
await self.send_device_command(
|
||||
clusters.OnOff.Commands.On(),
|
||||
)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn light off."""
|
||||
await self.send_device_command(
|
||||
clusters.OnOff.Commands.Off(),
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
|
||||
supports_color = self._supports_color()
|
||||
supports_color_temperature = (
|
||||
self._supports_color_temperature() if supports_color else False
|
||||
)
|
||||
supports_brightness = self._supports_brightness()
|
||||
|
||||
if self._attr_supported_color_modes is None:
|
||||
supported_color_modes = set()
|
||||
if supports_color:
|
||||
supported_color_modes.add(ColorMode.XY)
|
||||
if self._supports_hs_color():
|
||||
supported_color_modes.add(ColorMode.HS)
|
||||
|
||||
if supports_color_temperature:
|
||||
supported_color_modes.add(ColorMode.COLOR_TEMP)
|
||||
|
||||
if supports_brightness:
|
||||
supported_color_modes.add(ColorMode.BRIGHTNESS)
|
||||
|
||||
self._attr_supported_color_modes = (
|
||||
supported_color_modes if supported_color_modes else None
|
||||
)
|
||||
|
||||
LOGGER.debug(
|
||||
"Supported color modes: %s for %s",
|
||||
self._attr_supported_color_modes,
|
||||
self._device_type_instance,
|
||||
)
|
||||
|
||||
if supports_color:
|
||||
self._attr_color_mode = self._get_color_mode()
|
||||
if self._attr_color_mode == ColorMode.HS:
|
||||
self._attr_hs_color = self._get_hs_color()
|
||||
else:
|
||||
self._attr_xy_color = self._get_xy_color()
|
||||
|
||||
if supports_color_temperature:
|
||||
self._attr_color_temp = self._get_color_temperature()
|
||||
|
||||
if attr := self.get_matter_attribute(clusters.OnOff.Attributes.OnOff):
|
||||
self._attr_is_on = attr.value
|
||||
|
||||
if supports_brightness:
|
||||
self._attr_brightness = self._get_brightness()
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatterLightEntityDescription(
|
||||
|
@ -159,7 +421,7 @@ DEVICE_ENTITY: dict[
|
|||
subscribe_attributes=(
|
||||
clusters.OnOff.Attributes.OnOff,
|
||||
clusters.LevelControl.Attributes.CurrentLevel,
|
||||
clusters.ColorControl,
|
||||
clusters.ColorControl.Attributes.ColorTemperatureMireds,
|
||||
),
|
||||
),
|
||||
device_types.ExtendedColorLight: MatterLightEntityDescriptionFactory(
|
||||
|
@ -167,7 +429,12 @@ DEVICE_ENTITY: dict[
|
|||
subscribe_attributes=(
|
||||
clusters.OnOff.Attributes.OnOff,
|
||||
clusters.LevelControl.Attributes.CurrentLevel,
|
||||
clusters.ColorControl,
|
||||
clusters.ColorControl.Attributes.ColorMode,
|
||||
clusters.ColorControl.Attributes.CurrentHue,
|
||||
clusters.ColorControl.Attributes.CurrentSaturation,
|
||||
clusters.ColorControl.Attributes.CurrentX,
|
||||
clusters.ColorControl.Attributes.CurrentY,
|
||||
clusters.ColorControl.Attributes.ColorTemperatureMireds,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Provide integration utilities."""
|
||||
from __future__ import annotations
|
||||
|
||||
XY_COLOR_FACTOR = 65536
|
||||
|
||||
|
||||
def renormalize(
|
||||
number: float, from_range: tuple[float, float], to_range: tuple[float, float]
|
||||
|
@ -9,3 +11,33 @@ def renormalize(
|
|||
delta1 = from_range[1] - from_range[0]
|
||||
delta2 = to_range[1] - to_range[0]
|
||||
return (delta2 * (number - from_range[0]) / delta1) + to_range[0]
|
||||
|
||||
|
||||
def convert_to_matter_hs(hass_hs: tuple[float, float]) -> tuple[float, float]:
|
||||
"""Convert Home Assistant HS to Matter HS."""
|
||||
|
||||
return (
|
||||
hass_hs[0] / 360 * 254,
|
||||
renormalize(hass_hs[1], (0, 100), (0, 254)),
|
||||
)
|
||||
|
||||
|
||||
def convert_to_hass_hs(matter_hs: tuple[float, float]) -> tuple[float, float]:
|
||||
"""Convert Matter HS to Home Assistant HS."""
|
||||
|
||||
return (
|
||||
matter_hs[0] * 360 / 254,
|
||||
renormalize(matter_hs[1], (0, 254), (0, 100)),
|
||||
)
|
||||
|
||||
|
||||
def convert_to_matter_xy(hass_xy: tuple[float, float]) -> tuple[float, float]:
|
||||
"""Convert Home Assistant XY to Matter XY."""
|
||||
|
||||
return (hass_xy[0] * XY_COLOR_FACTOR, hass_xy[1] * XY_COLOR_FACTOR)
|
||||
|
||||
|
||||
def convert_to_hass_xy(matter_xy: tuple[float, float]) -> tuple[float, float]:
|
||||
"""Convert Matter XY to Home Assistant XY."""
|
||||
|
||||
return (matter_xy[0] / XY_COLOR_FACTOR, matter_xy[1] / XY_COLOR_FACTOR)
|
||||
|
|
2110
tests/components/matter/fixtures/nodes/extended-color-light.json
Normal file
2110
tests/components/matter/fixtures/nodes/extended-color-light.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -20,7 +20,7 @@ async def light_node_fixture(
|
|||
) -> MatterNode:
|
||||
"""Fixture for a light node."""
|
||||
return await setup_integration_with_node_fixture(
|
||||
hass, "dimmable-light", matter_client
|
||||
hass, "extended-color-light", matter_client
|
||||
)
|
||||
|
||||
|
||||
|
@ -30,22 +30,13 @@ async def test_turn_on(
|
|||
light_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test turning on a light."""
|
||||
state = hass.states.get("light.mock_dimmable_light")
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
set_node_attribute(light_node, 1, 6, 0, False)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("light.mock_dimmable_light")
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
# OnOff test
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": "light.mock_dimmable_light",
|
||||
"entity_id": "light.mock_extended_color_light",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -58,11 +49,12 @@ async def test_turn_on(
|
|||
)
|
||||
matter_client.send_device_command.reset_mock()
|
||||
|
||||
# Brightness test
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": "light.mock_dimmable_light",
|
||||
"entity_id": "light.mock_extended_color_light",
|
||||
"brightness": 128,
|
||||
},
|
||||
blocking=True,
|
||||
|
@ -77,6 +69,154 @@ async def test_turn_on(
|
|||
transitionTime=0,
|
||||
),
|
||||
)
|
||||
matter_client.send_device_command.reset_mock()
|
||||
|
||||
# HS Color test
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": "light.mock_extended_color_light",
|
||||
"hs_color": [0, 0],
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert matter_client.send_device_command.call_count == 2
|
||||
matter_client.send_device_command.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
node_id=light_node.node_id,
|
||||
endpoint=1,
|
||||
command=clusters.ColorControl.Commands.MoveToHueAndSaturation(
|
||||
hue=0,
|
||||
saturation=0,
|
||||
transitionTime=0,
|
||||
),
|
||||
),
|
||||
call(
|
||||
node_id=light_node.node_id,
|
||||
endpoint=1,
|
||||
command=clusters.OnOff.Commands.On(),
|
||||
),
|
||||
]
|
||||
)
|
||||
matter_client.send_device_command.reset_mock()
|
||||
|
||||
# XY Color test
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": "light.mock_extended_color_light",
|
||||
"xy_color": [0.5, 0.5],
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert matter_client.send_device_command.call_count == 2
|
||||
matter_client.send_device_command.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
node_id=light_node.node_id,
|
||||
endpoint=1,
|
||||
command=clusters.ColorControl.Commands.MoveToColor(
|
||||
colorX=(0.5 * 65536),
|
||||
colorY=(0.5 * 65536),
|
||||
transitionTime=0,
|
||||
),
|
||||
),
|
||||
call(
|
||||
node_id=light_node.node_id,
|
||||
endpoint=1,
|
||||
command=clusters.OnOff.Commands.On(),
|
||||
),
|
||||
]
|
||||
)
|
||||
matter_client.send_device_command.reset_mock()
|
||||
|
||||
# Color Temperature test
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": "light.mock_extended_color_light",
|
||||
"color_temp": 300,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert matter_client.send_device_command.call_count == 2
|
||||
matter_client.send_device_command.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
node_id=light_node.node_id,
|
||||
endpoint=1,
|
||||
command=clusters.ColorControl.Commands.MoveToColorTemperature(
|
||||
colorTemperature=300,
|
||||
transitionTime=0,
|
||||
),
|
||||
),
|
||||
call(
|
||||
node_id=light_node.node_id,
|
||||
endpoint=1,
|
||||
command=clusters.OnOff.Commands.On(),
|
||||
),
|
||||
]
|
||||
)
|
||||
matter_client.send_device_command.reset_mock()
|
||||
|
||||
state = hass.states.get("light.mock_extended_color_light")
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
# HS Color Test
|
||||
set_node_attribute(light_node, 1, 768, 8, 0)
|
||||
set_node_attribute(light_node, 1, 768, 1, 50)
|
||||
set_node_attribute(light_node, 1, 768, 0, 100)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("light.mock_extended_color_light")
|
||||
assert state
|
||||
assert state.attributes["color_mode"] == "hs"
|
||||
assert state.attributes["hs_color"] == (141.732, 19.685)
|
||||
|
||||
# XY Color Test
|
||||
set_node_attribute(light_node, 1, 768, 8, 1)
|
||||
set_node_attribute(light_node, 1, 768, 3, 50)
|
||||
set_node_attribute(light_node, 1, 768, 4, 100)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("light.mock_extended_color_light")
|
||||
assert state
|
||||
assert state.attributes["color_mode"] == "xy"
|
||||
assert state.attributes["xy_color"] == (0.0007630, 0.001526)
|
||||
|
||||
# Color Temperature Test
|
||||
set_node_attribute(light_node, 1, 768, 8, 2)
|
||||
set_node_attribute(light_node, 1, 768, 7, 100)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("light.mock_extended_color_light")
|
||||
assert state
|
||||
assert state.attributes["color_mode"] == "color_temp"
|
||||
assert state.attributes["color_temp"] == 100
|
||||
|
||||
# Brightness state test
|
||||
set_node_attribute(light_node, 1, 8, 0, 50)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("light.mock_extended_color_light")
|
||||
assert state
|
||||
assert state.attributes["brightness"] == 49
|
||||
|
||||
# Off state test
|
||||
set_node_attribute(light_node, 1, 6, 0, False)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("light.mock_extended_color_light")
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
|
||||
async def test_turn_off(
|
||||
|
@ -85,7 +225,7 @@ async def test_turn_off(
|
|||
light_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test turning off a light."""
|
||||
state = hass.states.get("light.mock_dimmable_light")
|
||||
state = hass.states.get("light.mock_extended_color_light")
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
|
@ -93,7 +233,7 @@ async def test_turn_off(
|
|||
"light",
|
||||
"turn_off",
|
||||
{
|
||||
"entity_id": "light.mock_dimmable_light",
|
||||
"entity_id": "light.mock_extended_color_light",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue