mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add light.group platform (#12229)
* Add grouped_light platform * 📝 Fix Lint issues * 🎨 Reformat code with yapf * A Few changes * ✨ Python 3.5 magic * Improvements Included the comments from #11323 * Fixes * Updates * Fixes & Tests * Fix bad-whitespace * Domain Config Validation ... by rebasing onto #12592 * Style changes & Improvements * Lint * Changes according to Review Comments * Use blocking light.async_turn_* * Revert "Use blocking light.async_turn_*" This reverts commit 9e83198552af9347aede9efb547f91793275cc5f. * Update service calls and state reporting * Add group service call tests * Remove unused constant.
This commit is contained in:
parent
168e1f0e2d
commit
03970764d8
@ -409,6 +409,7 @@ omit =
|
||||
homeassistant/components/light/decora_wifi.py
|
||||
homeassistant/components/light/flux_led.py
|
||||
homeassistant/components/light/greenwave.py
|
||||
homeassistant/components/light/group.py
|
||||
homeassistant/components/light/hue.py
|
||||
homeassistant/components/light/hyperion.py
|
||||
homeassistant/components/light/iglo.py
|
||||
|
@ -12,7 +12,8 @@ import os
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import group
|
||||
from homeassistant.components.group import \
|
||||
ENTITY_ID_FORMAT as GROUP_ENTITY_ID_FORMAT
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
STATE_ON)
|
||||
@ -30,7 +31,7 @@ DEPENDENCIES = ['group']
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
GROUP_NAME_ALL_LIGHTS = 'all lights'
|
||||
ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format('all_lights')
|
||||
ENTITY_ID_ALL_LIGHTS = GROUP_ENTITY_ID_FORMAT.format('all_lights')
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
@ -209,8 +210,9 @@ def async_turn_off(hass, entity_id=None, transition=None):
|
||||
DOMAIN, SERVICE_TURN_OFF, data))
|
||||
|
||||
|
||||
@callback
|
||||
@bind_hass
|
||||
def toggle(hass, entity_id=None, transition=None):
|
||||
def async_toggle(hass, entity_id=None, transition=None):
|
||||
"""Toggle all or specified light."""
|
||||
data = {
|
||||
key: value for key, value in [
|
||||
@ -219,7 +221,14 @@ def toggle(hass, entity_id=None, transition=None):
|
||||
] if value is not None
|
||||
}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
|
||||
hass.async_add_job(hass.services.async_call(
|
||||
DOMAIN, SERVICE_TOGGLE, data))
|
||||
|
||||
|
||||
@bind_hass
|
||||
def toggle(hass, entity_id=None, transition=None):
|
||||
"""Toggle all or specified light."""
|
||||
hass.add_job(async_toggle, hass, entity_id, transition)
|
||||
|
||||
|
||||
def preprocess_turn_on_alternatives(params):
|
||||
|
289
homeassistant/components/light/group.py
Normal file
289
homeassistant/components/light/group.py
Normal file
@ -0,0 +1,289 @@
|
||||
"""
|
||||
This component allows several lights to be grouped into one light.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/light.group/
|
||||
"""
|
||||
import logging
|
||||
import itertools
|
||||
from typing import List, Tuple, Optional, Iterator, Any, Callable
|
||||
from collections import Counter
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import State, callback
|
||||
from homeassistant.components import light
|
||||
from homeassistant.const import (STATE_ON, ATTR_ENTITY_ID, CONF_NAME,
|
||||
CONF_ENTITIES, STATE_UNAVAILABLE,
|
||||
ATTR_SUPPORTED_FEATURES)
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
||||
from homeassistant.components.light import (
|
||||
SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR, SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_TRANSITION, SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_XY_COLOR,
|
||||
SUPPORT_WHITE_VALUE, PLATFORM_SCHEMA, ATTR_BRIGHTNESS, ATTR_XY_COLOR,
|
||||
ATTR_RGB_COLOR, ATTR_WHITE_VALUE, ATTR_COLOR_TEMP, ATTR_MIN_MIREDS,
|
||||
ATTR_MAX_MIREDS, ATTR_EFFECT_LIST, ATTR_EFFECT, ATTR_FLASH,
|
||||
ATTR_TRANSITION)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'Group Light'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Required(CONF_ENTITIES): cv.entities_domain('light')
|
||||
})
|
||||
|
||||
SUPPORT_GROUP_LIGHT = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT
|
||||
| SUPPORT_FLASH | SUPPORT_RGB_COLOR | SUPPORT_TRANSITION
|
||||
| SUPPORT_XY_COLOR | SUPPORT_WHITE_VALUE)
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
||||
async_add_devices, discovery_info=None) -> None:
|
||||
"""Initialize light.group platform."""
|
||||
async_add_devices([GroupLight(config.get(CONF_NAME),
|
||||
config[CONF_ENTITIES])])
|
||||
|
||||
|
||||
class GroupLight(light.Light):
|
||||
"""Representation of a group light."""
|
||||
|
||||
def __init__(self, name: str, entity_ids: List[str]) -> None:
|
||||
"""Initialize a group light."""
|
||||
self._name = name # type: str
|
||||
self._entity_ids = entity_ids # type: List[str]
|
||||
self._is_on = False # type: bool
|
||||
self._available = False # type: bool
|
||||
self._brightness = None # type: Optional[int]
|
||||
self._xy_color = None # type: Optional[Tuple[float, float]]
|
||||
self._rgb_color = None # type: Optional[Tuple[int, int, int]]
|
||||
self._color_temp = None # type: Optional[int]
|
||||
self._min_mireds = 154 # type: Optional[int]
|
||||
self._max_mireds = 500 # type: Optional[int]
|
||||
self._white_value = None # type: Optional[int]
|
||||
self._effect_list = None # type: Optional[List[str]]
|
||||
self._effect = None # type: Optional[str]
|
||||
self._supported_features = 0 # type: int
|
||||
self._async_unsub_state_changed = None
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
@callback
|
||||
def async_state_changed_listener(entity_id: str, old_state: State,
|
||||
new_state: State):
|
||||
"""Handle child updates."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
self._async_unsub_state_changed = async_track_state_change(
|
||||
self.hass, self._entity_ids, async_state_changed_listener)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Callback when removed from HASS."""
|
||||
if self._async_unsub_state_changed:
|
||||
self._async_unsub_state_changed()
|
||||
self._async_unsub_state_changed = None
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the on/off state of the light."""
|
||||
return self._is_on
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return whether the light is available."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def brightness(self) -> Optional[int]:
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def xy_color(self) -> Optional[Tuple[float, float]]:
|
||||
"""Return the XY color value [float, float]."""
|
||||
return self._xy_color
|
||||
|
||||
@property
|
||||
def rgb_color(self) -> Optional[Tuple[int, int, int]]:
|
||||
"""Return the RGB color value [int, int, int]."""
|
||||
return self._rgb_color
|
||||
|
||||
@property
|
||||
def color_temp(self) -> Optional[int]:
|
||||
"""Return the CT color value in mireds."""
|
||||
return self._color_temp
|
||||
|
||||
@property
|
||||
def min_mireds(self) -> Optional[int]:
|
||||
"""Return the coldest color_temp that this light supports."""
|
||||
return self._min_mireds
|
||||
|
||||
@property
|
||||
def max_mireds(self) -> Optional[int]:
|
||||
"""Return the warmest color_temp that this light supports."""
|
||||
return self._max_mireds
|
||||
|
||||
@property
|
||||
def white_value(self) -> Optional[int]:
|
||||
"""Return the white value of this light between 0..255."""
|
||||
return self._white_value
|
||||
|
||||
@property
|
||||
def effect_list(self) -> Optional[List[str]]:
|
||||
"""Return the list of supported effects."""
|
||||
return self._effect_list
|
||||
|
||||
@property
|
||||
def effect(self) -> Optional[str]:
|
||||
"""Return the current effect."""
|
||||
return self._effect
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
return self._supported_features
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""No polling needed for a group light."""
|
||||
return False
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Forward the turn_on command to all lights in the group."""
|
||||
data = {ATTR_ENTITY_ID: self._entity_ids}
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS]
|
||||
|
||||
if ATTR_XY_COLOR in kwargs:
|
||||
data[ATTR_XY_COLOR] = kwargs[ATTR_XY_COLOR]
|
||||
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
data[ATTR_RGB_COLOR] = kwargs[ATTR_RGB_COLOR]
|
||||
|
||||
if ATTR_COLOR_TEMP in kwargs:
|
||||
data[ATTR_COLOR_TEMP] = kwargs[ATTR_COLOR_TEMP]
|
||||
|
||||
if ATTR_WHITE_VALUE in kwargs:
|
||||
data[ATTR_WHITE_VALUE] = kwargs[ATTR_WHITE_VALUE]
|
||||
|
||||
if ATTR_EFFECT in kwargs:
|
||||
data[ATTR_EFFECT] = kwargs[ATTR_EFFECT]
|
||||
|
||||
if ATTR_TRANSITION in kwargs:
|
||||
data[ATTR_TRANSITION] = kwargs[ATTR_TRANSITION]
|
||||
|
||||
if ATTR_FLASH in kwargs:
|
||||
data[ATTR_FLASH] = kwargs[ATTR_FLASH]
|
||||
|
||||
await self.hass.services.async_call(
|
||||
light.DOMAIN, light.SERVICE_TURN_ON, data, blocking=True)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Forward the turn_off command to all lights in the group."""
|
||||
data = {ATTR_ENTITY_ID: self._entity_ids}
|
||||
|
||||
if ATTR_TRANSITION in kwargs:
|
||||
data[ATTR_TRANSITION] = kwargs[ATTR_TRANSITION]
|
||||
|
||||
await self.hass.services.async_call(
|
||||
light.DOMAIN, light.SERVICE_TURN_OFF, data, blocking=True)
|
||||
|
||||
async def async_update(self):
|
||||
"""Query all members and determine the group state."""
|
||||
all_states = [self.hass.states.get(x) for x in self._entity_ids]
|
||||
states = list(filter(None, all_states))
|
||||
on_states = [state for state in states if state.state == STATE_ON]
|
||||
|
||||
self._is_on = len(on_states) > 0
|
||||
self._available = any(state.state != STATE_UNAVAILABLE
|
||||
for state in states)
|
||||
|
||||
self._brightness = _reduce_attribute(on_states, ATTR_BRIGHTNESS)
|
||||
|
||||
self._xy_color = _reduce_attribute(
|
||||
on_states, ATTR_XY_COLOR, reduce=_mean_tuple)
|
||||
|
||||
self._rgb_color = _reduce_attribute(
|
||||
on_states, ATTR_RGB_COLOR, reduce=_mean_tuple)
|
||||
if self._rgb_color is not None:
|
||||
self._rgb_color = tuple(map(int, self._rgb_color))
|
||||
|
||||
self._white_value = _reduce_attribute(on_states, ATTR_WHITE_VALUE)
|
||||
|
||||
self._color_temp = _reduce_attribute(on_states, ATTR_COLOR_TEMP)
|
||||
self._min_mireds = _reduce_attribute(
|
||||
states, ATTR_MIN_MIREDS, default=154, reduce=min)
|
||||
self._max_mireds = _reduce_attribute(
|
||||
states, ATTR_MAX_MIREDS, default=500, reduce=max)
|
||||
|
||||
self._effect_list = None
|
||||
all_effect_lists = list(
|
||||
_find_state_attributes(states, ATTR_EFFECT_LIST))
|
||||
if all_effect_lists:
|
||||
# Merge all effects from all effect_lists with a union merge.
|
||||
self._effect_list = list(set().union(*all_effect_lists))
|
||||
|
||||
self._effect = None
|
||||
all_effects = list(_find_state_attributes(on_states, ATTR_EFFECT))
|
||||
if all_effects:
|
||||
# Report the most common effect.
|
||||
effects_count = Counter(itertools.chain(all_effects))
|
||||
self._effect = effects_count.most_common(1)[0][0]
|
||||
|
||||
self._supported_features = 0
|
||||
for support in _find_state_attributes(states, ATTR_SUPPORTED_FEATURES):
|
||||
# Merge supported features by emulating support for every feature
|
||||
# we find.
|
||||
self._supported_features |= support
|
||||
# Bitwise-and the supported features with the GroupedLight's features
|
||||
# so that we don't break in the future when a new feature is added.
|
||||
self._supported_features &= SUPPORT_GROUP_LIGHT
|
||||
|
||||
|
||||
def _find_state_attributes(states: List[State],
|
||||
key: str) -> Iterator[Any]:
|
||||
"""Find attributes with matching key from states."""
|
||||
for state in states:
|
||||
value = state.attributes.get(key)
|
||||
if value is not None:
|
||||
yield value
|
||||
|
||||
|
||||
def _mean_int(*args):
|
||||
"""Return the mean of the supplied values."""
|
||||
return int(sum(args) / len(args))
|
||||
|
||||
|
||||
def _mean_tuple(*args):
|
||||
"""Return the mean values along the columns of the supplied values."""
|
||||
return tuple(sum(l) / len(l) for l in zip(*args))
|
||||
|
||||
|
||||
# https://github.com/PyCQA/pylint/issues/1831
|
||||
# pylint: disable=bad-whitespace
|
||||
def _reduce_attribute(states: List[State],
|
||||
key: str,
|
||||
default: Optional[Any] = None,
|
||||
reduce: Callable[..., Any] = _mean_int) -> Any:
|
||||
"""Find the first attribute matching key from states.
|
||||
|
||||
If none are found, return default.
|
||||
"""
|
||||
attrs = list(_find_state_attributes(states, key))
|
||||
|
||||
if not attrs:
|
||||
return default
|
||||
|
||||
if len(attrs) == 1:
|
||||
return attrs[0]
|
||||
|
||||
return reduce(*attrs)
|
417
tests/components/light/test_group.py
Normal file
417
tests/components/light/test_group.py
Normal file
@ -0,0 +1,417 @@
|
||||
"""The tests for the Group Light platform."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import asynctest
|
||||
|
||||
from homeassistant.components import light
|
||||
from homeassistant.components.light import group
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
async def test_default_state(hass):
|
||||
"""Test light group default state."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': [], 'name': 'Bedroom Group'
|
||||
}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get('light.bedroom_group')
|
||||
assert state is not None
|
||||
assert state.state == 'unavailable'
|
||||
assert state.attributes['supported_features'] == 0
|
||||
assert state.attributes.get('brightness') is None
|
||||
assert state.attributes.get('rgb_color') is None
|
||||
assert state.attributes.get('xy_color') is None
|
||||
assert state.attributes.get('color_temp') is None
|
||||
assert state.attributes.get('white_value') is None
|
||||
assert state.attributes.get('effect_list') is None
|
||||
assert state.attributes.get('effect') is None
|
||||
|
||||
|
||||
async def test_state_reporting(hass):
|
||||
"""Test the state reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on')
|
||||
hass.states.async_set('light.test2', 'unavailable')
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get('light.group_light').state == 'on'
|
||||
|
||||
hass.states.async_set('light.test1', 'on')
|
||||
hass.states.async_set('light.test2', 'off')
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get('light.group_light').state == 'on'
|
||||
|
||||
hass.states.async_set('light.test1', 'off')
|
||||
hass.states.async_set('light.test2', 'off')
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get('light.group_light').state == 'off'
|
||||
|
||||
hass.states.async_set('light.test1', 'unavailable')
|
||||
hass.states.async_set('light.test2', 'unavailable')
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get('light.group_light').state == 'unavailable'
|
||||
|
||||
|
||||
async def test_brightness(hass):
|
||||
"""Test brightness reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'brightness': 255, 'supported_features': 1})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['supported_features'] == 1
|
||||
assert state.attributes['brightness'] == 255
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'brightness': 100, 'supported_features': 1})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['brightness'] == 177
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'brightness': 255, 'supported_features': 1})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['supported_features'] == 1
|
||||
assert state.attributes['brightness'] == 100
|
||||
|
||||
|
||||
async def test_xy_color(hass):
|
||||
"""Test XY reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'xy_color': (1.0, 1.0), 'supported_features': 64})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['supported_features'] == 64
|
||||
assert state.attributes['xy_color'] == (1.0, 1.0)
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'xy_color': (0.5, 0.5), 'supported_features': 64})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['xy_color'] == (0.75, 0.75)
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'xy_color': (1.0, 1.0), 'supported_features': 64})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['xy_color'] == (0.5, 0.5)
|
||||
|
||||
|
||||
async def test_rgb_color(hass):
|
||||
"""Test RGB reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'rgb_color': (255, 0, 0), 'supported_features': 16})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['supported_features'] == 16
|
||||
assert state.attributes['rgb_color'] == (255, 0, 0)
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'rgb_color': (255, 255, 255),
|
||||
'supported_features': 16})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['rgb_color'] == (255, 127, 127)
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'rgb_color': (255, 0, 0), 'supported_features': 16})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['rgb_color'] == (255, 255, 255)
|
||||
|
||||
|
||||
async def test_white_value(hass):
|
||||
"""Test white value reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'white_value': 255, 'supported_features': 128})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['white_value'] == 255
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'white_value': 100, 'supported_features': 128})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['white_value'] == 177
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'white_value': 255, 'supported_features': 128})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['white_value'] == 100
|
||||
|
||||
|
||||
async def test_color_temp(hass):
|
||||
"""Test color temp reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'color_temp': 2, 'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['color_temp'] == 2
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'color_temp': 1000, 'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['color_temp'] == 501
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'color_temp': 2, 'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['color_temp'] == 1000
|
||||
|
||||
|
||||
async def test_min_max_mireds(hass):
|
||||
"""Test min/max mireds reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'min_mireds': 2, 'max_mireds': 5,
|
||||
'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['min_mireds'] == 2
|
||||
assert state.attributes['max_mireds'] == 5
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'min_mireds': 7, 'max_mireds': 1234567890,
|
||||
'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['min_mireds'] == 2
|
||||
assert state.attributes['max_mireds'] == 1234567890
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'min_mireds': 1, 'max_mireds': 2,
|
||||
'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['min_mireds'] == 1
|
||||
assert state.attributes['max_mireds'] == 1234567890
|
||||
|
||||
|
||||
async def test_effect_list(hass):
|
||||
"""Test effect_list reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'effect_list': ['None', 'Random', 'Colorloop']})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert set(state.attributes['effect_list']) == {
|
||||
'None', 'Random', 'Colorloop'}
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'effect_list': ['None', 'Random', 'Rainbow']})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert set(state.attributes['effect_list']) == {
|
||||
'None', 'Random', 'Colorloop', 'Rainbow'}
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'effect_list': ['None', 'Colorloop', 'Seven']})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert set(state.attributes['effect_list']) == {
|
||||
'None', 'Random', 'Colorloop', 'Seven', 'Rainbow'}
|
||||
|
||||
|
||||
async def test_effect(hass):
|
||||
"""Test effect reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2',
|
||||
'light.test3']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'effect': 'None', 'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['effect'] == 'None'
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'effect': 'None', 'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['effect'] == 'None'
|
||||
|
||||
hass.states.async_set('light.test3', 'on',
|
||||
{'effect': 'Random', 'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['effect'] == 'None'
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'effect': 'None', 'supported_features': 2})
|
||||
hass.states.async_set('light.test2', 'off',
|
||||
{'effect': 'None', 'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['effect'] == 'Random'
|
||||
|
||||
|
||||
async def test_supported_features(hass):
|
||||
"""Test supported features reporting."""
|
||||
await async_setup_component(hass, 'light', {'light': {
|
||||
'platform': 'group', 'entities': ['light.test1', 'light.test2']
|
||||
}})
|
||||
|
||||
hass.states.async_set('light.test1', 'on',
|
||||
{'supported_features': 0})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['supported_features'] == 0
|
||||
|
||||
hass.states.async_set('light.test2', 'on',
|
||||
{'supported_features': 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['supported_features'] == 2
|
||||
|
||||
hass.states.async_set('light.test1', 'off',
|
||||
{'supported_features': 41})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['supported_features'] == 43
|
||||
|
||||
hass.states.async_set('light.test2', 'off',
|
||||
{'supported_features': 256})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('light.group_light')
|
||||
assert state.attributes['supported_features'] == 41
|
||||
|
||||
|
||||
async def test_service_calls(hass):
|
||||
"""Test service calls."""
|
||||
await async_setup_component(hass, 'light', {'light': [
|
||||
{'platform': 'demo'},
|
||||
{'platform': 'group', 'entities': ['light.bed_light',
|
||||
'light.ceiling_lights',
|
||||
'light.kitchen_lights']}
|
||||
]})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get('light.group_light').state == 'on'
|
||||
light.async_toggle(hass, 'light.group_light')
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get('light.bed_light').state == 'off'
|
||||
assert hass.states.get('light.ceiling_lights').state == 'off'
|
||||
assert hass.states.get('light.kitchen_lights').state == 'off'
|
||||
|
||||
light.async_turn_on(hass, 'light.group_light')
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get('light.bed_light').state == 'on'
|
||||
assert hass.states.get('light.ceiling_lights').state == 'on'
|
||||
assert hass.states.get('light.kitchen_lights').state == 'on'
|
||||
|
||||
light.async_turn_off(hass, 'light.group_light')
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get('light.bed_light').state == 'off'
|
||||
assert hass.states.get('light.ceiling_lights').state == 'off'
|
||||
assert hass.states.get('light.kitchen_lights').state == 'off'
|
||||
|
||||
light.async_turn_on(hass, 'light.group_light', brightness=128,
|
||||
effect='Random', rgb_color=(42, 255, 255))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get('light.bed_light')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['brightness'] == 128
|
||||
assert state.attributes['effect'] == 'Random'
|
||||
assert state.attributes['rgb_color'] == (42, 255, 255)
|
||||
|
||||
state = hass.states.get('light.ceiling_lights')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['brightness'] == 128
|
||||
assert state.attributes['effect'] == 'Random'
|
||||
assert state.attributes['rgb_color'] == (42, 255, 255)
|
||||
|
||||
state = hass.states.get('light.kitchen_lights')
|
||||
assert state.state == 'on'
|
||||
assert state.attributes['brightness'] == 128
|
||||
assert state.attributes['effect'] == 'Random'
|
||||
assert state.attributes['rgb_color'] == (42, 255, 255)
|
||||
|
||||
|
||||
async def test_invalid_service_calls(hass):
|
||||
"""Test invalid service call arguments get discarded."""
|
||||
add_devices = MagicMock()
|
||||
await group.async_setup_platform(hass, {
|
||||
'entities': ['light.test1', 'light.test2']
|
||||
}, add_devices)
|
||||
|
||||
assert add_devices.call_count == 1
|
||||
grouped_light = add_devices.call_args[0][0][0]
|
||||
grouped_light.hass = hass
|
||||
|
||||
with asynctest.patch.object(hass.services, 'async_call') as mock_call:
|
||||
await grouped_light.async_turn_on(brightness=150, four_oh_four='404')
|
||||
data = {
|
||||
'entity_id': ['light.test1', 'light.test2'],
|
||||
'brightness': 150
|
||||
}
|
||||
mock_call.assert_called_once_with('light', 'turn_on', data,
|
||||
blocking=True)
|
||||
mock_call.reset_mock()
|
||||
|
||||
await grouped_light.async_turn_off(transition=4, four_oh_four='404')
|
||||
data = {
|
||||
'entity_id': ['light.test1', 'light.test2'],
|
||||
'transition': 4
|
||||
}
|
||||
mock_call.assert_called_once_with('light', 'turn_off', data,
|
||||
blocking=True)
|
||||
mock_call.reset_mock()
|
||||
|
||||
data = {
|
||||
'brightness': 150,
|
||||
'xy_color': (0.5, 0.42),
|
||||
'rgb_color': (80, 120, 50),
|
||||
'color_temp': 1234,
|
||||
'white_value': 1,
|
||||
'effect': 'Sunshine',
|
||||
'transition': 4,
|
||||
'flash': 'long'
|
||||
}
|
||||
await grouped_light.async_turn_on(**data)
|
||||
data['entity_id'] = ['light.test1', 'light.test2']
|
||||
mock_call.assert_called_once_with('light', 'turn_on', data,
|
||||
blocking=True)
|
Loading…
x
Reference in New Issue
Block a user