2013-12-11 00:07:30 -08:00
|
|
|
"""
|
|
|
|
This package contains components that can be plugged into Home Assistant.
|
2014-01-04 18:24:30 -08:00
|
|
|
|
|
|
|
Component design guidelines:
|
2016-03-08 17:55:57 +01:00
|
|
|
- Each component defines a constant DOMAIN that is equal to its filename.
|
|
|
|
- Each component that tracks states should create state entity names in the
|
|
|
|
format "<DOMAIN>.<OBJECT_ID>".
|
|
|
|
- Each component should publish services only under its own domain.
|
2013-12-11 00:07:30 -08:00
|
|
|
"""
|
2016-10-27 09:16:23 +02:00
|
|
|
import asyncio
|
2014-04-13 12:59:45 -07:00
|
|
|
import itertools as it
|
2014-08-13 14:28:45 +02:00
|
|
|
import logging
|
2018-07-31 17:00:17 +03:00
|
|
|
from typing import Awaitable
|
2014-01-23 23:26:00 -08:00
|
|
|
|
2018-10-09 16:54:38 +02:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2015-08-16 20:44:46 -07:00
|
|
|
import homeassistant.core as ha
|
2017-02-08 18:17:52 +01:00
|
|
|
import homeassistant.config as conf_util
|
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2016-01-23 22:57:14 -08:00
|
|
|
from homeassistant.helpers.service import extract_entity_ids
|
2018-02-11 12:33:19 -05:00
|
|
|
from homeassistant.helpers import intent
|
2014-12-06 23:57:02 -08:00
|
|
|
from homeassistant.const import (
|
2017-02-08 18:17:52 +01:00
|
|
|
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
|
|
|
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
|
|
|
|
RESTART_EXIT_CODE)
|
2018-10-09 16:54:38 +02:00
|
|
|
from homeassistant.helpers import config_validation as cv
|
2014-03-11 22:45:05 -07:00
|
|
|
|
2014-11-08 13:57:08 -08:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2014-08-13 14:28:45 +02:00
|
|
|
|
2016-06-22 09:13:18 -07:00
|
|
|
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
|
2017-02-08 18:17:52 +01:00
|
|
|
SERVICE_CHECK_CONFIG = 'check_config'
|
2018-10-09 16:54:38 +02:00
|
|
|
SERVICE_UPDATE_ENTITY = 'update_entity'
|
|
|
|
SCHEMA_UPDATE_ENTITY = vol.Schema({
|
2018-10-27 21:34:33 +02:00
|
|
|
ATTR_ENTITY_ID: cv.entity_ids
|
2018-10-09 16:54:38 +02:00
|
|
|
})
|
2016-06-22 09:13:18 -07:00
|
|
|
|
2014-01-23 23:26:00 -08:00
|
|
|
|
2014-04-24 00:40:45 -07:00
|
|
|
def is_on(hass, entity_id=None):
|
2016-03-08 17:55:57 +01:00
|
|
|
"""Load up the module to call the is_on method.
|
|
|
|
|
|
|
|
If there is no entity id given we will check all.
|
|
|
|
"""
|
2014-04-13 12:59:45 -07:00
|
|
|
if entity_id:
|
2017-07-16 12:39:38 -07:00
|
|
|
entity_ids = hass.components.group.expand_entity_ids([entity_id])
|
2014-04-13 12:59:45 -07:00
|
|
|
else:
|
2014-11-28 23:19:59 -08:00
|
|
|
entity_ids = hass.states.entity_ids()
|
2014-01-23 23:26:00 -08:00
|
|
|
|
2017-07-05 20:02:16 -07:00
|
|
|
for ent_id in entity_ids:
|
|
|
|
domain = ha.split_entity_id(ent_id)[0]
|
2014-01-23 23:26:00 -08:00
|
|
|
|
|
|
|
try:
|
2017-07-16 12:39:38 -07:00
|
|
|
component = getattr(hass.components, domain)
|
|
|
|
|
|
|
|
except ImportError:
|
|
|
|
_LOGGER.error('Failed to call %s.is_on: component not found',
|
|
|
|
domain)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not hasattr(component, 'is_on'):
|
|
|
|
_LOGGER.warning("Component %s has no is_on method.", domain)
|
|
|
|
continue
|
2014-01-23 23:26:00 -08:00
|
|
|
|
2017-07-16 12:39:38 -07:00
|
|
|
if component.is_on(ent_id):
|
|
|
|
return True
|
2014-01-23 23:26:00 -08:00
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2018-10-01 08:52:42 +02:00
|
|
|
async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
2017-04-30 07:04:49 +02:00
|
|
|
"""Set up general services related to Home Assistant."""
|
2018-10-01 08:52:42 +02:00
|
|
|
async def async_handle_turn_service(service):
|
2017-05-02 22:47:20 +02:00
|
|
|
"""Handle calls to homeassistant.turn_on/off."""
|
2014-04-24 00:40:45 -07:00
|
|
|
entity_ids = extract_entity_ids(hass, service)
|
2014-03-24 20:34:35 -07:00
|
|
|
|
2014-04-13 12:59:45 -07:00
|
|
|
# Generic turn on/off method requires entity id
|
|
|
|
if not entity_ids:
|
2014-11-08 13:57:08 -08:00
|
|
|
_LOGGER.error(
|
|
|
|
"homeassistant/%s cannot be called without entity_id",
|
|
|
|
service.service)
|
2014-04-13 12:59:45 -07:00
|
|
|
return
|
2014-01-23 23:26:00 -08:00
|
|
|
|
2014-04-13 12:59:45 -07:00
|
|
|
# Group entity_ids by domain. groupby requires sorted data.
|
|
|
|
by_domain = it.groupby(sorted(entity_ids),
|
2016-08-08 20:21:40 -07:00
|
|
|
lambda item: ha.split_entity_id(item)[0])
|
2014-04-13 12:59:45 -07:00
|
|
|
|
2016-10-27 09:16:23 +02:00
|
|
|
tasks = []
|
|
|
|
|
2014-04-13 12:59:45 -07:00
|
|
|
for domain, ent_ids in by_domain:
|
2016-01-03 21:25:15 -08:00
|
|
|
# We want to block for all calls and only return when all calls
|
|
|
|
# have been processed. If a service does not exist it causes a 10
|
|
|
|
# second delay while we're blocking waiting for a response.
|
|
|
|
# But services can be registered on other HA instances that are
|
2018-01-27 21:58:27 +02:00
|
|
|
# listening to the bus too. So as an in between solution, we'll
|
2016-01-03 21:25:15 -08:00
|
|
|
# block only if the service is defined in the current HA instance.
|
|
|
|
blocking = hass.services.has_service(domain, service.service)
|
|
|
|
|
2014-04-13 12:59:45 -07:00
|
|
|
# Create a new dict for this call
|
|
|
|
data = dict(service.data)
|
|
|
|
|
|
|
|
# ent_ids is a generator, convert it to a list.
|
|
|
|
data[ATTR_ENTITY_ID] = list(ent_ids)
|
|
|
|
|
2016-10-27 09:16:23 +02:00
|
|
|
tasks.append(hass.services.async_call(
|
|
|
|
domain, service.service, data, blocking))
|
|
|
|
|
2018-10-01 08:52:42 +02:00
|
|
|
await asyncio.wait(tasks, loop=hass.loop)
|
2014-01-23 23:26:00 -08:00
|
|
|
|
2016-10-27 09:16:23 +02:00
|
|
|
hass.services.async_register(
|
2018-01-07 23:54:16 +01:00
|
|
|
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
|
2016-10-27 09:16:23 +02:00
|
|
|
hass.services.async_register(
|
2018-01-07 23:54:16 +01:00
|
|
|
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
|
2016-10-27 09:16:23 +02:00
|
|
|
hass.services.async_register(
|
2018-01-07 23:54:16 +01:00
|
|
|
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
|
2018-02-11 12:33:19 -05:00
|
|
|
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
2018-03-01 07:35:12 -08:00
|
|
|
intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on"))
|
2018-02-11 12:33:19 -05:00
|
|
|
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
2018-03-01 07:35:12 -08:00
|
|
|
intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF,
|
|
|
|
"Turned {} off"))
|
2018-02-11 12:33:19 -05:00
|
|
|
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
|
|
|
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
|
2014-01-23 23:26:00 -08:00
|
|
|
|
2018-10-01 08:52:42 +02:00
|
|
|
async def async_handle_core_service(call):
|
2017-02-08 18:17:52 +01:00
|
|
|
"""Service handler for handling core services."""
|
|
|
|
if call.service == SERVICE_HOMEASSISTANT_STOP:
|
2018-07-23 14:05:38 +02:00
|
|
|
hass.async_create_task(hass.async_stop())
|
2017-02-08 18:17:52 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
2018-10-01 08:52:42 +02:00
|
|
|
errors = await conf_util.async_check_ha_config_file(hass)
|
2017-02-08 18:17:52 +01:00
|
|
|
except HomeAssistantError:
|
|
|
|
return
|
|
|
|
|
2017-02-12 11:31:46 -08:00
|
|
|
if errors:
|
|
|
|
_LOGGER.error(errors)
|
2017-07-16 12:39:38 -07:00
|
|
|
hass.components.persistent_notification.async_create(
|
|
|
|
"Config error. See dev-info panel for details.",
|
2017-02-12 11:31:46 -08:00
|
|
|
"Config validating", "{0}.check_config".format(ha.DOMAIN))
|
|
|
|
return
|
|
|
|
|
2017-02-08 18:17:52 +01:00
|
|
|
if call.service == SERVICE_HOMEASSISTANT_RESTART:
|
2018-07-23 14:05:38 +02:00
|
|
|
hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE))
|
2016-06-22 09:13:18 -07:00
|
|
|
|
2018-10-09 16:54:38 +02:00
|
|
|
async def async_handle_update_service(call):
|
|
|
|
"""Service handler for updating an entity."""
|
2018-10-27 21:34:33 +02:00
|
|
|
tasks = [hass.helpers.entity_component.async_update_entity(entity)
|
|
|
|
for entity in call.data[ATTR_ENTITY_ID]]
|
|
|
|
|
|
|
|
if tasks:
|
|
|
|
await asyncio.wait(tasks)
|
2018-10-09 16:54:38 +02:00
|
|
|
|
2017-02-08 18:17:52 +01:00
|
|
|
hass.services.async_register(
|
2018-01-07 23:54:16 +01:00
|
|
|
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
|
2017-02-08 18:17:52 +01:00
|
|
|
hass.services.async_register(
|
2018-01-07 23:54:16 +01:00
|
|
|
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service)
|
2017-02-08 18:17:52 +01:00
|
|
|
hass.services.async_register(
|
2018-01-07 23:54:16 +01:00
|
|
|
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
|
2018-10-09 16:54:38 +02:00
|
|
|
hass.services.async_register(
|
|
|
|
ha.DOMAIN, SERVICE_UPDATE_ENTITY, async_handle_update_service,
|
|
|
|
schema=SCHEMA_UPDATE_ENTITY)
|
2017-02-08 18:17:52 +01:00
|
|
|
|
2018-10-01 08:52:42 +02:00
|
|
|
async def async_handle_reload_config(call):
|
2017-02-08 18:17:52 +01:00
|
|
|
"""Service handler for reloading core config."""
|
2016-06-22 09:13:18 -07:00
|
|
|
try:
|
2018-10-01 08:52:42 +02:00
|
|
|
conf = await conf_util.async_hass_config_yaml(hass)
|
2016-06-22 09:13:18 -07:00
|
|
|
except HomeAssistantError as err:
|
|
|
|
_LOGGER.error(err)
|
|
|
|
return
|
|
|
|
|
2018-10-01 08:52:42 +02:00
|
|
|
await conf_util.async_process_ha_core_config(
|
2016-10-27 09:16:23 +02:00
|
|
|
hass, conf.get(ha.DOMAIN) or {})
|
2016-06-22 09:13:18 -07:00
|
|
|
|
2016-10-27 09:16:23 +02:00
|
|
|
hass.services.async_register(
|
2018-01-07 23:54:16 +01:00
|
|
|
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config)
|
2016-06-22 09:13:18 -07:00
|
|
|
|
2014-01-23 23:26:00 -08:00
|
|
|
return True
|