mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add light platform to Homee (#138776)
This commit is contained in:
parent
ec7ec993b0
commit
5d1eb69281
@ -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]
|
||||
|
||||
|
@ -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,
|
||||
|
213
homeassistant/components/homee/light.py
Normal file
213
homeassistant/components/homee/light.py
Normal 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
|
@ -61,6 +61,11 @@
|
||||
"name": "Ventilate"
|
||||
}
|
||||
},
|
||||
"light": {
|
||||
"light_instance": {
|
||||
"name": "Light {instance}"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"brightness_instance": {
|
||||
"name": "Illuminance {instance}"
|
||||
|
102
tests/components/homee/fixtures/light_single.json
Normal file
102
tests/components/homee/fixtures/light_single.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
333
tests/components/homee/fixtures/lights.json
Normal file
333
tests/components/homee/fixtures/lights.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
348
tests/components/homee/snapshots/test_light.ambr
Normal file
348
tests/components/homee/snapshots/test_light.ambr
Normal 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',
|
||||
})
|
||||
# ---
|
158
tests/components/homee/test_light.py
Normal file
158
tests/components/homee/test_light.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user