""" This package contains components that can be plugged into Home Assistant. Component design guidelines: - 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 ".". - Each component should publish services only under its own domain. """ import asyncio import itertools as it import logging from typing import Awaitable import homeassistant.core as ha import homeassistant.config as conf_util from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.service import extract_entity_ids from homeassistant.helpers import intent from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, RESTART_EXIT_CODE) _LOGGER = logging.getLogger(__name__) SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' SERVICE_CHECK_CONFIG = 'check_config' def is_on(hass, entity_id=None): """Load up the module to call the is_on method. If there is no entity id given we will check all. """ if entity_id: entity_ids = hass.components.group.expand_entity_ids([entity_id]) else: entity_ids = hass.states.entity_ids() for ent_id in entity_ids: domain = ha.split_entity_id(ent_id)[0] try: 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 if component.is_on(ent_id): return True return False def turn_on(hass, entity_id=None, **service_data): """Turn specified entity on if possible.""" if entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id hass.services.call(ha.DOMAIN, SERVICE_TURN_ON, service_data) def turn_off(hass, entity_id=None, **service_data): """Turn specified entity off.""" if entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data) def toggle(hass, entity_id=None, **service_data): """Toggle specified entity.""" if entity_id is not None: service_data[ATTR_ENTITY_ID] = entity_id hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data) def stop(hass): """Stop Home Assistant.""" hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP) def restart(hass): """Stop Home Assistant.""" hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART) def check_config(hass): """Check the config files.""" hass.services.call(ha.DOMAIN, SERVICE_CHECK_CONFIG) def reload_core_config(hass): """Reload the core config.""" hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG) @asyncio.coroutine def async_reload_core_config(hass): """Reload the core config.""" yield from hass.services.async_call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG) @asyncio.coroutine def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: """Set up general services related to Home Assistant.""" @asyncio.coroutine def async_handle_turn_service(service): """Handle calls to homeassistant.turn_on/off.""" entity_ids = extract_entity_ids(hass, service) # Generic turn on/off method requires entity id if not entity_ids: _LOGGER.error( "homeassistant/%s cannot be called without entity_id", service.service) return # Group entity_ids by domain. groupby requires sorted data. by_domain = it.groupby(sorted(entity_ids), lambda item: ha.split_entity_id(item)[0]) tasks = [] for domain, ent_ids in by_domain: # 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 # listening to the bus too. So as an in between solution, we'll # block only if the service is defined in the current HA instance. blocking = hass.services.has_service(domain, service.service) # 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) tasks.append(hass.services.async_call( domain, service.service, data, blocking)) yield from asyncio.wait(tasks, loop=hass.loop) hass.services.async_register( ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service) hass.services.async_register( ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) hass.services.async_register( ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) hass.helpers.intent.async_register(intent.ServiceIntentHandler( intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on")) hass.helpers.intent.async_register(intent.ServiceIntentHandler( intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, "Turned {} off")) hass.helpers.intent.async_register(intent.ServiceIntentHandler( intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}")) @asyncio.coroutine def async_handle_core_service(call): """Service handler for handling core services.""" if call.service == SERVICE_HOMEASSISTANT_STOP: hass.async_create_task(hass.async_stop()) return try: errors = yield from conf_util.async_check_ha_config_file(hass) except HomeAssistantError: return if errors: _LOGGER.error(errors) hass.components.persistent_notification.async_create( "Config error. See dev-info panel for details.", "Config validating", "{0}.check_config".format(ha.DOMAIN)) return if call.service == SERVICE_HOMEASSISTANT_RESTART: hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE)) hass.services.async_register( ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) hass.services.async_register( ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service) hass.services.async_register( ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service) @asyncio.coroutine def async_handle_reload_config(call): """Service handler for reloading core config.""" try: conf = yield from conf_util.async_hass_config_yaml(hass) except HomeAssistantError as err: _LOGGER.error(err) return yield from conf_util.async_process_ha_core_config( hass, conf.get(ha.DOMAIN) or {}) hass.services.async_register( ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config) return True