2015-01-30 16:26:06 +00:00
|
|
|
"""
|
2016-02-23 20:06:50 +00:00
|
|
|
Rest API for Home Assistant.
|
2015-10-25 14:30:56 +00:00
|
|
|
|
|
|
|
For more details about the RESTful API, please refer to the documentation at
|
2015-11-09 12:12:18 +00:00
|
|
|
https://home-assistant.io/developers/api/
|
2015-01-30 16:26:06 +00:00
|
|
|
"""
|
2016-02-19 05:27:50 +00:00
|
|
|
import json
|
2015-01-30 16:26:06 +00:00
|
|
|
import logging
|
2016-02-19 05:27:50 +00:00
|
|
|
import re
|
2015-02-14 02:59:42 +00:00
|
|
|
import threading
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2015-08-17 03:44:46 +00:00
|
|
|
import homeassistant.core as ha
|
2015-01-30 07:56:04 +00:00
|
|
|
import homeassistant.remote as rem
|
2015-11-07 09:44:02 +00:00
|
|
|
from homeassistant.bootstrap import ERROR_LOG_FILENAME
|
2015-01-30 07:56:04 +00:00
|
|
|
from homeassistant.const import (
|
2016-02-19 05:27:50 +00:00
|
|
|
CONTENT_TYPE_TEXT_PLAIN, EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED,
|
|
|
|
HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_HEADER_CONTENT_TYPE, HTTP_NOT_FOUND,
|
|
|
|
HTTP_OK, HTTP_UNPROCESSABLE_ENTITY, MATCH_ALL, URL_API, URL_API_COMPONENTS,
|
2016-05-07 05:11:35 +00:00
|
|
|
URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG,
|
|
|
|
URL_API_EVENT_FORWARD, URL_API_EVENTS, URL_API_LOG_OUT, URL_API_SERVICES,
|
|
|
|
URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM, URL_API_TEMPLATE,
|
|
|
|
__version__)
|
2016-02-19 05:27:50 +00:00
|
|
|
from homeassistant.exceptions import TemplateError
|
|
|
|
from homeassistant.helpers.state import TrackStates
|
2016-02-23 20:06:50 +00:00
|
|
|
from homeassistant.helpers import template
|
2016-05-10 01:09:38 +00:00
|
|
|
from homeassistant.components.wsgi import HomeAssistantView
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
DOMAIN = 'api'
|
2016-05-10 01:09:38 +00:00
|
|
|
DEPENDENCIES = ['http', 'wsgi']
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2015-02-19 08:15:21 +00:00
|
|
|
STREAM_PING_PAYLOAD = "ping"
|
|
|
|
STREAM_PING_INTERVAL = 50 # seconds
|
|
|
|
|
2015-01-30 16:26:06 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
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."""
|
2015-01-30 07:56:04 +00:00
|
|
|
# /api - for validation purposes
|
|
|
|
hass.http.register_path('GET', URL_API, _handle_get_api)
|
|
|
|
|
2015-05-02 01:24:32 +00:00
|
|
|
# /api/config
|
|
|
|
hass.http.register_path('GET', URL_API_CONFIG, _handle_get_api_config)
|
|
|
|
|
2016-05-07 05:11:35 +00:00
|
|
|
# /api/discovery_info
|
|
|
|
hass.http.register_path('GET', URL_API_DISCOVERY_INFO,
|
|
|
|
_handle_get_api_discovery_info,
|
|
|
|
require_auth=False)
|
|
|
|
|
|
|
|
# /api/stream
|
|
|
|
hass.http.register_path('GET', URL_API_STREAM, _handle_get_api_stream)
|
|
|
|
|
|
|
|
# /api/states
|
2015-01-30 07:56:04 +00:00
|
|
|
hass.http.register_path('GET', URL_API_STATES, _handle_get_api_states)
|
|
|
|
hass.http.register_path(
|
|
|
|
'GET', re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
|
|
|
|
_handle_get_api_states_entity)
|
|
|
|
hass.http.register_path(
|
|
|
|
'POST', re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
|
|
|
|
_handle_post_state_entity)
|
|
|
|
hass.http.register_path(
|
|
|
|
'PUT', re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
|
|
|
|
_handle_post_state_entity)
|
2016-02-14 07:00:38 +00:00
|
|
|
hass.http.register_path(
|
|
|
|
'DELETE', re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
|
|
|
|
_handle_delete_state_entity)
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2016-05-07 05:11:35 +00:00
|
|
|
# /api/events
|
2015-01-30 07:56:04 +00:00
|
|
|
hass.http.register_path('GET', URL_API_EVENTS, _handle_get_api_events)
|
|
|
|
hass.http.register_path(
|
|
|
|
'POST', re.compile(r'/api/events/(?P<event_type>[a-zA-Z\._0-9]+)'),
|
|
|
|
_handle_api_post_events_event)
|
|
|
|
|
2016-05-07 05:11:35 +00:00
|
|
|
# /api/services
|
2015-01-30 07:56:04 +00:00
|
|
|
hass.http.register_path('GET', URL_API_SERVICES, _handle_get_api_services)
|
|
|
|
hass.http.register_path(
|
|
|
|
'POST',
|
|
|
|
re.compile((r'/api/services/'
|
|
|
|
r'(?P<domain>[a-zA-Z\._0-9]+)/'
|
|
|
|
r'(?P<service>[a-zA-Z\._0-9]+)')),
|
|
|
|
_handle_post_api_services_domain_service)
|
|
|
|
|
2016-05-07 05:11:35 +00:00
|
|
|
# /api/event_forwarding
|
2015-01-30 07:56:04 +00:00
|
|
|
hass.http.register_path(
|
|
|
|
'POST', URL_API_EVENT_FORWARD, _handle_post_api_event_forward)
|
|
|
|
hass.http.register_path(
|
|
|
|
'DELETE', URL_API_EVENT_FORWARD, _handle_delete_api_event_forward)
|
|
|
|
|
2016-05-07 05:11:35 +00:00
|
|
|
# /api/components
|
2015-02-01 03:08:50 +00:00
|
|
|
hass.http.register_path(
|
|
|
|
'GET', URL_API_COMPONENTS, _handle_get_api_components)
|
|
|
|
|
2016-05-07 05:11:35 +00:00
|
|
|
# /api/error_log
|
2015-11-07 09:44:02 +00:00
|
|
|
hass.http.register_path('GET', URL_API_ERROR_LOG,
|
|
|
|
_handle_get_api_error_log)
|
|
|
|
|
2015-11-29 01:18:35 +00:00
|
|
|
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
|
|
|
|
|
2016-05-07 05:11:35 +00:00
|
|
|
# /api/template
|
2015-12-10 07:56:20 +00:00
|
|
|
hass.http.register_path('POST', URL_API_TEMPLATE,
|
|
|
|
_handle_post_api_template)
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
hass.wsgi.register_view(APIStatusView)
|
2016-05-10 01:26:52 +00:00
|
|
|
hass.wsgi.register_view(APIEventStream)
|
2016-05-10 01:09:38 +00:00
|
|
|
hass.wsgi.register_view(APIConfigView)
|
|
|
|
hass.wsgi.register_view(APIDiscoveryView)
|
|
|
|
hass.wsgi.register_view(APIStatesView)
|
2016-05-10 01:26:52 +00:00
|
|
|
hass.wsgi.register_view(APIEntityStateView)
|
2016-05-10 01:09:38 +00:00
|
|
|
hass.wsgi.register_view(APIEventListenersView)
|
2016-05-10 01:26:52 +00:00
|
|
|
hass.wsgi.register_view(APIEventView)
|
2016-05-10 01:09:38 +00:00
|
|
|
hass.wsgi.register_view(APIServicesView)
|
|
|
|
hass.wsgi.register_view(APIDomainServicesView)
|
2016-05-10 01:26:52 +00:00
|
|
|
hass.wsgi.register_view(APIEventForwardingView)
|
|
|
|
hass.wsgi.register_view(APIComponentsView)
|
|
|
|
hass.wsgi.register_view(APIErrorLogView)
|
|
|
|
hass.wsgi.register_view(APILogOutView)
|
|
|
|
hass.wsgi.register_view(APITemplateView)
|
2016-05-10 01:09:38 +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):
|
|
|
|
url = URL_API
|
|
|
|
name = "api:status"
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
return {'message': 'API running.'}
|
|
|
|
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
def _handle_get_api(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Render the debug interface."""
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message("API running.")
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEventStream(HomeAssistantView):
|
|
|
|
url = ""
|
|
|
|
name = ""
|
|
|
|
|
|
|
|
# TODO Implement this...
|
|
|
|
|
|
|
|
|
2015-02-14 02:59:42 +00:00
|
|
|
def _handle_get_api_stream(handler, path_match, data):
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Provide a streaming interface for the event bus."""
|
2015-02-19 08:15:21 +00:00
|
|
|
gracefully_closed = False
|
2015-02-14 02:59:42 +00:00
|
|
|
hass = handler.server.hass
|
|
|
|
wfile = handler.wfile
|
2015-02-19 08:15:21 +00:00
|
|
|
write_lock = threading.Lock()
|
2015-02-14 02:59:42 +00:00
|
|
|
block = threading.Event()
|
2015-11-29 06:14:40 +00:00
|
|
|
session_id = None
|
2015-02-14 02:59:42 +00:00
|
|
|
|
2015-09-24 04:35:23 +00:00
|
|
|
restrict = data.get('restrict')
|
|
|
|
if restrict:
|
|
|
|
restrict = restrict.split(',')
|
|
|
|
|
2015-02-19 08:15:21 +00:00
|
|
|
def write_message(payload):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Write a message to the output."""
|
2015-02-19 08:15:21 +00:00
|
|
|
with write_lock:
|
|
|
|
msg = "data: {}\n\n".format(payload)
|
|
|
|
|
|
|
|
try:
|
|
|
|
wfile.write(msg.encode("UTF-8"))
|
|
|
|
wfile.flush()
|
2015-12-12 02:43:00 +00:00
|
|
|
except (IOError, ValueError):
|
|
|
|
# IOError: socket errors
|
|
|
|
# ValueError: raised when 'I/O operation on closed file'
|
2015-02-19 08:15:21 +00:00
|
|
|
block.set()
|
|
|
|
|
|
|
|
def forward_events(event):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Forward events to the open request."""
|
2015-02-19 08:15:21 +00:00
|
|
|
nonlocal gracefully_closed
|
|
|
|
|
2015-12-15 07:20:43 +00:00
|
|
|
if block.is_set() or event.event_type == EVENT_TIME_CHANGED:
|
2015-02-14 02:59:42 +00:00
|
|
|
return
|
|
|
|
elif event.event_type == EVENT_HOMEASSISTANT_STOP:
|
2015-02-19 08:15:21 +00:00
|
|
|
gracefully_closed = True
|
2015-02-14 02:59:42 +00:00
|
|
|
block.set()
|
|
|
|
return
|
|
|
|
|
2015-12-15 07:20:43 +00:00
|
|
|
handler.server.sessions.extend_validation(session_id)
|
2015-02-19 08:15:21 +00:00
|
|
|
write_message(json.dumps(event, cls=rem.JSONEncoder))
|
2015-02-14 02:59:42 +00:00
|
|
|
|
|
|
|
handler.send_response(HTTP_OK)
|
|
|
|
handler.send_header('Content-type', 'text/event-stream')
|
2015-11-29 06:14:40 +00:00
|
|
|
session_id = handler.set_session_cookie_header()
|
2015-02-14 02:59:42 +00:00
|
|
|
handler.end_headers()
|
|
|
|
|
2015-12-15 07:20:43 +00:00
|
|
|
if restrict:
|
|
|
|
for event in restrict:
|
|
|
|
hass.bus.listen(event, forward_events)
|
|
|
|
else:
|
|
|
|
hass.bus.listen(MATCH_ALL, forward_events)
|
2015-02-19 08:15:21 +00:00
|
|
|
|
|
|
|
while True:
|
2015-02-20 07:31:06 +00:00
|
|
|
write_message(STREAM_PING_PAYLOAD)
|
|
|
|
|
2015-02-19 08:15:21 +00:00
|
|
|
block.wait(STREAM_PING_INTERVAL)
|
|
|
|
|
|
|
|
if block.is_set():
|
|
|
|
break
|
|
|
|
|
|
|
|
if not gracefully_closed:
|
|
|
|
_LOGGER.info("Found broken event stream to %s, cleaning up",
|
|
|
|
handler.client_address[0])
|
2015-02-14 02:59:42 +00:00
|
|
|
|
2015-12-15 07:20:43 +00:00
|
|
|
if restrict:
|
|
|
|
for event in restrict:
|
|
|
|
hass.bus.remove_listener(event, forward_events)
|
|
|
|
else:
|
|
|
|
hass.bus.remove_listener(MATCH_ALL, forward_events)
|
2015-02-14 02:59:42 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIConfigView(HomeAssistantView):
|
|
|
|
url = URL_API_CONFIG
|
|
|
|
name = "api:config"
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
return self.hass.config.as_dict()
|
|
|
|
|
|
|
|
|
2015-05-02 01:24:32 +00:00
|
|
|
def _handle_get_api_config(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the Home Assistant configuration."""
|
2015-05-02 01:24:32 +00:00
|
|
|
handler.write_json(handler.server.hass.config.as_dict())
|
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIDiscoveryView(HomeAssistantView):
|
|
|
|
url = URL_API_DISCOVERY_INFO
|
|
|
|
name = "api:discovery"
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
# TODO
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
2016-05-07 05:11:35 +00:00
|
|
|
def _handle_get_api_discovery_info(handler, path_match, data):
|
|
|
|
needs_auth = (handler.server.hass.config.api.api_password is not None)
|
|
|
|
params = {
|
|
|
|
'base_url': handler.server.hass.config.api.base_url,
|
|
|
|
'location_name': handler.server.hass.config.location_name,
|
|
|
|
'requires_api_password': needs_auth,
|
|
|
|
'version': __version__
|
|
|
|
}
|
|
|
|
handler.write_json(params)
|
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIStatesView(HomeAssistantView):
|
|
|
|
url = URL_API_STATES
|
|
|
|
name = "api:states"
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
return self.hass.states.all()
|
|
|
|
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
def _handle_get_api_states(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return a dict containing all entity ids and their state."""
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json(handler.server.hass.states.all())
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEntityStateView(HomeAssistantView):
|
|
|
|
url = "/api/states/<entity_id>"
|
|
|
|
name = "api:entity-state"
|
|
|
|
|
|
|
|
def get(self, request, entity_id):
|
|
|
|
state = self.hass.states.get(entity_id)
|
|
|
|
if state:
|
|
|
|
return state
|
|
|
|
else:
|
2016-05-12 05:55:24 +00:00
|
|
|
raise self.NotFound("State does not exist.")
|
2016-05-10 01:09:38 +00:00
|
|
|
|
|
|
|
def post(self, request, entity_id):
|
|
|
|
try:
|
|
|
|
new_state = request.values['state']
|
|
|
|
except KeyError:
|
2016-05-12 05:55:24 +00:00
|
|
|
raise self.BadRequest("state not specified")
|
2016-05-10 01:09:38 +00:00
|
|
|
|
|
|
|
attributes = request.values.get('attributes')
|
|
|
|
|
|
|
|
is_new_state = self.hass.states.get(entity_id) is None
|
|
|
|
|
|
|
|
# Write state
|
|
|
|
self.hass.states.set(entity_id, new_state, attributes)
|
|
|
|
|
|
|
|
# Read the state back for our response
|
|
|
|
msg = json.dumps(
|
|
|
|
self.hass.states.get(entity_id).as_dict(),
|
|
|
|
sort_keys=True,
|
|
|
|
cls=rem.JSONEncoder
|
|
|
|
).encode('UTF-8')
|
|
|
|
|
|
|
|
resp = Response(msg, mimetype="application/json")
|
|
|
|
|
|
|
|
if is_new_state:
|
|
|
|
resp.status_code = HTTP_CREATED
|
|
|
|
|
|
|
|
resp.headers.add('Location', URL_API_STATES_ENTITY.format(entity_id))
|
|
|
|
|
|
|
|
return resp
|
|
|
|
|
|
|
|
def delete(self, request, entity_id):
|
|
|
|
if self.hass.states.remove(entity_id):
|
|
|
|
return {"message:" "Entity removed"}
|
|
|
|
else:
|
|
|
|
return {
|
|
|
|
"message": "Entity not found",
|
|
|
|
"status_code": HTTP_NOT_FOUND,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
def _handle_get_api_states_entity(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the state of a specific entity."""
|
2015-01-30 07:56:04 +00:00
|
|
|
entity_id = path_match.group('entity_id')
|
|
|
|
|
|
|
|
state = handler.server.hass.states.get(entity_id)
|
|
|
|
|
|
|
|
if state:
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json(state)
|
2015-01-30 07:56:04 +00:00
|
|
|
else:
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message("State does not exist.", HTTP_NOT_FOUND)
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _handle_post_state_entity(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Handle updating the state of an entity.
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
This handles the following paths:
|
|
|
|
/api/states/<entity_id>
|
|
|
|
"""
|
|
|
|
entity_id = path_match.group('entity_id')
|
|
|
|
|
|
|
|
try:
|
|
|
|
new_state = data['state']
|
|
|
|
except KeyError:
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message("state not specified", HTTP_BAD_REQUEST)
|
2015-01-30 07:56:04 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
attributes = data['attributes'] if 'attributes' in data else None
|
|
|
|
|
|
|
|
is_new_state = handler.server.hass.states.get(entity_id) is None
|
|
|
|
|
|
|
|
# Write state
|
|
|
|
handler.server.hass.states.set(entity_id, new_state, attributes)
|
|
|
|
|
|
|
|
state = handler.server.hass.states.get(entity_id)
|
|
|
|
|
|
|
|
status_code = HTTP_CREATED if is_new_state else HTTP_OK
|
|
|
|
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json(
|
2015-01-30 07:56:04 +00:00
|
|
|
state.as_dict(),
|
|
|
|
status_code=status_code,
|
|
|
|
location=URL_API_STATES_ENTITY.format(entity_id))
|
|
|
|
|
|
|
|
|
2016-02-14 07:00:38 +00:00
|
|
|
def _handle_delete_state_entity(handler, path_match, data):
|
|
|
|
"""Handle request to delete an entity from state machine.
|
|
|
|
|
|
|
|
This handles the following paths:
|
|
|
|
/api/states/<entity_id>
|
|
|
|
"""
|
|
|
|
entity_id = path_match.group('entity_id')
|
|
|
|
|
|
|
|
if handler.server.hass.states.remove(entity_id):
|
|
|
|
handler.write_json_message(
|
|
|
|
"Entity not found", HTTP_NOT_FOUND)
|
|
|
|
else:
|
|
|
|
handler.write_json_message(
|
|
|
|
"Entity removed", HTTP_OK)
|
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEventListenersView(HomeAssistantView):
|
|
|
|
url = URL_API_EVENTS
|
|
|
|
name = "api:event-listeners"
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
return events_json(self.hass)
|
|
|
|
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
def _handle_get_api_events(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Handle getting overview of event listeners."""
|
2016-02-14 08:04:08 +00:00
|
|
|
handler.write_json(events_json(handler.server.hass))
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEventView(HomeAssistantView):
|
|
|
|
url = '/api/events/<event_type>'
|
|
|
|
name = "api:event"
|
|
|
|
|
|
|
|
def post(self, request, event_type):
|
|
|
|
event_data = request.values
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
self.hass.bus.fire(event_type, request.values, ha.EventOrigin.remote)
|
|
|
|
|
|
|
|
return {"message": "Event {} fired.".format(event_type)}
|
|
|
|
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
def _handle_api_post_events_event(handler, path_match, event_data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Handle firing of an event.
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2016-03-08 16:55:57 +00:00
|
|
|
This handles the following paths: /api/events/<event_type>
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
Events from /api are threated as remote events.
|
|
|
|
"""
|
|
|
|
event_type = path_match.group('event_type')
|
|
|
|
|
|
|
|
if event_data is not None and not isinstance(event_data, dict):
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message(
|
|
|
|
"event_data should be an object", HTTP_UNPROCESSABLE_ENTITY)
|
2016-02-14 07:00:38 +00:00
|
|
|
return
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
event_origin = ha.EventOrigin.remote
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
handler.server.hass.bus.fire(event_type, event_data, event_origin)
|
|
|
|
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_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):
|
|
|
|
url = URL_API_SERVICES
|
|
|
|
name = "api:services"
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
return services_json(self.hass)
|
|
|
|
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
def _handle_get_api_services(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Handle getting overview of services."""
|
2016-02-14 08:04:08 +00:00
|
|
|
handler.write_json(services_json(handler.server.hass))
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIDomainServicesView(HomeAssistantView):
|
|
|
|
url = "/api/services/<domain>/<service>"
|
|
|
|
name = "api:domain-services"
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
with TrackStates(self.hass) as changed_states:
|
|
|
|
self.hass.services.call(domain, service, request.values, True)
|
|
|
|
|
|
|
|
return changed_states
|
|
|
|
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
# pylint: disable=invalid-name
|
|
|
|
def _handle_post_api_services_domain_service(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Handle calling a service.
|
2015-01-30 07:56:04 +00:00
|
|
|
|
2016-03-08 16:55:57 +00:00
|
|
|
This handles the following paths: /api/services/<domain>/<service>
|
2015-01-30 07:56:04 +00:00
|
|
|
"""
|
|
|
|
domain = path_match.group('domain')
|
|
|
|
service = path_match.group('service')
|
|
|
|
|
|
|
|
with TrackStates(handler.server.hass) as changed_states:
|
|
|
|
handler.server.hass.services.call(domain, service, data, True)
|
|
|
|
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json(changed_states)
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIEventForwardingView(HomeAssistantView):
|
|
|
|
url = URL_API_EVENT_FORWARD
|
|
|
|
name = "api:event-forward"
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
try:
|
|
|
|
host = request.values['host']
|
|
|
|
api_password = request.values['api_password']
|
|
|
|
except KeyError:
|
|
|
|
return {
|
|
|
|
"message": "No host or api_password received.",
|
|
|
|
"status_code": HTTP_BAD_REQUEST,
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
port = int(data['port']) if 'port' in data else None
|
|
|
|
except ValueError:
|
|
|
|
return {
|
|
|
|
"message": "Invalid value received for port.",
|
|
|
|
"status_code": HTTP_UNPROCESSABLE_ENTITY,
|
|
|
|
}
|
|
|
|
|
|
|
|
api = rem.API(host, api_password, port)
|
|
|
|
|
|
|
|
if not api.validate_api():
|
|
|
|
return {
|
|
|
|
"message": "Unable to validate API.",
|
|
|
|
"status_code": HTTP_UNPROCESSABLE_ENTITY,
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.hass.event_forwarder is None:
|
|
|
|
self.hass.event_forwarder = rem.EventForwarder(self.hass)
|
|
|
|
|
|
|
|
self.hass.event_forwarder.connect(api)
|
|
|
|
|
|
|
|
return {"message": "Event forwarding setup."}
|
|
|
|
|
|
|
|
def delete(self, request):
|
|
|
|
try:
|
|
|
|
host = request.values['host']
|
|
|
|
except KeyError:
|
|
|
|
return {
|
|
|
|
"message": "No host received.",
|
|
|
|
"status_code": HTTP_BAD_REQUEST,
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
port = int(data['port']) if 'port' in data else None
|
|
|
|
except ValueError:
|
|
|
|
return {
|
|
|
|
"message": "Invalid value received for port",
|
|
|
|
"status_code": HTTP_UNPROCESSABLE_ENTITY,
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.hass.event_forwarder is not None:
|
|
|
|
api = rem.API(host, None, port)
|
|
|
|
|
|
|
|
self.hass.event_forwarder.disconnect(api)
|
|
|
|
|
|
|
|
return {"message": "Event forwarding cancelled."}
|
|
|
|
|
|
|
|
|
2015-01-30 07:56:04 +00:00
|
|
|
# pylint: disable=invalid-name
|
|
|
|
def _handle_post_api_event_forward(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Handle adding an event forwarding target."""
|
2015-01-30 07:56:04 +00:00
|
|
|
try:
|
|
|
|
host = data['host']
|
|
|
|
api_password = data['api_password']
|
|
|
|
except KeyError:
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message(
|
|
|
|
"No host or api_password received.", HTTP_BAD_REQUEST)
|
2015-01-30 07:56:04 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
port = int(data['port']) if 'port' in data else None
|
|
|
|
except ValueError:
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message(
|
2015-01-30 07:56:04 +00:00
|
|
|
"Invalid value received for port", HTTP_UNPROCESSABLE_ENTITY)
|
|
|
|
return
|
|
|
|
|
|
|
|
api = rem.API(host, api_password, port)
|
|
|
|
|
|
|
|
if not api.validate_api():
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message(
|
2015-01-30 07:56:04 +00:00
|
|
|
"Unable to validate API", HTTP_UNPROCESSABLE_ENTITY)
|
|
|
|
return
|
|
|
|
|
|
|
|
if handler.server.event_forwarder is None:
|
|
|
|
handler.server.event_forwarder = \
|
|
|
|
rem.EventForwarder(handler.server.hass)
|
|
|
|
|
|
|
|
handler.server.event_forwarder.connect(api)
|
|
|
|
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message("Event forwarding setup.")
|
2015-01-30 07:56:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _handle_delete_api_event_forward(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Handle deleting an event forwarding target."""
|
2015-01-30 07:56:04 +00:00
|
|
|
try:
|
|
|
|
host = data['host']
|
|
|
|
except KeyError:
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message("No host received.", HTTP_BAD_REQUEST)
|
2015-01-30 07:56:04 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
port = int(data['port']) if 'port' in data else None
|
|
|
|
except ValueError:
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message(
|
2015-01-30 07:56:04 +00:00
|
|
|
"Invalid value received for port", HTTP_UNPROCESSABLE_ENTITY)
|
|
|
|
return
|
|
|
|
|
|
|
|
if handler.server.event_forwarder is not None:
|
|
|
|
api = rem.API(host, None, port)
|
|
|
|
|
|
|
|
handler.server.event_forwarder.disconnect(api)
|
|
|
|
|
2015-01-30 16:26:06 +00:00
|
|
|
handler.write_json_message("Event forwarding cancelled.")
|
2015-02-01 03:08:50 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APIComponentsView(HomeAssistantView):
|
|
|
|
url = URL_API_COMPONENTS
|
|
|
|
name = "api:components"
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
return self.hass.config.components
|
|
|
|
|
|
|
|
|
2015-02-01 03:08:50 +00:00
|
|
|
def _handle_get_api_components(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return all the loaded components."""
|
2015-03-22 04:10:46 +00:00
|
|
|
handler.write_json(handler.server.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 APIErrorLogView(HomeAssistantView):
|
|
|
|
url = URL_API_ERROR_LOG
|
|
|
|
name = "api:error-log"
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
# TODO
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
2015-11-07 09:44:02 +00:00
|
|
|
def _handle_get_api_error_log(handler, path_match, data):
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Return the logged errors for this session."""
|
2015-11-28 22:08:01 +00:00
|
|
|
handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME),
|
|
|
|
False)
|
2015-11-07 09:44:02 +00:00
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APILogOutView(HomeAssistantView):
|
|
|
|
url = URL_API_LOG_OUT
|
|
|
|
name = "api:log-out"
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
# TODO
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
2015-11-29 01:18:35 +00:00
|
|
|
def _handle_post_api_log_out(handler, path_match, data):
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Log user out."""
|
2015-11-29 01:18:35 +00:00
|
|
|
handler.send_response(HTTP_OK)
|
|
|
|
handler.destroy_session()
|
|
|
|
handler.end_headers()
|
|
|
|
|
|
|
|
|
2016-05-10 01:09:38 +00:00
|
|
|
class APITemplateView(HomeAssistantView):
|
|
|
|
url = URL_API_TEMPLATE
|
|
|
|
name = "api:template"
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
# TODO
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
2015-12-10 07:56:20 +00:00
|
|
|
def _handle_post_api_template(handler, path_match, data):
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Log user out."""
|
2015-12-10 07:56:20 +00:00
|
|
|
template_string = data.get('template', '')
|
|
|
|
|
2015-12-18 07:33:09 +00:00
|
|
|
try:
|
|
|
|
rendered = template.render(handler.server.hass, template_string)
|
|
|
|
|
|
|
|
handler.send_response(HTTP_OK)
|
|
|
|
handler.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN)
|
|
|
|
handler.end_headers()
|
|
|
|
handler.wfile.write(rendered.encode('utf-8'))
|
|
|
|
except TemplateError as e:
|
|
|
|
handler.write_json_message(str(e), HTTP_UNPROCESSABLE_ENTITY)
|
|
|
|
return
|
2015-12-10 07:56:20 +00:00
|
|
|
|
|
|
|
|
2016-02-14 08:04:08 +00:00
|
|
|
def services_json(hass):
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Generate services data to JSONify."""
|
2015-05-02 02:02:29 +00:00
|
|
|
return [{"domain": key, "services": value}
|
|
|
|
for key, value in hass.services.services.items()]
|
|
|
|
|
2015-05-02 03:56:10 +00:00
|
|
|
|
2016-02-14 08:04:08 +00:00
|
|
|
def events_json(hass):
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Generate event data to JSONify."""
|
2015-05-02 02:02:29 +00:00
|
|
|
return [{"event": key, "listener_count": value}
|
|
|
|
for key, value in hass.bus.listeners.items()]
|