2019-02-13 20:21:14 +00:00
|
|
|
"""Rest API for Home Assistant."""
|
2016-09-13 02:16:14 +00:00
|
|
|
import asyncio
|
2016-02-19 05:27:50 +00:00
|
|
|
import json
|
2015-01-30 16:26:06 +00:00
|
|
|
import logging
|
2016-10-24 06:48:01 +00:00
|
|
|
|
|
|
|
from aiohttp import web
|
2018-11-30 20:28:35 +00:00
|
|
|
from aiohttp.web_exceptions import HTTPBadRequest
|
2016-10-24 06:48:01 +00:00
|
|
|
import async_timeout
|
2018-11-30 20:28:35 +00:00
|
|
|
import voluptuous as vol
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2017-09-16 05:25:32 +00:00
|
|
|
from homeassistant.bootstrap import DATA_LOGGING
|
2018-05-27 18:16:30 +00:00
|
|
|
from homeassistant.components.http import HomeAssistantView
|
2015-01-30 07:56:04 +00:00
|
|
|
from homeassistant.const import (
|
2018-05-27 18:16:30 +00:00
|
|
|
EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, HTTP_BAD_REQUEST,
|
|
|
|
HTTP_CREATED, HTTP_NOT_FOUND, MATCH_ALL, URL_API, URL_API_COMPONENTS,
|
|
|
|
URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG, URL_API_EVENTS,
|
|
|
|
URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM,
|
|
|
|
URL_API_TEMPLATE, __version__)
|
|
|
|
import homeassistant.core as ha
|
2018-11-25 17:04:48 +00:00
|
|
|
from homeassistant.auth.permissions.const import POLICY_READ
|
2018-11-30 20:28:35 +00:00
|
|
|
from homeassistant.exceptions import (
|
|
|
|
TemplateError, Unauthorized, ServiceNotFound)
|
2016-02-23 20:06:50 +00:00
|
|
|
from homeassistant.helpers import template
|
2018-05-27 18:16:30 +00:00
|
|
|
from homeassistant.helpers.service import async_get_all_descriptions
|
|
|
|
from homeassistant.helpers.state import AsyncTrackStates
|
2018-08-21 13:49:58 +00:00
|
|
|
from homeassistant.helpers.json import JSONEncoder
|
2018-05-27 18:16:30 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
ATTR_BASE_URL = 'base_url'
|
|
|
|
ATTR_LOCATION_NAME = 'location_name'
|
|
|
|
ATTR_REQUIRES_API_PASSWORD = 'requires_api_password'
|
|
|
|
ATTR_VERSION = 'version'
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
DOMAIN = 'api'
|
2018-05-27 18:16:30 +00:00
|
|
|
STREAM_PING_PAYLOAD = 'ping'
|
2015-02-19 08:15:21 +00:00
|
|
|
STREAM_PING_INTERVAL = 50 # seconds
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
def setup(hass, config):
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Register the API with the HTTP interface."""
|
2016-10-24 06:48:01 +00:00
|
|
|
hass.http.register_view(APIStatusView)
|
|
|
|
hass.http.register_view(APIEventStream)
|
|
|
|
hass.http.register_view(APIConfigView)
|
|
|
|
hass.http.register_view(APIDiscoveryView)
|
|
|
|
hass.http.register_view(APIStatesView)
|
|
|
|
hass.http.register_view(APIEntityStateView)
|
|
|
|
hass.http.register_view(APIEventListenersView)
|
|
|
|
hass.http.register_view(APIEventView)
|
|
|
|
hass.http.register_view(APIServicesView)
|
|
|
|
hass.http.register_view(APIDomainServicesView)
|
|
|
|
hass.http.register_view(APIComponentsView)
|
|
|
|
hass.http.register_view(APITemplateView)
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2018-04-13 11:32:05 +00:00
|
|
|
if DATA_LOGGING in hass.data:
|
|
|
|
hass.http.register_view(APIErrorLog)
|
2017-03-30 07:50:53 +00:00
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
return True
|
|
|
|
|
2015-01-30 16:26:06 +00:00
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIStatusView(HomeAssistantView):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""View to handle Status requests."""
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
url = URL_API
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:status'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@ha.callback
|
2016-05-10 01:09:38 +00:00
|
|
|
def get(self, request):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Retrieve if API is running."""
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message("API running.")
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEventStream(HomeAssistantView):
|
2016-05-17 02:47:15 +00:00
|
|
|
"""View to handle EventStream requests."""
|
2015-02-14 02:59:42 +00:00
|
|
|
|
2016-05-14 07:58:36 +00:00
|
|
|
url = URL_API_STREAM
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:stream'
|
2015-09-24 04:35:23 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def get(self, request):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Provide a streaming interface for the event bus."""
|
2018-11-25 17:04:48 +00:00
|
|
|
if not request['hass_user'].is_admin:
|
|
|
|
raise Unauthorized()
|
2016-11-25 21:04:06 +00:00
|
|
|
hass = request.app['hass']
|
2016-05-14 07:58:36 +00:00
|
|
|
stop_obj = object()
|
2016-11-25 21:04:06 +00:00
|
|
|
to_write = asyncio.Queue(loop=hass.loop)
|
2016-05-14 07:58:36 +00:00
|
|
|
|
2017-05-26 20:12:17 +00:00
|
|
|
restrict = request.query.get('restrict')
|
2016-05-14 07:58:36 +00:00
|
|
|
if restrict:
|
2016-06-30 16:02:12 +00:00
|
|
|
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
|
2016-05-14 07:58:36 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def forward_events(event):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Forward events to the open request."""
|
|
|
|
if event.event_type == EVENT_TIME_CHANGED:
|
2016-05-16 06:54:14 +00:00
|
|
|
return
|
|
|
|
|
2016-05-22 01:24:03 +00:00
|
|
|
if restrict and event.event_type not in restrict:
|
|
|
|
return
|
|
|
|
|
2018-05-27 18:16:30 +00:00
|
|
|
_LOGGER.debug("STREAM %s FORWARDING %s", id(stop_obj), event)
|
2016-05-16 06:54:14 +00:00
|
|
|
|
|
|
|
if event.event_type == EVENT_HOMEASSISTANT_STOP:
|
|
|
|
data = stop_obj
|
2016-05-14 07:58:36 +00:00
|
|
|
else:
|
2018-08-21 13:49:58 +00:00
|
|
|
data = json.dumps(event, cls=JSONEncoder)
|
2016-05-16 06:54:14 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
await to_write.put(data)
|
2016-05-14 07:58:36 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
response = web.StreamResponse()
|
|
|
|
response.content_type = 'text/event-stream'
|
2018-04-28 23:26:20 +00:00
|
|
|
await response.prepare(request)
|
2016-05-14 07:58:36 +00:00
|
|
|
|
2016-11-25 21:04:06 +00:00
|
|
|
unsub_stream = hass.bus.async_listen(MATCH_ALL, forward_events)
|
2015-02-19 08:15:21 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
try:
|
2018-05-27 18:16:30 +00:00
|
|
|
_LOGGER.debug("STREAM %s ATTACHED", id(stop_obj))
|
2016-05-17 02:47:15 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
# Fire off one message so browsers fire open event right away
|
2018-04-28 23:26:20 +00:00
|
|
|
await to_write.put(STREAM_PING_PAYLOAD)
|
2015-02-14 02:59:42 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
with async_timeout.timeout(STREAM_PING_INTERVAL,
|
2016-11-25 21:04:06 +00:00
|
|
|
loop=hass.loop):
|
2018-04-28 23:26:20 +00:00
|
|
|
payload = await to_write.get()
|
2015-02-14 02:59:42 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
if payload is stop_obj:
|
2016-09-07 13:59:59 +00:00
|
|
|
break
|
2015-02-20 07:31:06 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
msg = "data: {}\n\n".format(payload)
|
2018-05-27 18:16:30 +00:00
|
|
|
_LOGGER.debug(
|
|
|
|
"STREAM %s WRITING %s", id(stop_obj), msg.strip())
|
|
|
|
await response.write(msg.encode('UTF-8'))
|
2016-10-24 06:48:01 +00:00
|
|
|
except asyncio.TimeoutError:
|
2018-04-28 23:26:20 +00:00
|
|
|
await to_write.put(STREAM_PING_PAYLOAD)
|
2016-10-24 06:48:01 +00:00
|
|
|
|
2017-01-15 16:36:24 +00:00
|
|
|
except asyncio.CancelledError:
|
2018-05-27 18:16:30 +00:00
|
|
|
_LOGGER.debug("STREAM %s ABORT", id(stop_obj))
|
2017-01-15 16:36:24 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
finally:
|
2018-05-27 18:16:30 +00:00
|
|
|
_LOGGER.debug("STREAM %s RESPONSE CLOSED", id(stop_obj))
|
2016-10-24 06:48:01 +00:00
|
|
|
unsub_stream()
|
2015-02-14 02:59:42 +00:00
|
|
|
|
2018-10-15 08:31:49 +00:00
|
|
|
return response
|
|
|
|
|
2015-02-14 02:59:42 +00:00
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIConfigView(HomeAssistantView):
|
2018-05-27 18:16:30 +00:00
|
|
|
"""View to handle Configuration requests."""
|
2016-05-14 07:58:36 +00:00
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
url = URL_API_CONFIG
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:config'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@ha.callback
|
2016-05-10 01:09:38 +00:00
|
|
|
def get(self, request):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Get current configuration."""
|
2016-11-25 21:04:06 +00:00
|
|
|
return self.json(request.app['hass'].config.as_dict())
|
2015-05-02 01:24:32 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIDiscoveryView(HomeAssistantView):
|
2018-05-27 18:16:30 +00:00
|
|
|
"""View to provide Discovery information."""
|
2016-05-14 07:58:36 +00:00
|
|
|
|
|
|
|
requires_auth = False
|
2016-05-10 01:09:38 +00:00
|
|
|
url = URL_API_DISCOVERY_INFO
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:discovery'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@ha.callback
|
2016-05-10 01:09:38 +00:00
|
|
|
def get(self, request):
|
2018-05-27 18:16:30 +00:00
|
|
|
"""Get discovery information."""
|
2016-11-25 21:04:06 +00:00
|
|
|
hass = request.app['hass']
|
2016-05-14 07:58:36 +00:00
|
|
|
return self.json({
|
2018-05-27 18:16:30 +00:00
|
|
|
ATTR_BASE_URL: hass.config.api.base_url,
|
|
|
|
ATTR_LOCATION_NAME: hass.config.location_name,
|
2019-03-11 02:55:36 +00:00
|
|
|
# always needs authentication
|
|
|
|
ATTR_REQUIRES_API_PASSWORD: True,
|
2018-05-27 18:16:30 +00:00
|
|
|
ATTR_VERSION: __version__,
|
2016-05-14 07:58:36 +00:00
|
|
|
})
|
2016-05-07 05:11:35 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIStatesView(HomeAssistantView):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""View to handle States requests."""
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
url = URL_API_STATES
|
|
|
|
name = "api:states"
|
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@ha.callback
|
2016-05-10 01:09:38 +00:00
|
|
|
def get(self, request):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Get current states."""
|
2018-11-25 17:04:48 +00:00
|
|
|
user = request['hass_user']
|
|
|
|
entity_perm = user.permissions.check_entity
|
|
|
|
states = [
|
|
|
|
state for state in request.app['hass'].states.async_all()
|
|
|
|
if entity_perm(state.entity_id, 'read')
|
|
|
|
]
|
|
|
|
return self.json(states)
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEntityStateView(HomeAssistantView):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""View to handle EntityState requests."""
|
|
|
|
|
2018-05-27 18:16:30 +00:00
|
|
|
url = '/api/states/{entity_id}'
|
|
|
|
name = 'api:entity-state'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@ha.callback
|
2016-05-10 01:09:38 +00:00
|
|
|
def get(self, request, entity_id):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Retrieve state of entity."""
|
2018-11-25 17:04:48 +00:00
|
|
|
user = request['hass_user']
|
|
|
|
if not user.permissions.check_entity(entity_id, POLICY_READ):
|
|
|
|
raise Unauthorized(entity_id=entity_id)
|
|
|
|
|
2016-11-25 21:04:06 +00:00
|
|
|
state = request.app['hass'].states.get(entity_id)
|
2016-05-10 01:09:38 +00:00
|
|
|
if state:
|
2016-05-14 07:58:36 +00:00
|
|
|
return self.json(state)
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message("Entity not found.", HTTP_NOT_FOUND)
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def post(self, request, entity_id):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Update state of entity."""
|
2018-11-25 17:04:48 +00:00
|
|
|
if not request['hass_user'].is_admin:
|
|
|
|
raise Unauthorized(entity_id=entity_id)
|
2016-11-25 21:04:06 +00:00
|
|
|
hass = request.app['hass']
|
2016-05-10 01:09:38 +00:00
|
|
|
try:
|
2018-04-28 23:26:20 +00:00
|
|
|
data = await request.json()
|
2016-10-24 06:48:01 +00:00
|
|
|
except ValueError:
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message(
|
|
|
|
"Invalid JSON specified.", HTTP_BAD_REQUEST)
|
2016-10-24 06:48:01 +00:00
|
|
|
|
|
|
|
new_state = data.get('state')
|
|
|
|
|
2017-07-03 21:35:57 +00:00
|
|
|
if new_state is None:
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message("No state specified.", HTTP_BAD_REQUEST)
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
attributes = data.get('attributes')
|
|
|
|
force_update = data.get('force_update', False)
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-11-25 21:04:06 +00:00
|
|
|
is_new_state = hass.states.get(entity_id) is None
|
2016-05-10 01:09:38 +00:00
|
|
|
|
|
|
|
# Write state
|
2018-07-29 00:53:37 +00:00
|
|
|
hass.states.async_set(entity_id, new_state, attributes, force_update,
|
|
|
|
self.context(request))
|
2016-05-10 01:09:38 +00:00
|
|
|
|
|
|
|
# Read the state back for our response
|
2016-10-24 06:48:01 +00:00
|
|
|
status_code = HTTP_CREATED if is_new_state else 200
|
2016-11-25 21:04:06 +00:00
|
|
|
resp = self.json(hass.states.get(entity_id), status_code)
|
2016-05-10 01:09:38 +00:00
|
|
|
|
|
|
|
resp.headers.add('Location', URL_API_STATES_ENTITY.format(entity_id))
|
|
|
|
|
|
|
|
return resp
|
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@ha.callback
|
2016-05-10 01:09:38 +00:00
|
|
|
def delete(self, request, entity_id):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Remove entity."""
|
2018-11-25 17:04:48 +00:00
|
|
|
if not request['hass_user'].is_admin:
|
|
|
|
raise Unauthorized(entity_id=entity_id)
|
2016-11-25 21:04:06 +00:00
|
|
|
if request.app['hass'].states.async_remove(entity_id):
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message("Entity removed.")
|
|
|
|
return self.json_message("Entity not found.", HTTP_NOT_FOUND)
|
2016-02-14 07:00:38 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEventListenersView(HomeAssistantView):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""View to handle EventListeners requests."""
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
url = URL_API_EVENTS
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:event-listeners'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@ha.callback
|
2016-05-10 01:09:38 +00:00
|
|
|
def get(self, request):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Get event listeners."""
|
2016-11-25 21:04:06 +00:00
|
|
|
return self.json(async_events_json(request.app['hass']))
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEventView(HomeAssistantView):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""View to handle Event requests."""
|
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
url = '/api/events/{event_type}'
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:event'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def post(self, request, event_type):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Fire events."""
|
2018-11-25 17:04:48 +00:00
|
|
|
if not request['hass_user'].is_admin:
|
|
|
|
raise Unauthorized()
|
2018-04-28 23:26:20 +00:00
|
|
|
body = await request.text()
|
2017-10-22 07:44:46 +00:00
|
|
|
try:
|
|
|
|
event_data = json.loads(body) if body else None
|
|
|
|
except ValueError:
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message(
|
|
|
|
"Event data should be valid JSON.", HTTP_BAD_REQUEST)
|
2016-05-14 07:58:36 +00:00
|
|
|
|
|
|
|
if event_data is not None and not isinstance(event_data, dict):
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message(
|
|
|
|
"Event data should be a JSON object", HTTP_BAD_REQUEST)
|
2016-05-10 01:09:38 +00:00
|
|
|
|
|
|
|
# Special case handling for event STATE_CHANGED
|
|
|
|
# We will try to convert state dicts back to State objects
|
|
|
|
if event_type == ha.EVENT_STATE_CHANGED and event_data:
|
|
|
|
for key in ('old_state', 'new_state'):
|
|
|
|
state = ha.State.from_dict(event_data.get(key))
|
|
|
|
|
|
|
|
if state:
|
|
|
|
event_data[key] = state
|
|
|
|
|
2018-05-27 18:16:30 +00:00
|
|
|
request.app['hass'].bus.async_fire(
|
2018-07-29 00:53:37 +00:00
|
|
|
event_type, event_data, ha.EventOrigin.remote,
|
|
|
|
self.context(request))
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2016-05-14 07:58:36 +00:00
|
|
|
return self.json_message("Event {} fired.".format(event_type))
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIServicesView(HomeAssistantView):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""View to handle Services requests."""
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
url = URL_API_SERVICES
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:services'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def get(self, request):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Get registered services."""
|
2018-04-28 23:26:20 +00:00
|
|
|
services = await async_services_json(request.app['hass'])
|
2018-01-07 22:54:16 +00:00
|
|
|
return self.json(services)
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIDomainServicesView(HomeAssistantView):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""View to handle DomainServices requests."""
|
|
|
|
|
2018-05-27 18:16:30 +00:00
|
|
|
url = '/api/services/{domain}/{service}'
|
|
|
|
name = 'api:domain-services'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def post(self, request, domain, service):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Call a service.
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-05-14 07:58:36 +00:00
|
|
|
Returns a list of changed states.
|
|
|
|
"""
|
2016-11-25 21:04:06 +00:00
|
|
|
hass = request.app['hass']
|
2018-04-28 23:26:20 +00:00
|
|
|
body = await request.text()
|
2017-10-22 07:44:46 +00:00
|
|
|
try:
|
|
|
|
data = json.loads(body) if body else None
|
|
|
|
except ValueError:
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message(
|
|
|
|
"Data should be valid JSON.", HTTP_BAD_REQUEST)
|
2016-10-24 06:48:01 +00:00
|
|
|
|
2016-11-25 21:04:06 +00:00
|
|
|
with AsyncTrackStates(hass) as changed_states:
|
2018-11-30 20:28:35 +00:00
|
|
|
try:
|
|
|
|
await hass.services.async_call(
|
|
|
|
domain, service, data, True, self.context(request))
|
|
|
|
except (vol.Invalid, ServiceNotFound):
|
|
|
|
raise HTTPBadRequest()
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-05-14 07:58:36 +00:00
|
|
|
return self.json(changed_states)
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIComponentsView(HomeAssistantView):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""View to handle Components requests."""
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
url = URL_API_COMPONENTS
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:components'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@ha.callback
|
2016-05-10 01:09:38 +00:00
|
|
|
def get(self, request):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Get current loaded components."""
|
2016-11-25 21:04:06 +00:00
|
|
|
return self.json(request.app['hass'].config.components)
|
2015-05-02 02:02:29 +00:00
|
|
|
|
2015-05-02 03:56:10 +00:00
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APITemplateView(HomeAssistantView):
|
2018-05-27 18:16:30 +00:00
|
|
|
"""View to handle Template requests."""
|
2016-05-14 07:58:36 +00:00
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
url = URL_API_TEMPLATE
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:template'
|
2016-05-10 01:09:38 +00:00
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def post(self, request):
|
2016-05-14 07:58:36 +00:00
|
|
|
"""Render a template."""
|
2018-11-25 17:04:48 +00:00
|
|
|
if not request['hass_user'].is_admin:
|
|
|
|
raise Unauthorized()
|
2016-05-14 07:58:36 +00:00
|
|
|
try:
|
2018-04-28 23:26:20 +00:00
|
|
|
data = await request.json()
|
2016-11-25 21:04:06 +00:00
|
|
|
tpl = template.Template(data['template'], request.app['hass'])
|
2016-10-24 06:48:01 +00:00
|
|
|
return tpl.async_render(data.get('variables'))
|
|
|
|
except (ValueError, TemplateError) as ex:
|
2018-05-27 18:16:30 +00:00
|
|
|
return self.json_message(
|
|
|
|
"Error rendering template: {}".format(ex), HTTP_BAD_REQUEST)
|
2015-12-10 07:56:20 +00:00
|
|
|
|
|
|
|
|
2018-04-13 11:32:05 +00:00
|
|
|
class APIErrorLog(HomeAssistantView):
|
2018-05-27 18:16:30 +00:00
|
|
|
"""View to fetch the API error log."""
|
2018-04-13 11:32:05 +00:00
|
|
|
|
|
|
|
url = URL_API_ERROR_LOG
|
2018-05-27 18:16:30 +00:00
|
|
|
name = 'api:error_log'
|
2018-04-13 11:32:05 +00:00
|
|
|
|
|
|
|
async def get(self, request):
|
|
|
|
"""Retrieve API error log."""
|
2018-11-25 17:04:48 +00:00
|
|
|
if not request['hass_user'].is_admin:
|
|
|
|
raise Unauthorized()
|
2018-05-27 18:16:30 +00:00
|
|
|
return web.FileResponse(request.app['hass'].data[DATA_LOGGING])
|
2018-04-13 11:32:05 +00:00
|
|
|
|
|
|
|
|
2018-04-28 23:26:20 +00:00
|
|
|
async def async_services_json(hass):
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Generate services data to JSONify."""
|
2018-04-28 23:26:20 +00:00
|
|
|
descriptions = await async_get_all_descriptions(hass)
|
2018-05-27 18:16:30 +00:00
|
|
|
return [{'domain': key, 'services': value}
|
2018-01-07 22:54:16 +00:00
|
|
|
for key, value in descriptions.items()]
|
2015-05-02 02:02:29 +00:00
|
|
|
|
2015-05-02 03:56:10 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
def async_events_json(hass):
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Generate event data to JSONify."""
|
2018-05-27 18:16:30 +00:00
|
|
|
return [{'event': key, 'listener_count': value}
|
2016-10-24 06:48:01 +00:00
|
|
|
for key, value in hass.bus.async_listeners().items()]
|