mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 18:27:09 +00:00
Add scene.apply service (#27298)
* Add scene.apply service * Use return value entity ID validator" * Require entities field in service call * Simplify scene service
This commit is contained in:
parent
a51e0d7a5f
commit
f5bd0f29b4
@ -26,6 +26,36 @@ from homeassistant.helpers import (
|
|||||||
from homeassistant.helpers.state import HASS_DOMAIN, async_reproduce_state
|
from homeassistant.helpers.state import HASS_DOMAIN, async_reproduce_state
|
||||||
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, STATES, Scene
|
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, STATES, Scene
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_states(states):
|
||||||
|
"""Convert state definitions to State objects."""
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for entity_id in states:
|
||||||
|
entity_id = cv.entity_id(entity_id)
|
||||||
|
|
||||||
|
if isinstance(states[entity_id], dict):
|
||||||
|
entity_attrs = states[entity_id].copy()
|
||||||
|
state = entity_attrs.pop(ATTR_STATE, None)
|
||||||
|
attributes = entity_attrs
|
||||||
|
else:
|
||||||
|
state = states[entity_id]
|
||||||
|
attributes = {}
|
||||||
|
|
||||||
|
# YAML translates 'on' to a boolean
|
||||||
|
# http://yaml.org/type/bool.html
|
||||||
|
if isinstance(state, bool):
|
||||||
|
state = STATE_ON if state else STATE_OFF
|
||||||
|
elif not isinstance(state, str):
|
||||||
|
raise vol.Invalid(f"State for {entity_id} should be a string")
|
||||||
|
|
||||||
|
result[entity_id] = State(entity_id, state, attributes)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
STATES_SCHEMA = vol.All(dict, _convert_states)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = vol.Schema(
|
PLATFORM_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_PLATFORM): HASS_DOMAIN,
|
vol.Required(CONF_PLATFORM): HASS_DOMAIN,
|
||||||
@ -34,9 +64,7 @@ PLATFORM_SCHEMA = vol.Schema(
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
vol.Required(CONF_NAME): cv.string,
|
vol.Required(CONF_NAME): cv.string,
|
||||||
vol.Required(CONF_ENTITIES): {
|
vol.Required(CONF_ENTITIES): STATES_SCHEMA,
|
||||||
cv.entity_id: vol.Any(str, bool, dict)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -44,6 +72,7 @@ PLATFORM_SCHEMA = vol.Schema(
|
|||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SERVICE_APPLY = "apply"
|
||||||
SCENECONFIG = namedtuple("SceneConfig", [CONF_NAME, STATES])
|
SCENECONFIG = namedtuple("SceneConfig", [CONF_NAME, STATES])
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -87,6 +116,19 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
SCENE_DOMAIN, SERVICE_RELOAD, reload_config
|
SCENE_DOMAIN, SERVICE_RELOAD, reload_config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def apply_service(call):
|
||||||
|
"""Apply a scene."""
|
||||||
|
await async_reproduce_state(
|
||||||
|
hass, call.data[CONF_ENTITIES].values(), blocking=True, context=call.context
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
SCENE_DOMAIN,
|
||||||
|
SERVICE_APPLY,
|
||||||
|
apply_service,
|
||||||
|
vol.Schema({vol.Required(CONF_ENTITIES): STATES_SCHEMA}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _process_scenes_config(hass, async_add_entities, config):
|
def _process_scenes_config(hass, async_add_entities, config):
|
||||||
"""Process multiple scenes and add them."""
|
"""Process multiple scenes and add them."""
|
||||||
@ -97,41 +139,11 @@ def _process_scenes_config(hass, async_add_entities, config):
|
|||||||
return
|
return
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
HomeAssistantScene(hass, _process_scene_config(scene)) for scene in scene_config
|
HomeAssistantScene(hass, SCENECONFIG(scene[CONF_NAME], scene[CONF_ENTITIES]))
|
||||||
|
for scene in scene_config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _process_scene_config(scene_config):
|
|
||||||
"""Process passed in config into a format to work with.
|
|
||||||
|
|
||||||
Async friendly.
|
|
||||||
"""
|
|
||||||
name = scene_config.get(CONF_NAME)
|
|
||||||
|
|
||||||
states = {}
|
|
||||||
c_entities = dict(scene_config.get(CONF_ENTITIES, {}))
|
|
||||||
|
|
||||||
for entity_id in c_entities:
|
|
||||||
if isinstance(c_entities[entity_id], dict):
|
|
||||||
entity_attrs = c_entities[entity_id].copy()
|
|
||||||
state = entity_attrs.pop(ATTR_STATE, None)
|
|
||||||
attributes = entity_attrs
|
|
||||||
else:
|
|
||||||
state = c_entities[entity_id]
|
|
||||||
attributes = {}
|
|
||||||
|
|
||||||
# YAML translates 'on' to a boolean
|
|
||||||
# http://yaml.org/type/bool.html
|
|
||||||
if isinstance(state, bool):
|
|
||||||
state = STATE_ON if state else STATE_OFF
|
|
||||||
else:
|
|
||||||
state = str(state)
|
|
||||||
|
|
||||||
states[entity_id.lower()] = State(entity_id, state, attributes)
|
|
||||||
|
|
||||||
return SCENECONFIG(name, states)
|
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantScene(Scene):
|
class HomeAssistantScene(Scene):
|
||||||
"""A scene is a group of entities and the states we want them to be."""
|
"""A scene is a group of entities and the states we want them to be."""
|
||||||
|
|
||||||
@ -148,8 +160,13 @@ class HomeAssistantScene(Scene):
|
|||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the scene state attributes."""
|
"""Return the scene state attributes."""
|
||||||
return {ATTR_ENTITY_ID: list(self.scene_config.states.keys())}
|
return {ATTR_ENTITY_ID: list(self.scene_config.states)}
|
||||||
|
|
||||||
async def async_activate(self):
|
async def async_activate(self):
|
||||||
"""Activate scene. Try to get entities into requested state."""
|
"""Activate scene. Try to get entities into requested state."""
|
||||||
await async_reproduce_state(self.hass, self.scene_config.states.values(), True)
|
await async_reproduce_state(
|
||||||
|
self.hass,
|
||||||
|
self.scene_config.states.values(),
|
||||||
|
blocking=True,
|
||||||
|
context=self._context,
|
||||||
|
)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Allow users to set and activate scenes."""
|
"""Allow users to set and activate scenes."""
|
||||||
import asyncio
|
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -7,7 +6,6 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.core import DOMAIN as HA_DOMAIN
|
from homeassistant.core import DOMAIN as HA_DOMAIN
|
||||||
from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON
|
from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON
|
||||||
from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.state import HASS_DOMAIN
|
from homeassistant.helpers.state import HASS_DOMAIN
|
||||||
@ -69,20 +67,7 @@ async def async_setup(hass, config):
|
|||||||
HA_DOMAIN, {"platform": "homeasistant", STATES: []}
|
HA_DOMAIN, {"platform": "homeasistant", STATES: []}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_handle_scene_service(service):
|
component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_activate")
|
||||||
"""Handle calls to the switch services."""
|
|
||||||
target_scenes = await component.async_extract_from_service(service)
|
|
||||||
|
|
||||||
tasks = [scene.async_activate() for scene in target_scenes]
|
|
||||||
if tasks:
|
|
||||||
await asyncio.wait(tasks)
|
|
||||||
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_TURN_ON,
|
|
||||||
async_handle_scene_service,
|
|
||||||
schema=ENTITY_SERVICE_SCHEMA,
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -5,4 +5,18 @@ turn_on:
|
|||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of scenes to turn on
|
description: Name(s) of scenes to turn on
|
||||||
example: 'scene.romantic'
|
example: "scene.romantic"
|
||||||
|
|
||||||
|
reload:
|
||||||
|
description: Reload the scene configuration
|
||||||
|
|
||||||
|
apply:
|
||||||
|
description: Activate a scene. Takes same data as the entities field from a single scene in the config.
|
||||||
|
fields:
|
||||||
|
entities:
|
||||||
|
description: The entities and the state that they need to be.
|
||||||
|
example:
|
||||||
|
light.kitchen: "on"
|
||||||
|
light.ceiling:
|
||||||
|
state: "on"
|
||||||
|
brightness: 80
|
||||||
|
@ -28,3 +28,26 @@ async def test_reload_config_service(hass):
|
|||||||
|
|
||||||
assert hass.states.get("scene.hallo") is None
|
assert hass.states.get("scene.hallo") is None
|
||||||
assert hass.states.get("scene.bye") is not None
|
assert hass.states.get("scene.bye") is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_apply_service(hass):
|
||||||
|
"""Test the apply service."""
|
||||||
|
assert await async_setup_component(hass, "scene", {})
|
||||||
|
assert await async_setup_component(hass, "light", {"light": {"platform": "demo"}})
|
||||||
|
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
"scene", "apply", {"entities": {"light.bed_light": "off"}}, blocking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.states.get("light.bed_light").state == "off"
|
||||||
|
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
"scene",
|
||||||
|
"apply",
|
||||||
|
{"entities": {"light.bed_light": {"state": "on", "brightness": 50}}},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get("light.bed_light")
|
||||||
|
assert state.state == "on"
|
||||||
|
assert state.attributes["brightness"] == 50
|
||||||
|
Loading…
x
Reference in New Issue
Block a user