Add light platform to Homee (#138776)

This commit is contained in:
Markus Adrario 2025-02-20 19:31:31 +01:00 committed by GitHub
parent ec7ec993b0
commit 5d1eb69281
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1167 additions and 1 deletions

View File

@ -14,7 +14,13 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.BUTTON, Platform.COVER, Platform.SENSOR, Platform.SWITCH]
PLATFORMS = [
Platform.BUTTON,
Platform.COVER,
Platform.LIGHT,
Platform.SENSOR,
Platform.SWITCH,
]
type HomeeConfigEntry = ConfigEntry[Homee]

View File

@ -76,6 +76,7 @@ CLIMATE_PROFILES = [
NodeProfile.WIFI_RADIATOR_THERMOSTAT,
NodeProfile.WIFI_ROOM_THERMOSTAT,
]
LIGHT_PROFILES = [
NodeProfile.DIMMABLE_COLOR_LIGHT,
NodeProfile.DIMMABLE_COLOR_METERING_PLUG,

View File

@ -0,0 +1,213 @@
"""The Homee light platform."""
from typing import Any
from pyHomee.const import AttributeType
from pyHomee.model import HomeeAttribute, HomeeNode
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_HS_COLOR,
ColorMode,
LightEntity,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.color import (
brightness_to_value,
color_hs_to_RGB,
color_RGB_to_hs,
value_to_brightness,
)
from . import HomeeConfigEntry
from .const import LIGHT_PROFILES
from .entity import HomeeNodeEntity
LIGHT_ATTRIBUTES = [
AttributeType.COLOR,
AttributeType.COLOR_MODE,
AttributeType.COLOR_TEMPERATURE,
AttributeType.DIMMING_LEVEL,
]
def is_light_node(node: HomeeNode) -> bool:
"""Determine if a node is controllable as a homee light based on its profile and attributes."""
assert node.attribute_map is not None
return node.profile in LIGHT_PROFILES and AttributeType.ON_OFF in node.attribute_map
def get_color_mode(supported_modes: set[ColorMode]) -> ColorMode:
"""Determine the color mode from the supported modes."""
if ColorMode.HS in supported_modes:
return ColorMode.HS
if ColorMode.COLOR_TEMP in supported_modes:
return ColorMode.COLOR_TEMP
if ColorMode.BRIGHTNESS in supported_modes:
return ColorMode.BRIGHTNESS
return ColorMode.ONOFF
def get_light_attribute_sets(
node: HomeeNode,
) -> list[dict[AttributeType, HomeeAttribute]]:
"""Return the lights with their attributes as found in the node."""
lights: list[dict[AttributeType, HomeeAttribute]] = []
on_off_attributes = [
i for i in node.attributes if i.type == AttributeType.ON_OFF and i.editable
]
for a in on_off_attributes:
attribute_dict: dict[AttributeType, HomeeAttribute] = {a.type: a}
for attribute in node.attributes:
if attribute.instance == a.instance and attribute.type in LIGHT_ATTRIBUTES:
attribute_dict[attribute.type] = attribute
lights.append(attribute_dict)
return lights
def rgb_list_to_decimal(color: tuple[int, int, int]) -> int:
"""Convert an rgb color from list to decimal representation."""
return int(int(color[0]) << 16) + (int(color[1]) << 8) + (int(color[2]))
def decimal_to_rgb_list(color: float) -> list[int]:
"""Convert an rgb color from decimal to list representation."""
return [
(int(color) & 0xFF0000) >> 16,
(int(color) & 0x00FF00) >> 8,
(int(color) & 0x0000FF),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: HomeeConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add the Homee platform for the light entity."""
async_add_entities(
HomeeLight(node, light, config_entry)
for node in config_entry.runtime_data.nodes
for light in get_light_attribute_sets(node)
if is_light_node(node)
)
class HomeeLight(HomeeNodeEntity, LightEntity):
"""Representation of a Homee light."""
def __init__(
self,
node: HomeeNode,
light: dict[AttributeType, HomeeAttribute],
entry: HomeeConfigEntry,
) -> None:
"""Initialize a Homee light."""
super().__init__(node, entry)
self._on_off_attr: HomeeAttribute = light[AttributeType.ON_OFF]
self._dimmer_attr: HomeeAttribute | None = light.get(
AttributeType.DIMMING_LEVEL
)
self._col_attr: HomeeAttribute | None = light.get(AttributeType.COLOR)
self._temp_attr: HomeeAttribute | None = light.get(
AttributeType.COLOR_TEMPERATURE
)
self._mode_attr: HomeeAttribute | None = light.get(AttributeType.COLOR_MODE)
self._attr_supported_color_modes = self._get_supported_color_modes()
self._attr_color_mode = get_color_mode(self._attr_supported_color_modes)
if self._temp_attr is not None:
self._attr_min_color_temp_kelvin = int(self._temp_attr.minimum)
self._attr_max_color_temp_kelvin = int(self._temp_attr.maximum)
if self._on_off_attr.instance > 0:
self._attr_translation_key = "light_instance"
self._attr_translation_placeholders = {
"instance": str(self._on_off_attr.instance)
}
else:
# If a device has only one light, it will get its name.
self._attr_name = None
self._attr_unique_id = (
f"{entry.runtime_data.settings.uid}-{self._node.id}-{self._on_off_attr.id}"
)
@property
def brightness(self) -> int:
"""Return the brightness of the light."""
assert self._dimmer_attr is not None
return value_to_brightness(
(self._dimmer_attr.minimum + 1, self._dimmer_attr.maximum),
self._dimmer_attr.current_value,
)
@property
def hs_color(self) -> tuple[float, float] | None:
"""Return the color of the light."""
assert self._col_attr is not None
rgb = decimal_to_rgb_list(self._col_attr.current_value)
return color_RGB_to_hs(*rgb)
@property
def color_temp_kelvin(self) -> int:
"""Return the color temperature of the light."""
assert self._temp_attr is not None
return int(self._temp_attr.current_value)
@property
def is_on(self) -> bool:
"""Return true if light is on."""
return bool(self._on_off_attr.current_value)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Instruct the light to turn on."""
if ATTR_BRIGHTNESS in kwargs and self._dimmer_attr is not None:
target_value = round(
brightness_to_value(
(self._dimmer_attr.minimum, self._dimmer_attr.maximum),
kwargs[ATTR_BRIGHTNESS],
)
)
await self.async_set_value(self._dimmer_attr, target_value)
else:
# If no brightness value is given, just turn on.
await self.async_set_value(self._on_off_attr, 1)
if ATTR_COLOR_TEMP_KELVIN in kwargs and self._temp_attr is not None:
await self.async_set_value(self._temp_attr, kwargs[ATTR_COLOR_TEMP_KELVIN])
if ATTR_HS_COLOR in kwargs:
color = kwargs[ATTR_HS_COLOR]
if self._col_attr is not None:
await self.async_set_value(
self._col_attr,
rgb_list_to_decimal(color_hs_to_RGB(*color)),
)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Instruct the light to turn off."""
await self.async_set_value(self._on_off_attr, 0)
def _get_supported_color_modes(self) -> set[ColorMode]:
"""Determine the supported color modes from the available attributes."""
color_modes: set[ColorMode] = set()
if self._temp_attr is not None and self._temp_attr.editable:
color_modes.add(ColorMode.COLOR_TEMP)
if self._col_attr is not None:
color_modes.add(ColorMode.HS)
# If no other color modes are available, set one of those.
if len(color_modes) == 0:
if self._dimmer_attr is not None:
color_modes.add(ColorMode.BRIGHTNESS)
else:
color_modes.add(ColorMode.ONOFF)
return color_modes

View File

@ -61,6 +61,11 @@
"name": "Ventilate"
}
},
"light": {
"light_instance": {
"name": "Light {instance}"
}
},
"sensor": {
"brightness_instance": {
"name": "Illuminance {instance}"

View File

@ -0,0 +1,102 @@
{
"id": 2,
"name": "Another Test Light",
"profile": 1002,
"image": "default",
"favorite": 0,
"order": 48,
"protocol": 21,
"sub_protocol": 3,
"routing": 0,
"state": 1,
"state_changed": 1694024544,
"added": 1679551927,
"history": 1,
"cube_type": 8,
"note": "",
"services": 7,
"phonetic_name": "",
"owner": 2,
"security": 0,
"attributes": [
{
"id": 12,
"node_id": 2,
"instance": 0,
"minimum": 0,
"maximum": 1,
"current_value": 1.0,
"target_value": 1.0,
"last_value": 1.0,
"unit": "",
"step_value": 1.0,
"editable": 1,
"type": 1,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"automations": ["toggle"],
"history": {
"day": 35,
"week": 5,
"month": 1,
"stepped": true
}
}
},
{
"id": 13,
"node_id": 2,
"instance": 0,
"minimum": 0,
"maximum": 100,
"current_value": 100.0,
"target_value": 100.0,
"last_value": 100.0,
"unit": "%",
"step_value": 1.0,
"editable": 1,
"type": 2,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
},
{
"id": 14,
"node_id": 2,
"instance": 0,
"minimum": 2000,
"maximum": 7000,
"current_value": 3700.0,
"target_value": 3700.0,
"last_value": 3700.0,
"unit": "K",
"step_value": 1.0,
"editable": 1,
"type": 42,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
}
]
}

View File

@ -0,0 +1,333 @@
{
"id": 1,
"name": "Test Light",
"profile": 1002,
"image": "default",
"favorite": 0,
"order": 48,
"protocol": 21,
"sub_protocol": 3,
"routing": 0,
"state": 1,
"state_changed": 1694024544,
"added": 1679551927,
"history": 1,
"cube_type": 8,
"note": "",
"services": 7,
"phonetic_name": "",
"owner": 2,
"security": 0,
"attributes": [
{
"id": 1,
"node_id": 1,
"instance": 1,
"minimum": 0,
"maximum": 1,
"current_value": 1.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "",
"step_value": 1.0,
"editable": 1,
"type": 1,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"automations": ["toggle"],
"history": {
"day": 35,
"week": 5,
"month": 1,
"stepped": true
}
}
},
{
"id": 2,
"node_id": 1,
"instance": 1,
"minimum": 0,
"maximum": 100,
"current_value": 100.0,
"target_value": 100.0,
"last_value": 100.0,
"unit": "%",
"step_value": 1.0,
"editable": 1,
"type": 2,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
},
{
"id": 3,
"node_id": 1,
"instance": 1,
"minimum": 0,
"maximum": 1073741824,
"current_value": 16763000,
"target_value": 16763000,
"last_value": 16763000,
"unit": "",
"step_value": 1.0,
"editable": 1,
"type": 23,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "7001020;16419669;12026363;16525995",
"name": ""
},
{
"id": 4,
"node_id": 1,
"instance": 1,
"minimum": 153,
"maximum": 500,
"current_value": 366.0,
"target_value": 366.0,
"last_value": 366.0,
"unit": "K",
"step_value": 1.0,
"editable": 1,
"type": 42,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
},
{
"id": 5,
"node_id": 1,
"instance": 2,
"minimum": 0,
"maximum": 1,
"current_value": 0.0,
"target_value": 0.0,
"last_value": 0.0,
"unit": "",
"step_value": 1.0,
"editable": 1,
"type": 1,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"automations": ["toggle"],
"history": {
"day": 35,
"week": 5,
"month": 1,
"stepped": true
}
}
},
{
"id": 6,
"node_id": 1,
"instance": 2,
"minimum": 0,
"maximum": 100,
"current_value": 100.0,
"target_value": 100.0,
"last_value": 100.0,
"unit": "%",
"step_value": 1.0,
"editable": 1,
"type": 2,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
},
{
"id": 7,
"node_id": 1,
"instance": 2,
"minimum": 0,
"maximum": 1073741824,
"current_value": 16763000,
"target_value": 16763000,
"last_value": 16763000,
"unit": "",
"step_value": 1.0,
"editable": 1,
"type": 23,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "7001020;16419669;12026363;16525995",
"name": ""
},
{
"id": 8,
"node_id": 1,
"instance": 2,
"minimum": 2202,
"maximum": 4000,
"current_value": 3000.0,
"target_value": 3000.0,
"last_value": 3000.0,
"unit": "K",
"step_value": 1.0,
"editable": 1,
"type": 42,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
},
{
"id": 9,
"node_id": 1,
"instance": 3,
"minimum": 0,
"maximum": 1,
"current_value": 1.0,
"target_value": 1.0,
"last_value": 1.0,
"unit": "n/a",
"step_value": 1.0,
"editable": 1,
"type": 1,
"state": 1,
"last_changed": 1736743294,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"automations": ["toggle"],
"history": {
"day": 35,
"week": 5,
"month": 1,
"stepped": true
}
}
},
{
"id": 10,
"node_id": 1,
"instance": 3,
"minimum": 0,
"maximum": 100,
"current_value": 40.0,
"target_value": 40.0,
"last_value": 40.0,
"unit": "%",
"step_value": 1.0,
"editable": 1,
"type": 2,
"state": 1,
"last_changed": 1736743291,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
},
{
"id": 11,
"node_id": 1,
"instance": 4,
"minimum": 0,
"maximum": 1,
"current_value": 1.0,
"target_value": 1.0,
"last_value": 1.0,
"unit": "",
"step_value": 1.0,
"editable": 1,
"type": 1,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"can_observe": [300],
"automations": ["toggle"],
"history": {
"day": 35,
"week": 5,
"month": 1,
"stepped": true
}
}
},
{
"id": 12,
"node_id": 1,
"instance": 4,
"minimum": 2200,
"maximum": 4000,
"current_value": 3000.0,
"target_value": 3000.0,
"last_value": 3000.0,
"unit": "K",
"step_value": 1.0,
"editable": 0,
"type": 42,
"state": 1,
"last_changed": 1694024544,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": "",
"options": {
"automations": ["step"]
}
}
]
}

View File

@ -0,0 +1,348 @@
# serializer version: 1
# name: test_light_snapshot[light.another_test_light-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max_color_temp_kelvin': 7000,
'max_mireds': 500,
'min_color_temp_kelvin': 2000,
'min_mireds': 142,
'supported_color_modes': list([
<ColorMode.COLOR_TEMP: 'color_temp'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'light',
'entity_category': None,
'entity_id': 'light.another_test_light',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'homee',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00055511EECC-2-12',
'unit_of_measurement': None,
})
# ---
# name: test_light_snapshot[light.another_test_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'brightness': 255,
'color_mode': <ColorMode.COLOR_TEMP: 'color_temp'>,
'color_temp': 270,
'color_temp_kelvin': 3700,
'friendly_name': 'Another Test Light',
'hs_color': tuple(
26.996,
40.593,
),
'max_color_temp_kelvin': 7000,
'max_mireds': 500,
'min_color_temp_kelvin': 2000,
'min_mireds': 142,
'rgb_color': tuple(
255,
198,
151,
),
'supported_color_modes': list([
<ColorMode.COLOR_TEMP: 'color_temp'>,
]),
'supported_features': <LightEntityFeature: 0>,
'xy_color': tuple(
0.44,
0.371,
),
}),
'context': <ANY>,
'entity_id': 'light.another_test_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_light_snapshot[light.test_light_light_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max_color_temp_kelvin': 500,
'max_mireds': 6535,
'min_color_temp_kelvin': 153,
'min_mireds': 2000,
'supported_color_modes': list([
<ColorMode.COLOR_TEMP: 'color_temp'>,
<ColorMode.HS: 'hs'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'light',
'entity_category': None,
'entity_id': 'light.test_light_light_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Light 1',
'platform': 'homee',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'light_instance',
'unique_id': '00055511EECC-1-1',
'unit_of_measurement': None,
})
# ---
# name: test_light_snapshot[light.test_light_light_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'brightness': 255,
'color_mode': <ColorMode.HS: 'hs'>,
'color_temp': None,
'color_temp_kelvin': None,
'friendly_name': 'Test Light Light 1',
'hs_color': tuple(
35.556,
52.941,
),
'max_color_temp_kelvin': 500,
'max_mireds': 6535,
'min_color_temp_kelvin': 153,
'min_mireds': 2000,
'rgb_color': tuple(
255,
200,
120,
),
'supported_color_modes': list([
<ColorMode.COLOR_TEMP: 'color_temp'>,
<ColorMode.HS: 'hs'>,
]),
'supported_features': <LightEntityFeature: 0>,
'xy_color': tuple(
0.464,
0.402,
),
}),
'context': <ANY>,
'entity_id': 'light.test_light_light_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_light_snapshot[light.test_light_light_2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max_color_temp_kelvin': 4000,
'max_mireds': 454,
'min_color_temp_kelvin': 2202,
'min_mireds': 250,
'supported_color_modes': list([
<ColorMode.COLOR_TEMP: 'color_temp'>,
<ColorMode.HS: 'hs'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'light',
'entity_category': None,
'entity_id': 'light.test_light_light_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Light 2',
'platform': 'homee',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'light_instance',
'unique_id': '00055511EECC-1-5',
'unit_of_measurement': None,
})
# ---
# name: test_light_snapshot[light.test_light_light_2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'brightness': None,
'color_mode': None,
'color_temp': None,
'color_temp_kelvin': None,
'friendly_name': 'Test Light Light 2',
'hs_color': None,
'max_color_temp_kelvin': 4000,
'max_mireds': 454,
'min_color_temp_kelvin': 2202,
'min_mireds': 250,
'rgb_color': None,
'supported_color_modes': list([
<ColorMode.COLOR_TEMP: 'color_temp'>,
<ColorMode.HS: 'hs'>,
]),
'supported_features': <LightEntityFeature: 0>,
'xy_color': None,
}),
'context': <ANY>,
'entity_id': 'light.test_light_light_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_light_snapshot[light.test_light_light_3-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'supported_color_modes': list([
<ColorMode.BRIGHTNESS: 'brightness'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'light',
'entity_category': None,
'entity_id': 'light.test_light_light_3',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Light 3',
'platform': 'homee',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'light_instance',
'unique_id': '00055511EECC-1-9',
'unit_of_measurement': None,
})
# ---
# name: test_light_snapshot[light.test_light_light_3-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'brightness': 102,
'color_mode': <ColorMode.BRIGHTNESS: 'brightness'>,
'friendly_name': 'Test Light Light 3',
'supported_color_modes': list([
<ColorMode.BRIGHTNESS: 'brightness'>,
]),
'supported_features': <LightEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'light.test_light_light_3',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_light_snapshot[light.test_light_light_4-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'supported_color_modes': list([
<ColorMode.ONOFF: 'onoff'>,
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'light',
'entity_category': None,
'entity_id': 'light.test_light_light_4',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Light 4',
'platform': 'homee',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'light_instance',
'unique_id': '00055511EECC-1-11',
'unit_of_measurement': None,
})
# ---
# name: test_light_snapshot[light.test_light_light_4-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'color_mode': <ColorMode.ONOFF: 'onoff'>,
'friendly_name': 'Test Light Light 4',
'supported_color_modes': list([
<ColorMode.ONOFF: 'onoff'>,
]),
'supported_features': <LightEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'light.test_light_light_4',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

View File

@ -0,0 +1,158 @@
"""Test homee lights."""
from typing import Any
from unittest.mock import MagicMock, call, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_HS_COLOR,
DOMAIN as LIGHT_DOMAIN,
SERVICE_TOGGLE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import build_mock_node, setup_integration
from tests.common import MockConfigEntry, snapshot_platform
def mock_attribute_map(attributes) -> dict:
"""Mock the attribute map of a Homee node."""
attribute_map = {}
for a in attributes:
attribute_map[a.type] = a
return attribute_map
async def setup_mock_light(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
file: str,
) -> None:
"""Setups the light node for the tests."""
mock_homee.nodes = [build_mock_node(file)]
mock_homee.nodes[0].attribute_map = mock_attribute_map(
mock_homee.nodes[0].attributes
)
await setup_integration(hass, mock_config_entry)
@pytest.mark.parametrize(
("data", "calls"),
[
({}, [call(1, 1, 1)]),
({ATTR_BRIGHTNESS: 255}, [call(1, 2, 100)]),
(
{
ATTR_BRIGHTNESS: 255,
ATTR_COLOR_TEMP_KELVIN: 4300,
},
[call(1, 2, 100), call(1, 4, 4300)],
),
({ATTR_HS_COLOR: (100, 100)}, [call(1, 1, 1), call(1, 3, 5635840)]),
],
)
async def test_turn_on(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
data: dict[str, Any],
calls: list[call],
) -> None:
"""Test turning on the light."""
await setup_mock_light(hass, mock_homee, mock_config_entry, "lights.json")
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.test_light_light_1"} | data,
blocking=True,
)
assert mock_homee.set_value.call_args_list == calls
async def test_turn_off(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test turning off a light."""
await setup_mock_light(hass, mock_homee, mock_config_entry, "lights.json")
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{
ATTR_ENTITY_ID: "light.test_light_light_1",
},
blocking=True,
)
mock_homee.set_value.assert_called_once_with(1, 1, 0)
async def test_toggle(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test toggling a light."""
await setup_mock_light(hass, mock_homee, mock_config_entry, "lights.json")
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TOGGLE,
{
ATTR_ENTITY_ID: "light.test_light_light_1",
},
blocking=True,
)
mock_homee.set_value.assert_called_once_with(1, 1, 0)
mock_homee.nodes[0].attributes[0].current_value = 0.0
mock_homee.nodes[0].add_on_changed_listener.call_args_list[0][0][0](
mock_homee.nodes[0]
)
await hass.async_block_till_done()
mock_homee.reset_mock()
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TOGGLE,
{
ATTR_ENTITY_ID: "light.test_light_light_1",
},
blocking=True,
)
mock_homee.set_value.assert_called_once_with(1, 1, 1)
async def test_light_snapshot(
hass: HomeAssistant,
mock_homee: MagicMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test snapshot of lights."""
mock_homee.nodes = [
build_mock_node("lights.json"),
build_mock_node("light_single.json"),
]
for i in range(2):
mock_homee.nodes[i].attribute_map = mock_attribute_map(
mock_homee.nodes[i].attributes
)
with patch("homeassistant.components.homee.PLATFORMS", [Platform.LIGHT]):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)