276 lines
8.7 KiB
Python
276 lines
8.7 KiB
Python
"""
|
|
Exposes regular rest commands as services.
|
|
|
|
For more details about this platform, please refer to the documentation at
|
|
https://home-assistant.io/components/hassio/
|
|
"""
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
|
|
import aiohttp
|
|
from aiohttp import web
|
|
from aiohttp.web_exceptions import HTTPBadGateway
|
|
import async_timeout
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.config import load_yaml_config_file
|
|
from homeassistant.components.http import HomeAssistantView
|
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
DOMAIN = 'hassio'
|
|
DEPENDENCIES = ['http']
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
LONG_TASK_TIMEOUT = 900
|
|
DEFAULT_TIMEOUT = 10
|
|
|
|
SERVICE_HOST_SHUTDOWN = 'host_shutdown'
|
|
SERVICE_HOST_REBOOT = 'host_reboot'
|
|
|
|
SERVICE_HOST_UPDATE = 'host_update'
|
|
SERVICE_SUPERVISOR_UPDATE = 'supervisor_update'
|
|
SERVICE_HOMEASSISTANT_UPDATE = 'homeassistant_update'
|
|
|
|
SERVICE_ADDON_INSTALL = 'addon_install'
|
|
SERVICE_ADDON_UNINSTALL = 'addon_uninstall'
|
|
SERVICE_ADDON_UPDATE = 'addon_update'
|
|
SERVICE_ADDON_START = 'addon_start'
|
|
SERVICE_ADDON_STOP = 'addon_stop'
|
|
|
|
ATTR_ADDON = 'addon'
|
|
ATTR_VERSION = 'version'
|
|
|
|
|
|
SCHEMA_SERVICE_UPDATE = vol.Schema({
|
|
vol.Optional(ATTR_VERSION): cv.string,
|
|
})
|
|
|
|
SCHEMA_SERVICE_ADDONS = vol.Schema({
|
|
vol.Required(ATTR_ADDON): cv.slug,
|
|
})
|
|
|
|
SCHEMA_SERVICE_ADDONS_VERSION = SCHEMA_SERVICE_ADDONS.extend({
|
|
vol.Optional(ATTR_VERSION): cv.string,
|
|
})
|
|
|
|
|
|
SERVICE_MAP = {
|
|
SERVICE_HOST_SHUTDOWN: None,
|
|
SERVICE_HOST_REBOOT: None,
|
|
SERVICE_HOST_UPDATE: SCHEMA_SERVICE_UPDATE,
|
|
SERVICE_SUPERVISOR_UPDATE: SCHEMA_SERVICE_UPDATE,
|
|
SERVICE_HOMEASSISTANT_UPDATE: SCHEMA_SERVICE_UPDATE,
|
|
SERVICE_ADDON_INSTALL: SCHEMA_SERVICE_ADDONS_VERSION,
|
|
SERVICE_ADDON_UNINSTALL: SCHEMA_SERVICE_ADDONS,
|
|
SERVICE_ADDON_START: SCHEMA_SERVICE_ADDONS,
|
|
SERVICE_ADDON_STOP: SCHEMA_SERVICE_ADDONS,
|
|
SERVICE_ADDON_UPDATE: SCHEMA_SERVICE_ADDONS_VERSION,
|
|
}
|
|
|
|
|
|
@asyncio.coroutine
|
|
def async_setup(hass, config):
|
|
"""Setup the hassio component."""
|
|
try:
|
|
host = os.environ['HASSIO']
|
|
except KeyError:
|
|
_LOGGER.error("No HassIO supervisor detect!")
|
|
return False
|
|
|
|
websession = async_get_clientsession(hass)
|
|
hassio = HassIO(hass.loop, websession, host)
|
|
|
|
api_ok = yield from hassio.is_connected()
|
|
if not api_ok:
|
|
_LOGGER.error("Not connected with HassIO!")
|
|
return False
|
|
|
|
# register base api views
|
|
for base in ('host', 'homeassistant'):
|
|
hass.http.register_view(HassIOBaseView(hassio, base))
|
|
for base in ('supervisor', 'network'):
|
|
hass.http.register_view(HassIOBaseEditView(hassio, base))
|
|
|
|
# register view for addons
|
|
hass.http.register_view(HassIOAddonsView(hassio))
|
|
|
|
@asyncio.coroutine
|
|
def async_service_handler(service):
|
|
"""Handle HassIO service calls."""
|
|
addon = service.data.get(ATTR_ADDON)
|
|
if ATTR_VERSION in service.data:
|
|
version = {ATTR_VERSION: service.data[ATTR_VERSION]}
|
|
else:
|
|
version = None
|
|
|
|
# map to api call
|
|
if service.service == SERVICE_HOST_UPDATE:
|
|
yield from hassio.send_command(
|
|
"/host/update", payload=version)
|
|
elif service.service == SERVICE_HOST_REBOOT:
|
|
yield from hassio.send_command("/host/reboot")
|
|
elif service.service == SERVICE_HOST_SHUTDOWN:
|
|
yield from hassio.send_command("/host/shutdown")
|
|
elif service.service == SERVICE_SUPERVISOR_UPDATE:
|
|
yield from hassio.send_command(
|
|
"/supervisor/update", payload=version)
|
|
elif service.service == SERVICE_HOMEASSISTANT_UPDATE:
|
|
yield from hassio.send_command(
|
|
"/homeassistant/update", payload=version,
|
|
timeout=LONG_TASK_TIMEOUT)
|
|
elif service.service == SERVICE_ADDON_INSTALL:
|
|
yield from hassio.send_command(
|
|
"/addons/{}/install".format(addon), payload=version,
|
|
timeout=LONG_TASK_TIMEOUT)
|
|
elif service.service == SERVICE_ADDON_UNINSTALL:
|
|
yield from hassio.send_command(
|
|
"/addons/{}/uninstall".format(addon))
|
|
elif service.service == SERVICE_ADDON_START:
|
|
yield from hassio.send_command("/addons/{}/start".format(addon))
|
|
elif service.service == SERVICE_ADDON_STOP:
|
|
yield from hassio.send_command(
|
|
"/addons/{}/stop".format(addon), timeout=LONG_TASK_TIMEOUT)
|
|
elif service.service == SERVICE_ADDON_UPDATE:
|
|
yield from hassio.send_command(
|
|
"/addons/{}/update".format(addon), payload=version,
|
|
timeout=LONG_TASK_TIMEOUT)
|
|
|
|
descriptions = yield from hass.loop.run_in_executor(
|
|
None, load_yaml_config_file, os.path.join(
|
|
os.path.dirname(__file__), 'services.yaml'))
|
|
|
|
for service, schema in SERVICE_MAP.items():
|
|
hass.services.async_register(
|
|
DOMAIN, service, async_service_handler,
|
|
descriptions[DOMAIN][service], schema=schema)
|
|
|
|
return True
|
|
|
|
|
|
class HassIO(object):
|
|
"""Small API wrapper for HassIO."""
|
|
|
|
def __init__(self, loop, websession, ip):
|
|
"""Initialze HassIO api."""
|
|
self.loop = loop
|
|
self.websession = websession
|
|
self._ip = ip
|
|
|
|
def is_connected(self):
|
|
"""Return True if it connected to HassIO supervisor.
|
|
|
|
Return a coroutine.
|
|
"""
|
|
return self.send_command("/supervisor/ping")
|
|
|
|
@asyncio.coroutine
|
|
def send_command(self, cmd, payload=None, timeout=DEFAULT_TIMEOUT):
|
|
"""Send request to API."""
|
|
answer = yield from self.send_raw(
|
|
cmd, payload=payload, timeout=timeout
|
|
)
|
|
if answer and answer['result'] == 'ok':
|
|
return answer['data'] if answer['data'] else True
|
|
|
|
_LOGGER.error("%s return error %s.", cmd, answer['message'])
|
|
return False
|
|
|
|
@asyncio.coroutine
|
|
def send_raw(self, cmd, payload=None, timeout=DEFAULT_TIMEOUT):
|
|
"""Send raw request to API."""
|
|
try:
|
|
with async_timeout.timeout(timeout, loop=self.loop):
|
|
request = yield from self.websession.get(
|
|
"http://{}{}".format(self._ip, cmd),
|
|
timeout=None, json=payload
|
|
)
|
|
|
|
if request.status != 200:
|
|
_LOGGER.error("%s return code %d.", cmd, request.status)
|
|
return
|
|
|
|
return (yield from request.json())
|
|
|
|
except asyncio.TimeoutError:
|
|
_LOGGER.error("Timeout on api request %s.", cmd)
|
|
|
|
except aiohttp.ClientError:
|
|
_LOGGER.error("Client error on api request %s.", cmd)
|
|
|
|
|
|
class HassIOBaseView(HomeAssistantView):
|
|
"""HassIO view to handle base part."""
|
|
|
|
requires_auth = True
|
|
|
|
def __init__(self, hassio, base):
|
|
"""Initialize a hassio base view."""
|
|
self.hassio = hassio
|
|
self._url_info = "/{}/info".format(base)
|
|
|
|
self.url = "/api/hassio/{}".format(base)
|
|
self.name = "api:hassio:{}".format(base)
|
|
|
|
@asyncio.coroutine
|
|
def get(self, request):
|
|
"""Get base data."""
|
|
data = yield from self.hassio.send_command(self._url_info)
|
|
if not data:
|
|
raise HTTPBadGateway()
|
|
return web.json_response(data)
|
|
|
|
|
|
class HassIOBaseEditView(HassIOBaseView):
|
|
"""HassIO view to handle base with options support."""
|
|
|
|
def __init__(self, hassio, base):
|
|
"""Initialize a hassio base edit view."""
|
|
super().__init__(hassio, base)
|
|
self._url_options = "/{}/options".format(base)
|
|
|
|
@asyncio.coroutine
|
|
def post(self, request):
|
|
"""Set options on host."""
|
|
data = yield from request.json()
|
|
|
|
response = yield from self.hassio.send_raw(
|
|
self._url_options, payload=data)
|
|
if not response:
|
|
raise HTTPBadGateway()
|
|
return web.json_response(response)
|
|
|
|
|
|
class HassIOAddonsView(HomeAssistantView):
|
|
"""HassIO view to handle addons part."""
|
|
|
|
requires_auth = True
|
|
url = "/api/hassio/addons/{addon}"
|
|
name = "api:hassio:addons"
|
|
|
|
def __init__(self, hassio):
|
|
"""Initialize a hassio addon view."""
|
|
self.hassio = hassio
|
|
|
|
@asyncio.coroutine
|
|
def get(self, request, addon):
|
|
"""Get addon data."""
|
|
data = yield from self.hassio.send_command(
|
|
"/addons/{}/info".format(addon))
|
|
if not data:
|
|
raise HTTPBadGateway()
|
|
return web.json_response(data)
|
|
|
|
@asyncio.coroutine
|
|
def post(self, request, addon):
|
|
"""Set options on host."""
|
|
data = yield from request.json()
|
|
|
|
response = yield from self.hassio.send_raw(
|
|
"/addons/{}/options".format(addon), payload=data)
|
|
if not response:
|
|
raise HTTPBadGateway()
|
|
return web.json_response(response)
|