2017-02-14 05:34:36 +00:00
|
|
|
"""Component to configure Home Assistant via an API."""
|
2017-02-12 01:29:05 +00:00
|
|
|
import asyncio
|
2017-02-21 05:53:55 +00:00
|
|
|
import os
|
|
|
|
|
|
|
|
import voluptuous as vol
|
2017-02-12 01:29:05 +00:00
|
|
|
|
2017-02-14 05:34:36 +00:00
|
|
|
from homeassistant.core import callback
|
2017-05-10 01:44:00 +00:00
|
|
|
from homeassistant.const import EVENT_COMPONENT_LOADED, CONF_ID
|
2017-03-05 09:41:54 +00:00
|
|
|
from homeassistant.setup import (
|
2017-02-14 05:34:36 +00:00
|
|
|
async_prepare_setup_platform, ATTR_COMPONENT)
|
2017-02-21 05:53:55 +00:00
|
|
|
from homeassistant.components.http import HomeAssistantView
|
|
|
|
from homeassistant.util.yaml import load_yaml, dump
|
2017-02-12 01:29:05 +00:00
|
|
|
|
|
|
|
DOMAIN = 'config'
|
|
|
|
DEPENDENCIES = ['http']
|
2018-02-24 18:53:59 +00:00
|
|
|
SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script',
|
2018-04-03 21:23:21 +00:00
|
|
|
'entity_registry', 'config_entries')
|
2018-02-16 22:07:38 +00:00
|
|
|
ON_DEMAND = ('zwave',)
|
2017-02-12 01:29:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_setup(hass, config):
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Set up the config component."""
|
2017-10-25 02:36:27 +00:00
|
|
|
yield from hass.components.frontend.async_register_built_in_panel(
|
2017-10-27 05:29:59 +00:00
|
|
|
'config', 'config', 'mdi:settings')
|
2017-02-12 01:29:05 +00:00
|
|
|
|
2017-02-14 05:34:36 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def setup_panel(panel_name):
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Set up a panel."""
|
|
|
|
panel = yield from async_prepare_setup_platform(
|
|
|
|
hass, config, DOMAIN, panel_name)
|
2017-02-12 01:29:05 +00:00
|
|
|
|
|
|
|
if not panel:
|
2017-02-14 05:34:36 +00:00
|
|
|
return
|
2017-02-12 01:29:05 +00:00
|
|
|
|
|
|
|
success = yield from panel.async_setup(hass)
|
|
|
|
|
|
|
|
if success:
|
2017-02-14 05:34:36 +00:00
|
|
|
key = '{}.{}'.format(DOMAIN, panel_name)
|
|
|
|
hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key})
|
|
|
|
hass.config.components.add(key)
|
|
|
|
|
|
|
|
tasks = [setup_panel(panel_name) for panel_name in SECTIONS]
|
|
|
|
|
|
|
|
for panel_name in ON_DEMAND:
|
|
|
|
if panel_name in hass.config.components:
|
|
|
|
tasks.append(setup_panel(panel_name))
|
|
|
|
|
|
|
|
if tasks:
|
|
|
|
yield from asyncio.wait(tasks, loop=hass.loop)
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def component_loaded(event):
|
|
|
|
"""Respond to components being loaded."""
|
|
|
|
panel_name = event.data.get(ATTR_COMPONENT)
|
|
|
|
if panel_name in ON_DEMAND:
|
|
|
|
hass.async_add_job(setup_panel(panel_name))
|
|
|
|
|
|
|
|
hass.bus.async_listen(EVENT_COMPONENT_LOADED, component_loaded)
|
2017-02-12 01:29:05 +00:00
|
|
|
|
|
|
|
return True
|
2017-02-21 05:53:55 +00:00
|
|
|
|
|
|
|
|
2017-05-10 01:44:00 +00:00
|
|
|
class BaseEditConfigView(HomeAssistantView):
|
2017-02-21 05:53:55 +00:00
|
|
|
"""Configure a Group endpoint."""
|
|
|
|
|
|
|
|
def __init__(self, component, config_type, path, key_schema, data_schema,
|
|
|
|
*, post_write_hook=None):
|
|
|
|
"""Initialize a config view."""
|
|
|
|
self.url = '/api/config/%s/%s/{config_key}' % (component, config_type)
|
|
|
|
self.name = 'api:config:%s:%s' % (component, config_type)
|
|
|
|
self.path = path
|
|
|
|
self.key_schema = key_schema
|
|
|
|
self.data_schema = data_schema
|
|
|
|
self.post_write_hook = post_write_hook
|
|
|
|
|
2017-05-10 01:44:00 +00:00
|
|
|
def _empty_config(self):
|
|
|
|
"""Empty config if file not found."""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2017-08-26 17:02:32 +00:00
|
|
|
def _get_value(self, hass, data, config_key):
|
2017-05-10 01:44:00 +00:00
|
|
|
"""Get value."""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2017-08-26 17:02:32 +00:00
|
|
|
def _write_value(self, hass, data, config_key, new_value):
|
2017-05-10 01:44:00 +00:00
|
|
|
"""Set value."""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2017-02-21 05:53:55 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def get(self, request, config_key):
|
|
|
|
"""Fetch device specific config."""
|
|
|
|
hass = request.app['hass']
|
2017-05-10 01:44:00 +00:00
|
|
|
current = yield from self.read_config(hass)
|
2017-08-26 17:02:32 +00:00
|
|
|
value = self._get_value(hass, current, config_key)
|
2017-05-10 01:44:00 +00:00
|
|
|
|
|
|
|
if value is None:
|
|
|
|
return self.json_message('Resource not found', 404)
|
|
|
|
|
|
|
|
return self.json(value)
|
2017-02-21 05:53:55 +00:00
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def post(self, request, config_key):
|
|
|
|
"""Validate config and return results."""
|
|
|
|
try:
|
|
|
|
data = yield from request.json()
|
|
|
|
except ValueError:
|
|
|
|
return self.json_message('Invalid JSON specified', 400)
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.key_schema(config_key)
|
|
|
|
except vol.Invalid as err:
|
|
|
|
return self.json_message('Key malformed: {}'.format(err), 400)
|
|
|
|
|
|
|
|
try:
|
|
|
|
# We just validate, we don't store that data because
|
|
|
|
# we don't want to store the defaults.
|
|
|
|
self.data_schema(data)
|
|
|
|
except vol.Invalid as err:
|
|
|
|
return self.json_message('Message malformed: {}'.format(err), 400)
|
|
|
|
|
|
|
|
hass = request.app['hass']
|
|
|
|
path = hass.config.path(self.path)
|
|
|
|
|
2017-05-10 01:44:00 +00:00
|
|
|
current = yield from self.read_config(hass)
|
2017-08-26 17:02:32 +00:00
|
|
|
self._write_value(hass, current, config_key, data)
|
2017-02-21 05:53:55 +00:00
|
|
|
|
2017-05-10 01:44:00 +00:00
|
|
|
yield from hass.async_add_job(_write, path, current)
|
2017-02-21 05:53:55 +00:00
|
|
|
|
|
|
|
if self.post_write_hook is not None:
|
|
|
|
hass.async_add_job(self.post_write_hook(hass))
|
|
|
|
|
|
|
|
return self.json({
|
|
|
|
'result': 'ok',
|
|
|
|
})
|
|
|
|
|
2017-05-10 01:44:00 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def read_config(self, hass):
|
|
|
|
"""Read the config."""
|
|
|
|
current = yield from hass.async_add_job(
|
|
|
|
_read, hass.config.path(self.path))
|
|
|
|
if not current:
|
|
|
|
current = self._empty_config()
|
|
|
|
return current
|
|
|
|
|
|
|
|
|
|
|
|
class EditKeyBasedConfigView(BaseEditConfigView):
|
|
|
|
"""Configure a list of entries."""
|
|
|
|
|
|
|
|
def _empty_config(self):
|
|
|
|
"""Return an empty config."""
|
|
|
|
return {}
|
|
|
|
|
2017-08-26 17:02:32 +00:00
|
|
|
def _get_value(self, hass, data, config_key):
|
2017-05-10 01:44:00 +00:00
|
|
|
"""Get value."""
|
2018-02-21 12:16:08 +00:00
|
|
|
return data.get(config_key)
|
2017-05-10 01:44:00 +00:00
|
|
|
|
2017-08-26 17:02:32 +00:00
|
|
|
def _write_value(self, hass, data, config_key, new_value):
|
2017-05-10 01:44:00 +00:00
|
|
|
"""Set value."""
|
|
|
|
data.setdefault(config_key, {}).update(new_value)
|
|
|
|
|
|
|
|
|
|
|
|
class EditIdBasedConfigView(BaseEditConfigView):
|
|
|
|
"""Configure key based config entries."""
|
|
|
|
|
|
|
|
def _empty_config(self):
|
|
|
|
"""Return an empty config."""
|
|
|
|
return []
|
|
|
|
|
2017-08-26 17:02:32 +00:00
|
|
|
def _get_value(self, hass, data, config_key):
|
2017-05-10 01:44:00 +00:00
|
|
|
"""Get value."""
|
|
|
|
return next(
|
|
|
|
(val for val in data if val.get(CONF_ID) == config_key), None)
|
|
|
|
|
2017-08-26 17:02:32 +00:00
|
|
|
def _write_value(self, hass, data, config_key, new_value):
|
2017-05-10 01:44:00 +00:00
|
|
|
"""Set value."""
|
2017-08-26 17:02:32 +00:00
|
|
|
value = self._get_value(hass, data, config_key)
|
2017-05-10 01:44:00 +00:00
|
|
|
|
|
|
|
if value is None:
|
|
|
|
value = {CONF_ID: config_key}
|
|
|
|
data.append(value)
|
|
|
|
|
|
|
|
value.update(new_value)
|
|
|
|
|
2017-02-21 05:53:55 +00:00
|
|
|
|
|
|
|
def _read(path):
|
|
|
|
"""Read YAML helper."""
|
|
|
|
if not os.path.isfile(path):
|
2017-05-10 01:44:00 +00:00
|
|
|
return None
|
2017-02-21 05:53:55 +00:00
|
|
|
|
|
|
|
return load_yaml(path)
|
|
|
|
|
|
|
|
|
|
|
|
def _write(path, data):
|
|
|
|
"""Write YAML helper."""
|
2017-02-26 23:28:12 +00:00
|
|
|
# Do it before opening file. If dump causes error it will now not
|
|
|
|
# truncate the file.
|
|
|
|
data = dump(data)
|
2017-02-21 05:53:55 +00:00
|
|
|
with open(path, 'w', encoding='utf-8') as outfile:
|
2017-02-26 23:28:12 +00:00
|
|
|
outfile.write(data)
|