mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 00:37:13 +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.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(
|
||||
{
|
||||
vol.Required(CONF_PLATFORM): HASS_DOMAIN,
|
||||
@ -34,9 +64,7 @@ PLATFORM_SCHEMA = vol.Schema(
|
||||
[
|
||||
{
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_ENTITIES): {
|
||||
cv.entity_id: vol.Any(str, bool, dict)
|
||||
},
|
||||
vol.Required(CONF_ENTITIES): STATES_SCHEMA,
|
||||
}
|
||||
],
|
||||
),
|
||||
@ -44,6 +72,7 @@ PLATFORM_SCHEMA = vol.Schema(
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
SERVICE_APPLY = "apply"
|
||||
SCENECONFIG = namedtuple("SceneConfig", [CONF_NAME, STATES])
|
||||
_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
|
||||
)
|
||||
|
||||
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):
|
||||
"""Process multiple scenes and add them."""
|
||||
@ -97,41 +139,11 @@ def _process_scenes_config(hass, async_add_entities, config):
|
||||
return
|
||||
|
||||
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):
|
||||
"""A scene is a group of entities and the states we want them to be."""
|
||||
|
||||
@ -148,8 +160,13 @@ class HomeAssistantScene(Scene):
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""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):
|
||||
"""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."""
|
||||
import asyncio
|
||||
import importlib
|
||||
import logging
|
||||
|
||||
@ -7,7 +6,6 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.core import DOMAIN as HA_DOMAIN
|
||||
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_component import EntityComponent
|
||||
from homeassistant.helpers.state import HASS_DOMAIN
|
||||
@ -69,20 +67,7 @@ async def async_setup(hass, config):
|
||||
HA_DOMAIN, {"platform": "homeasistant", STATES: []}
|
||||
)
|
||||
|
||||
async def async_handle_scene_service(service):
|
||||
"""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,
|
||||
)
|
||||
component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_activate")
|
||||
|
||||
return True
|
||||
|
||||
|
@ -5,4 +5,18 @@ turn_on:
|
||||
fields:
|
||||
entity_id:
|
||||
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.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