2019-02-13 20:21:14 +00:00
|
|
|
"""Support for scripts."""
|
2016-11-18 05:50:01 +00:00
|
|
|
import asyncio
|
2015-03-08 03:14:21 +00:00
|
|
|
import logging
|
|
|
|
|
2016-04-03 17:19:09 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2016-02-19 05:27:50 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_ENTITY_ID,
|
|
|
|
SERVICE_TURN_OFF,
|
|
|
|
SERVICE_TURN_ON,
|
|
|
|
SERVICE_TOGGLE,
|
|
|
|
SERVICE_RELOAD,
|
|
|
|
STATE_ON,
|
|
|
|
CONF_ALIAS,
|
|
|
|
EVENT_SCRIPT_STARTED,
|
|
|
|
ATTR_NAME,
|
|
|
|
)
|
2017-07-16 17:14:46 +00:00
|
|
|
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
|
2016-04-03 17:19:09 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2019-07-23 19:37:47 +00:00
|
|
|
from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA
|
2015-03-08 03:14:21 +00:00
|
|
|
|
2016-04-21 22:52:20 +00:00
|
|
|
from homeassistant.helpers.script import Script
|
|
|
|
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2015-10-15 06:09:52 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN = "script"
|
|
|
|
ATTR_CAN_CANCEL = "can_cancel"
|
|
|
|
ATTR_LAST_ACTION = "last_action"
|
|
|
|
ATTR_LAST_TRIGGERED = "last_triggered"
|
|
|
|
ATTR_VARIABLES = "variables"
|
2015-03-08 03:14:21 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_SEQUENCE = "sequence"
|
2017-04-30 05:04:49 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
2017-04-30 05:04:49 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
GROUP_NAME_ALL_SCRIPTS = "all scripts"
|
2015-03-08 03:14:21 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SCRIPT_ENTRY_SCHEMA = vol.Schema(
|
|
|
|
{CONF_ALIAS: cv.string, vol.Required(CONF_SEQUENCE): cv.SCRIPT_SCHEMA}
|
|
|
|
)
|
2016-04-03 17:19:09 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
|
|
{DOMAIN: cv.schema_with_slug_keys(SCRIPT_ENTRY_SCHEMA)}, extra=vol.ALLOW_EXTRA
|
|
|
|
)
|
2016-04-03 17:19:09 +00:00
|
|
|
|
2016-04-22 02:21:11 +00:00
|
|
|
SCRIPT_SERVICE_SCHEMA = vol.Schema(dict)
|
2019-07-31 19:25:30 +00:00
|
|
|
SCRIPT_TURN_ONOFF_SCHEMA = ENTITY_SERVICE_SCHEMA.extend(
|
|
|
|
{vol.Optional(ATTR_VARIABLES): dict}
|
|
|
|
)
|
2017-03-08 06:51:34 +00:00
|
|
|
RELOAD_SERVICE_SCHEMA = vol.Schema({})
|
2016-04-12 05:26:21 +00:00
|
|
|
|
2015-03-08 03:14:21 +00:00
|
|
|
|
2017-07-16 17:14:46 +00:00
|
|
|
@bind_hass
|
2015-10-15 06:09:52 +00:00
|
|
|
def is_on(hass, entity_id):
|
2016-10-25 11:20:40 +00:00
|
|
|
"""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)
|
|
|
|
|
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def async_setup(hass, config):
|
2016-03-07 17:49:31 +00:00
|
|
|
"""Load the scripts from the configuration."""
|
2017-03-08 06:51:34 +00:00
|
|
|
component = EntityComponent(
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER, DOMAIN, hass, group_name=GROUP_NAME_ALL_SCRIPTS
|
|
|
|
)
|
2017-03-08 06:51:34 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
await _async_process_config(hass, config, component)
|
2015-10-15 06:09:52 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def reload_service(service):
|
2017-03-08 06:51:34 +00:00
|
|
|
"""Call a service to reload scripts."""
|
2018-09-04 19:16:24 +00:00
|
|
|
conf = await component.async_prepare_reload()
|
2017-03-08 06:51:34 +00:00
|
|
|
if conf is None:
|
2016-02-21 17:11:35 +00:00
|
|
|
return
|
2015-10-15 06:09:52 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
await _async_process_config(hass, conf, component)
|
2015-10-15 06:09:52 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async 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
|
2016-04-22 02:21:11 +00:00
|
|
|
# one way to do it. Otherwise no easy way to detect invocations.
|
2016-11-18 05:50:01 +00:00
|
|
|
var = service.data.get(ATTR_VARIABLES)
|
2019-03-04 17:51:12 +00:00
|
|
|
for script in await component.async_extract_from_service(service):
|
2019-07-31 19:25:30 +00:00
|
|
|
await hass.services.async_call(
|
|
|
|
DOMAIN, script.object_id, var, context=service.context
|
|
|
|
)
|
2015-10-15 06:09:52 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def turn_off_service(service):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Cancel a script."""
|
2016-11-18 05:50:01 +00:00
|
|
|
# Stopping a script is ok to be done in parallel
|
2019-06-28 15:49:33 +00:00
|
|
|
scripts = await component.async_extract_from_service(service)
|
|
|
|
|
|
|
|
if not scripts:
|
|
|
|
return
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
await asyncio.wait([script.async_turn_off() for script in scripts])
|
2015-03-08 03:14:21 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def toggle_service(service):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Toggle a script."""
|
2019-03-04 17:51:12 +00:00
|
|
|
for script in await component.async_extract_from_service(service):
|
2018-09-04 19:16:24 +00:00
|
|
|
await script.async_toggle(context=service.context)
|
2016-11-18 05:50:01 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
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
|
|
|
|
)
|
2017-03-01 04:33:19 +00:00
|
|
|
|
2015-03-08 03:14:21 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def _async_process_config(hass, config, component):
|
|
|
|
"""Process script configuration."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def service_handler(service):
|
2017-03-08 06:51:34 +00:00
|
|
|
"""Execute a service call to script.<script name>."""
|
|
|
|
entity_id = ENTITY_ID_FORMAT.format(service.service)
|
2018-01-23 06:54:41 +00:00
|
|
|
script = component.get_entity(entity_id)
|
2017-03-08 06:51:34 +00:00
|
|
|
if script.is_on:
|
|
|
|
_LOGGER.warning("Script %s already running.", entity_id)
|
|
|
|
return
|
2019-07-31 19:25:30 +00:00
|
|
|
await script.async_turn_on(variables=service.data, context=service.context)
|
2017-03-08 06:51:34 +00:00
|
|
|
|
|
|
|
scripts = []
|
|
|
|
|
2019-02-08 04:07:15 +00:00
|
|
|
for object_id, cfg in config.get(DOMAIN, {}).items():
|
2017-03-08 06:51:34 +00:00
|
|
|
alias = cfg.get(CONF_ALIAS, object_id)
|
|
|
|
script = ScriptEntity(hass, object_id, alias, cfg[CONF_SEQUENCE])
|
|
|
|
scripts.append(script)
|
|
|
|
hass.services.async_register(
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN, object_id, service_handler, schema=SCRIPT_SERVICE_SCHEMA
|
|
|
|
)
|
2017-03-08 06:51:34 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
await component.async_add_entities(scripts)
|
2017-03-08 06:51:34 +00:00
|
|
|
|
|
|
|
|
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."""
|
2016-11-18 05:50:01 +00:00
|
|
|
self.object_id = object_id
|
2015-10-28 19:24:33 +00:00
|
|
|
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
|
2016-10-04 05:39:27 +00:00
|
|
|
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."""
|
2016-02-28 06:30:24 +00:00
|
|
|
attrs = {}
|
2017-01-11 15:23:05 +00:00
|
|
|
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
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def async_turn_on(self, **kwargs):
|
2016-10-25 11:20:40 +00:00
|
|
|
"""Turn the script on."""
|
2019-07-31 19:25:30 +00:00
|
|
|
context = kwargs.get("context")
|
2018-12-04 08:45:17 +00:00
|
|
|
self.async_set_context(context)
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.bus.async_fire(
|
|
|
|
EVENT_SCRIPT_STARTED,
|
|
|
|
{ATTR_NAME: self.script.name, ATTR_ENTITY_ID: self.entity_id},
|
|
|
|
context=context,
|
|
|
|
)
|
2019-05-14 05:09:11 +00:00
|
|
|
try:
|
2019-07-31 19:25:30 +00:00
|
|
|
await self.script.async_run(kwargs.get(ATTR_VARIABLES), context)
|
2019-05-14 05:09:11 +00:00
|
|
|
except Exception as err: # pylint: disable=broad-except
|
|
|
|
self.script.async_log_exception(
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER, "Error executing script {}".format(self.entity_id), err
|
|
|
|
)
|
2019-05-14 05:09:11 +00:00
|
|
|
raise err
|
2015-10-15 06:09:52 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def async_turn_off(self, **kwargs):
|
2016-03-07 17:49:31 +00:00
|
|
|
"""Turn script off."""
|
2016-11-18 05:50:01 +00:00
|
|
|
self.script.async_stop()
|
2017-03-08 06:51:34 +00:00
|
|
|
|
2018-09-04 19:16:24 +00:00
|
|
|
async def async_will_remove_from_hass(self):
|
2018-01-23 06:54:41 +00:00
|
|
|
"""Stop script and remove service when it will be removed from HASS."""
|
2017-03-08 06:51:34 +00:00
|
|
|
if self.script.is_running:
|
|
|
|
self.script.async_stop()
|
|
|
|
|
|
|
|
# remove service
|
|
|
|
self.hass.services.async_remove(DOMAIN, self.object_id)
|