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:
Paulus Schoutsen 2019-10-08 09:59:32 -07:00 committed by GitHub
parent a51e0d7a5f
commit f5bd0f29b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 54 deletions

View File

@ -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,
)

View File

@ -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

View File

@ -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

View File

@ -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