Add all option to light group (#68447)
Co-authored-by: Franck Nijhof <frenck@frenck.nl> Co-authored-by: Erik Montnemery <erik@montnemery.com>pull/68512/head
parent
6a66b4dbff
commit
0720b0f891
|
@ -46,10 +46,20 @@ BINARY_SENSOR_OPTIONS_SCHEMA = basic_group_options_schema("binary_sensor").exten
|
|||
}
|
||||
)
|
||||
|
||||
LIGHT_OPTIONS_SCHEMA = basic_group_options_schema("light").extend(
|
||||
{
|
||||
vol.Required(CONF_ALL, default=False): selector.selector({"boolean": {}}),
|
||||
}
|
||||
)
|
||||
|
||||
BINARY_SENSOR_CONFIG_SCHEMA = vol.Schema(
|
||||
{vol.Required("name"): selector.selector({"text": {}})}
|
||||
).extend(BINARY_SENSOR_OPTIONS_SCHEMA.schema)
|
||||
|
||||
LIGHT_CONFIG_SCHEMA = vol.Schema(
|
||||
{vol.Required("name"): selector.selector({"text": {}})}
|
||||
).extend(LIGHT_OPTIONS_SCHEMA.schema)
|
||||
|
||||
|
||||
INITIAL_STEP_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -81,7 +91,7 @@ CONFIG_FLOW = {
|
|||
"binary_sensor": HelperFlowStep(BINARY_SENSOR_CONFIG_SCHEMA),
|
||||
"cover": HelperFlowStep(basic_group_config_schema("cover")),
|
||||
"fan": HelperFlowStep(basic_group_config_schema("fan")),
|
||||
"light": HelperFlowStep(basic_group_config_schema("light")),
|
||||
"light": HelperFlowStep(LIGHT_CONFIG_SCHEMA),
|
||||
"media_player": HelperFlowStep(basic_group_config_schema("media_player")),
|
||||
}
|
||||
|
||||
|
@ -91,7 +101,7 @@ OPTIONS_FLOW = {
|
|||
"binary_sensor": HelperFlowStep(BINARY_SENSOR_OPTIONS_SCHEMA),
|
||||
"cover": HelperFlowStep(basic_group_options_schema("cover")),
|
||||
"fan": HelperFlowStep(basic_group_options_schema("fan")),
|
||||
"light": HelperFlowStep(basic_group_options_schema("light")),
|
||||
"light": HelperFlowStep(LIGHT_OPTIONS_SCHEMA),
|
||||
"media_player": HelperFlowStep(basic_group_options_schema("media_player")),
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ from homeassistant.const import (
|
|||
SERVICE_TURN_ON,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, State, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
|
@ -58,6 +59,7 @@ from . import GroupEntity
|
|||
from .util import find_state_attributes, mean_tuple, reduce_attribute
|
||||
|
||||
DEFAULT_NAME = "Light Group"
|
||||
CONF_ALL = "all"
|
||||
|
||||
# No limit on parallel updates to enable a group calling another group
|
||||
PARALLEL_UPDATES = 0
|
||||
|
@ -67,6 +69,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Required(CONF_ENTITIES): cv.entities_domain(light.DOMAIN),
|
||||
vol.Optional(CONF_ALL): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -87,7 +90,10 @@ async def async_setup_platform(
|
|||
async_add_entities(
|
||||
[
|
||||
LightGroup(
|
||||
config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES]
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config[CONF_NAME],
|
||||
config[CONF_ENTITIES],
|
||||
config.get(CONF_ALL),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
@ -103,9 +109,10 @@ async def async_setup_entry(
|
|||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
mode = config_entry.options[CONF_ALL]
|
||||
|
||||
async_add_entities(
|
||||
[LightGroup(config_entry.entry_id, config_entry.title, entities)]
|
||||
[LightGroup(config_entry.entry_id, config_entry.title, entities, mode)]
|
||||
)
|
||||
|
||||
|
||||
|
@ -132,12 +139,13 @@ class LightGroup(GroupEntity, LightEntity):
|
|||
|
||||
_attr_available = False
|
||||
_attr_icon = "mdi:lightbulb-group"
|
||||
_attr_is_on = False
|
||||
_attr_max_mireds = 500
|
||||
_attr_min_mireds = 154
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, unique_id: str | None, name: str, entity_ids: list[str]) -> None:
|
||||
def __init__(
|
||||
self, unique_id: str | None, name: str, entity_ids: list[str], mode: str | None
|
||||
) -> None:
|
||||
"""Initialize a light group."""
|
||||
self._entity_ids = entity_ids
|
||||
self._white_value: int | None = None
|
||||
|
@ -145,6 +153,9 @@ class LightGroup(GroupEntity, LightEntity):
|
|||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||
self._attr_unique_id = unique_id
|
||||
self.mode = any
|
||||
if mode:
|
||||
self.mode = all
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
|
@ -207,7 +218,22 @@ class LightGroup(GroupEntity, LightEntity):
|
|||
states: list[State] = list(filter(None, all_states))
|
||||
on_states = [state for state in states if state.state == STATE_ON]
|
||||
|
||||
self._attr_is_on = len(on_states) > 0
|
||||
# filtered_states are members currently in the state machine
|
||||
filtered_states: list[str] = [x.state for x in all_states if x is not None]
|
||||
|
||||
valid_state = self.mode(
|
||||
state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) for state in filtered_states
|
||||
)
|
||||
|
||||
if not valid_state:
|
||||
# Set as unknown if any / all member is unknown or unavailable
|
||||
self._attr_is_on = None
|
||||
else:
|
||||
# Set as ON if any / all member is ON
|
||||
self._attr_is_on = self.mode(
|
||||
list(map(lambda x: x == STATE_ON, filtered_states))
|
||||
)
|
||||
|
||||
self._attr_available = any(state.state != STATE_UNAVAILABLE for state in states)
|
||||
self._attr_brightness = reduce_attribute(on_states, ATTR_BRIGHTNESS)
|
||||
|
||||
|
|
|
@ -32,7 +32,9 @@
|
|||
}
|
||||
},
|
||||
"light": {
|
||||
"description": "[%key:component::group::config::step::binary_sensor::description%]",
|
||||
"data": {
|
||||
"all": "[%key:component::group::config::step::binary_sensor::data::all%]",
|
||||
"title": "[%key:component::group::config::step::user::title%]",
|
||||
"entities": "[%key:component::group::config::step::binary_sensor::data::entities%]",
|
||||
"name": "[%key:component::group::config::step::binary_sensor::data::name%]"
|
||||
|
@ -67,6 +69,7 @@
|
|||
},
|
||||
"light_options": {
|
||||
"data": {
|
||||
"all": "[%key:component::group::config::step::binary_sensor::data::all%]",
|
||||
"entities": "[%key:component::group::config::step::binary_sensor::data::entities%]"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -15,62 +15,30 @@
|
|||
"entities": "Members",
|
||||
"name": "Name",
|
||||
"title": "New Group"
|
||||
},
|
||||
"description": "Select group options"
|
||||
},
|
||||
"cover_options": {
|
||||
"data": {
|
||||
"entities": "Group members"
|
||||
},
|
||||
"description": "Select group options"
|
||||
}
|
||||
},
|
||||
"fan": {
|
||||
"data": {
|
||||
"entities": "Members",
|
||||
"name": "Name",
|
||||
"title": "New Group"
|
||||
},
|
||||
"description": "Select group options"
|
||||
},
|
||||
"fan_options": {
|
||||
"data": {
|
||||
"entities": "Group members"
|
||||
},
|
||||
"description": "Select group options"
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
"group_type": "Group type"
|
||||
},
|
||||
"description": "Select group type"
|
||||
}
|
||||
},
|
||||
"light": {
|
||||
"data": {
|
||||
"all": "All entities",
|
||||
"entities": "Members",
|
||||
"name": "Name",
|
||||
"title": "New Group"
|
||||
},
|
||||
"description": "Select group options"
|
||||
},
|
||||
"light_options": {
|
||||
"data": {
|
||||
"entities": "Group members"
|
||||
},
|
||||
"description": "Select group options"
|
||||
"description": "If \"all entities\" is enabled, the group's state is on only if all members are on. If \"all entities\" is disabled, the group's state is on if any member is on."
|
||||
},
|
||||
"media_player": {
|
||||
"data": {
|
||||
"entities": "Members",
|
||||
"name": "Name",
|
||||
"title": "New Group"
|
||||
},
|
||||
"description": "Select group options"
|
||||
},
|
||||
"media_player_options": {
|
||||
"data": {
|
||||
"entities": "Group members"
|
||||
},
|
||||
"description": "Select group options"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
|
@ -100,6 +68,7 @@
|
|||
},
|
||||
"light_options": {
|
||||
"data": {
|
||||
"all": "All entities",
|
||||
"entities": "Members"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -19,7 +19,8 @@ from tests.common import MockConfigEntry
|
|||
("binary_sensor", "on", "on", {}, {"all": True}, {"all": True}, {}),
|
||||
("cover", "open", "open", {}, {}, {}, {}),
|
||||
("fan", "on", "on", {}, {}, {}, {}),
|
||||
("light", "on", "on", {}, {}, {}, {}),
|
||||
("light", "on", "on", {}, {}, {"all": False}, {}),
|
||||
("light", "on", "on", {}, {"all": True}, {"all": True}, {}),
|
||||
("media_player", "on", "on", {}, {}, {}, {}),
|
||||
),
|
||||
)
|
||||
|
@ -174,7 +175,7 @@ def get_suggested(schema, key):
|
|||
("binary_sensor", "on", {"all": False}),
|
||||
("cover", "open", {}),
|
||||
("fan", "on", {}),
|
||||
("light", "on", {}),
|
||||
("light", "on", {"all": False}),
|
||||
("media_player", "on", {}),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1370,7 +1370,7 @@ async def test_plant_group(hass):
|
|||
("binary_sensor", "on", {"all": False}),
|
||||
("cover", "open", {}),
|
||||
("fan", "on", {}),
|
||||
("light", "on", {}),
|
||||
("light", "on", {"all": False}),
|
||||
("media_player", "on", {}),
|
||||
),
|
||||
)
|
||||
|
@ -1435,7 +1435,7 @@ async def test_setup_and_remove_config_entry(
|
|||
("binary_sensor", {"all": False}),
|
||||
("cover", {}),
|
||||
("fan", {}),
|
||||
("light", {}),
|
||||
("light", {"all": False}),
|
||||
("media_player", {}),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -49,6 +49,7 @@ from homeassistant.const import (
|
|||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
@ -68,6 +69,7 @@ async def test_default_state(hass):
|
|||
"entities": ["light.kitchen", "light.bedroom"],
|
||||
"name": "Bedroom Group",
|
||||
"unique_id": "unique_identifier",
|
||||
"all": "false",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -102,6 +104,7 @@ async def test_state_reporting(hass):
|
|||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -130,6 +133,49 @@ async def test_state_reporting(hass):
|
|||
assert hass.states.get("light.light_group").state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_state_reporting_all(hass):
|
||||
"""Test the state reporting."""
|
||||
await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "true",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.states.async_set("light.test1", STATE_ON)
|
||||
hass.states.async_set("light.test2", STATE_UNAVAILABLE)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("light.light_group").state == STATE_UNKNOWN
|
||||
|
||||
hass.states.async_set("light.test1", STATE_ON)
|
||||
hass.states.async_set("light.test2", STATE_OFF)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("light.light_group").state == STATE_OFF
|
||||
|
||||
hass.states.async_set("light.test1", STATE_OFF)
|
||||
hass.states.async_set("light.test2", STATE_OFF)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("light.light_group").state == STATE_OFF
|
||||
|
||||
hass.states.async_set("light.test1", STATE_ON)
|
||||
hass.states.async_set("light.test2", STATE_ON)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("light.light_group").state == STATE_ON
|
||||
|
||||
hass.states.async_set("light.test1", STATE_UNAVAILABLE)
|
||||
hass.states.async_set("light.test2", STATE_UNAVAILABLE)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("light.light_group").state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_brightness(hass, enable_custom_integrations):
|
||||
"""Test brightness reporting."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
|
@ -155,6 +201,7 @@ async def test_brightness(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -225,6 +272,7 @@ async def test_color_hs(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -296,6 +344,7 @@ async def test_color_rgb(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -367,6 +416,7 @@ async def test_color_rgbw(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -438,6 +488,7 @@ async def test_color_rgbww(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -489,6 +540,7 @@ async def test_white_value(hass):
|
|||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -548,6 +600,7 @@ async def test_white(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -603,6 +656,7 @@ async def test_color_temp(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -674,6 +728,7 @@ async def test_emulated_color_temp_group(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2", "light.test3"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -739,6 +794,7 @@ async def test_min_max_mireds(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -784,6 +840,7 @@ async def test_effect_list(hass):
|
|||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -843,6 +900,7 @@ async def test_effect(hass):
|
|||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2", "light.test3"],
|
||||
"all": "false",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -909,6 +967,7 @@ async def test_supported_color_modes(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2", "light.test3"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -957,6 +1016,7 @@ async def test_color_mode(hass, enable_custom_integrations):
|
|||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2", "light.test3"],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -1051,6 +1111,7 @@ async def test_color_mode2(hass, enable_custom_integrations):
|
|||
"light.test5",
|
||||
"light.test6",
|
||||
],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -1082,6 +1143,7 @@ async def test_supported_features(hass):
|
|||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
"all": "false",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -1157,6 +1219,7 @@ async def test_service_calls(hass, enable_custom_integrations, supported_color_m
|
|||
"light.ceiling_lights",
|
||||
"light.kitchen_lights",
|
||||
],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -1269,6 +1332,7 @@ async def test_service_call_effect(hass):
|
|||
"light.ceiling_lights",
|
||||
"light.kitchen_lights",
|
||||
],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -1369,6 +1433,7 @@ async def test_reload(hass):
|
|||
"light.ceiling_lights",
|
||||
"light.kitchen_lights",
|
||||
],
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -1481,11 +1546,13 @@ async def test_nested_group(hass):
|
|||
"platform": DOMAIN,
|
||||
"entities": ["light.bedroom_group"],
|
||||
"name": "Nested Group",
|
||||
"all": "false",
|
||||
},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.bed_light", "light.kitchen_lights"],
|
||||
"name": "Bedroom Group",
|
||||
"all": "false",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue