core/homeassistant/components/script.py

230 lines
7.0 KiB
Python
Raw Normal View History

"""
2016-03-08 16:55:57 +00:00
Support for scripts.
Scripts are a sequence of actions that can be triggered manually
by the user or automatically based upon automation events, etc.
2015-10-25 14:14:56 +00:00
For more details about this component, please refer to the documentation at
2015-11-09 12:12:18 +00:00
https://home-assistant.io/components/script/
"""
import asyncio
import logging
import voluptuous as vol
2016-02-19 05:27:50 +00:00
from homeassistant.const import (
2016-04-21 22:52:20 +00:00
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_TOGGLE, SERVICE_RELOAD, STATE_ON, CONF_ALIAS)
2016-08-09 03:21:40 +00:00
from homeassistant.core import split_entity_id
from homeassistant.loader import bind_hass
2016-08-09 03:21:40 +00:00
from homeassistant.helpers.entity import ToggleEntity
2016-02-19 05:27:50 +00:00
from homeassistant.helpers.entity_component import EntityComponent
import homeassistant.helpers.config_validation as cv
2016-04-21 22:52:20 +00:00
from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__)
2015-10-15 06:09:52 +00:00
DOMAIN = 'script'
DEPENDENCIES = ['group']
ATTR_CAN_CANCEL = 'can_cancel'
2015-10-15 06:09:52 +00:00
ATTR_LAST_ACTION = 'last_action'
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
CONF_SEQUENCE = 'sequence'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
GROUP_NAME_ALL_SCRIPTS = 'all scripts'
SCRIPT_ENTRY_SCHEMA = vol.Schema({
CONF_ALIAS: cv.string,
2016-04-21 22:52:20 +00:00
vol.Required(CONF_SEQUENCE): cv.SCRIPT_SCHEMA,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({cv.slug: SCRIPT_ENTRY_SCHEMA})
}, extra=vol.ALLOW_EXTRA)
SCRIPT_SERVICE_SCHEMA = vol.Schema(dict)
SCRIPT_TURN_ONOFF_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_VARIABLES): dict,
})
RELOAD_SERVICE_SCHEMA = vol.Schema({})
@bind_hass
2015-10-15 06:09:52 +00:00
def is_on(hass, entity_id):
"""Return if the script is on based on the statemachine."""
2015-10-15 06:09:52 +00:00
return hass.states.is_state(entity_id, STATE_ON)
@bind_hass
def turn_on(hass, entity_id, variables=None):
2016-03-07 17:49:31 +00:00
"""Turn script on."""
2015-10-15 06:09:52 +00:00
_, object_id = split_entity_id(entity_id)
hass.services.call(DOMAIN, object_id, variables)
2015-10-15 06:09:52 +00:00
@bind_hass
2015-10-15 06:09:52 +00:00
def turn_off(hass, entity_id):
2016-03-07 17:49:31 +00:00
"""Turn script on."""
2015-10-15 06:09:52 +00:00
hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
@bind_hass
2016-02-21 17:22:38 +00:00
def toggle(hass, entity_id):
2016-03-08 16:55:57 +00:00
"""Toggle the script."""
2016-02-21 17:22:38 +00:00
hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id})
@bind_hass
def reload(hass):
"""Reload script component."""
hass.services.call(DOMAIN, SERVICE_RELOAD)
@bind_hass
def async_reload(hass):
"""Reload the scripts from config.
Returns a coroutine object.
"""
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
@asyncio.coroutine
def async_setup(hass, config):
2016-03-07 17:49:31 +00:00
"""Load the scripts from the configuration."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, group_name=GROUP_NAME_ALL_SCRIPTS)
yield from _async_process_config(hass, config, component)
2015-10-15 06:09:52 +00:00
@asyncio.coroutine
def reload_service(service):
"""Call a service to reload scripts."""
conf = yield from component.async_prepare_reload()
if conf is None:
return
2015-10-15 06:09:52 +00:00
yield from _async_process_config(hass, conf, component)
2015-10-15 06:09:52 +00:00
@asyncio.coroutine
2015-10-15 06:09:52 +00:00
def turn_on_service(service):
2016-03-08 16:55:57 +00:00
"""Call a service to turn script on."""
2015-10-15 06:09:52 +00:00
# We could turn on script directly here, but we only want to offer
# one way to do it. Otherwise no easy way to detect invocations.
var = service.data.get(ATTR_VARIABLES)
for script in component.async_extract_from_service(service):
yield from hass.services.async_call(DOMAIN, script.object_id, var)
2015-10-15 06:09:52 +00:00
@asyncio.coroutine
2015-10-15 06:09:52 +00:00
def turn_off_service(service):
2016-03-08 16:55:57 +00:00
"""Cancel a script."""
# Stopping a script is ok to be done in parallel
yield from asyncio.wait(
[script.async_turn_off() for script
in component.async_extract_from_service(service)], loop=hass.loop)
@asyncio.coroutine
2016-02-21 17:22:38 +00:00
def toggle_service(service):
2016-03-08 16:55:57 +00:00
"""Toggle a script."""
for script in component.async_extract_from_service(service):
yield from script.async_toggle()
hass.services.async_register(DOMAIN, SERVICE_RELOAD, reload_service,
schema=RELOAD_SERVICE_SCHEMA)
hass.services.async_register(DOMAIN, SERVICE_TURN_ON, turn_on_service,
schema=SCRIPT_TURN_ONOFF_SCHEMA)
hass.services.async_register(DOMAIN, SERVICE_TURN_OFF, turn_off_service,
schema=SCRIPT_TURN_ONOFF_SCHEMA)
hass.services.async_register(DOMAIN, SERVICE_TOGGLE, toggle_service,
schema=SCRIPT_TURN_ONOFF_SCHEMA)
return True
@asyncio.coroutine
def _async_process_config(hass, config, component):
"""Process group configuration."""
@asyncio.coroutine
def service_handler(service):
"""Execute a service call to script.<script name>."""
entity_id = ENTITY_ID_FORMAT.format(service.service)
script = component.get_entity(entity_id)
if script.is_on:
_LOGGER.warning("Script %s already running.", entity_id)
return
yield from script.async_turn_on(variables=service.data)
scripts = []
for object_id, cfg in config[DOMAIN].items():
alias = cfg.get(CONF_ALIAS, object_id)
script = ScriptEntity(hass, object_id, alias, cfg[CONF_SEQUENCE])
scripts.append(script)
hass.services.async_register(
DOMAIN, object_id, service_handler, schema=SCRIPT_SERVICE_SCHEMA)
yield from component.async_add_entities(scripts)
2016-04-21 22:52:20 +00:00
class ScriptEntity(ToggleEntity):
"""Representation of a script entity."""
2016-03-08 16:55:57 +00:00
2016-04-21 22:52:20 +00:00
def __init__(self, hass, object_id, name, sequence):
2016-03-08 16:55:57 +00:00
"""Initialize the script."""
self.object_id = object_id
2015-10-28 19:24:33 +00:00
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self.script = Script(hass, sequence, name, self.async_update_ha_state)
2015-10-15 06:09:52 +00:00
@property
def should_poll(self):
2016-03-07 17:49:31 +00:00
"""No polling needed."""
2015-10-15 06:09:52 +00:00
return False
@property
def name(self):
2016-03-07 17:49:31 +00:00
"""Return the name of the entity."""
2016-04-21 22:52:20 +00:00
return self.script.name
2015-10-15 06:09:52 +00:00
@property
def state_attributes(self):
2016-03-07 17:49:31 +00:00
"""Return the state attributes."""
attrs = {}
attrs[ATTR_LAST_TRIGGERED] = self.script.last_triggered
2016-04-21 22:52:20 +00:00
if self.script.can_cancel:
attrs[ATTR_CAN_CANCEL] = self.script.can_cancel
if self.script.last_action:
attrs[ATTR_LAST_ACTION] = self.script.last_action
2015-10-15 06:09:52 +00:00
return attrs
@property
def is_on(self):
2016-03-08 16:55:57 +00:00
"""Return true if script is on."""
2016-04-21 22:52:20 +00:00
return self.script.is_running
2015-10-15 06:09:52 +00:00
@asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Turn the script on."""
yield from self.script.async_run(kwargs.get(ATTR_VARIABLES))
2015-10-15 06:09:52 +00:00
@asyncio.coroutine
def async_turn_off(self, **kwargs):
2016-03-07 17:49:31 +00:00
"""Turn script off."""
self.script.async_stop()
@asyncio.coroutine
def async_will_remove_from_hass(self):
"""Stop script and remove service when it will be removed from HASS."""
if self.script.is_running:
self.script.async_stop()
# remove service
self.hass.services.async_remove(DOMAIN, self.object_id)