core/homeassistant/components/lovelace/__init__.py

240 lines
6.6 KiB
Python
Raw Normal View History

"""Support for the Lovelace UI."""
from functools import wraps
import logging
import os
import time
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.yaml import load_yaml
_LOGGER = logging.getLogger(__name__)
2019-07-31 19:25:30 +00:00
DOMAIN = "lovelace"
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
2019-07-31 19:25:30 +00:00
CONF_MODE = "mode"
MODE_YAML = "yaml"
MODE_STORAGE = "storage"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Optional(CONF_MODE, default=MODE_STORAGE): vol.All(
vol.Lower, vol.In([MODE_YAML, MODE_STORAGE])
)
}
)
},
extra=vol.ALLOW_EXTRA,
)
2019-07-31 19:25:30 +00:00
EVENT_LOVELACE_UPDATED = "lovelace_updated"
2019-07-31 19:25:30 +00:00
LOVELACE_CONFIG_FILE = "ui-lovelace.yaml"
2019-07-31 19:25:30 +00:00
WS_TYPE_GET_LOVELACE_UI = "lovelace/config"
WS_TYPE_SAVE_CONFIG = "lovelace/config/save"
2019-07-31 19:25:30 +00:00
SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): WS_TYPE_GET_LOVELACE_UI,
vol.Optional("force", default=False): bool,
}
)
2019-07-31 19:25:30 +00:00
SCHEMA_SAVE_CONFIG = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): WS_TYPE_SAVE_CONFIG,
vol.Required("config"): vol.Any(str, dict),
}
)
class ConfigNotFound(HomeAssistantError):
"""When no config available."""
async def async_setup(hass, config):
"""Set up the Lovelace commands."""
# Pass in default to `get` because defaults not set if loaded as dep
mode = config.get(DOMAIN, {}).get(CONF_MODE, MODE_STORAGE)
hass.components.frontend.async_register_built_in_panel(
2019-07-31 19:25:30 +00:00
DOMAIN, config={"mode": mode}
)
if mode == MODE_YAML:
hass.data[DOMAIN] = LovelaceYAML(hass)
else:
hass.data[DOMAIN] = LovelaceStorage(hass)
hass.components.websocket_api.async_register_command(
2019-07-31 19:25:30 +00:00
WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, SCHEMA_GET_LOVELACE_UI
)
hass.components.websocket_api.async_register_command(
2019-07-31 19:25:30 +00:00
WS_TYPE_SAVE_CONFIG, websocket_lovelace_save_config, SCHEMA_SAVE_CONFIG
)
2019-07-31 19:25:30 +00:00
hass.components.system_health.async_register_info(DOMAIN, system_health_info)
2019-01-30 20:57:56 +00:00
return True
class LovelaceStorage:
"""Class to handle Storage based Lovelace config."""
def __init__(self, hass):
"""Initialize Lovelace config based on storage helper."""
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
self._data = None
self._hass = hass
2019-01-30 20:57:56 +00:00
async def async_get_info(self):
"""Return the YAML storage mode."""
if self._data is None:
await self._load()
2019-07-31 19:25:30 +00:00
if self._data["config"] is None:
return {"mode": "auto-gen"}
2019-01-30 20:57:56 +00:00
2019-07-31 19:25:30 +00:00
return _config_info("storage", self._data["config"])
2019-01-30 20:57:56 +00:00
async def async_load(self, force):
"""Load config."""
if self._data is None:
2019-01-30 20:57:56 +00:00
await self._load()
2018-10-26 15:29:33 +00:00
2019-07-31 19:25:30 +00:00
config = self._data["config"]
if config is None:
raise ConfigNotFound
return config
async def async_save(self, config):
"""Save config."""
if self._data is None:
2019-01-30 20:57:56 +00:00
await self._load()
2019-07-31 19:25:30 +00:00
self._data["config"] = config
self._hass.bus.async_fire(EVENT_LOVELACE_UPDATED)
await self._store.async_save(self._data)
2019-01-30 20:57:56 +00:00
async def _load(self):
"""Load the config."""
data = await self._store.async_load()
2019-07-31 19:25:30 +00:00
self._data = data if data else {"config": None}
2019-01-30 20:57:56 +00:00
class LovelaceYAML:
"""Class to handle YAML-based Lovelace config."""
def __init__(self, hass):
"""Initialize the YAML config."""
self.hass = hass
self._cache = None
2019-01-30 20:57:56 +00:00
async def async_get_info(self):
"""Return the YAML storage mode."""
try:
config = await self.async_load(False)
except ConfigNotFound:
return {
2019-07-31 19:25:30 +00:00
"mode": "yaml",
"error": "{} not found".format(
self.hass.config.path(LOVELACE_CONFIG_FILE)
),
2019-01-30 20:57:56 +00:00
}
2019-07-31 19:25:30 +00:00
return _config_info("yaml", config)
2019-01-30 20:57:56 +00:00
async def async_load(self, force):
"""Load config."""
is_updated, config = await self.hass.async_add_executor_job(
self._load_config, force
)
if is_updated:
self.hass.bus.async_fire(EVENT_LOVELACE_UPDATED)
return config
def _load_config(self, force):
"""Load the actual config."""
fname = self.hass.config.path(LOVELACE_CONFIG_FILE)
# Check for a cached version of the config
if not force and self._cache is not None:
config, last_update = self._cache
modtime = os.path.getmtime(fname)
if config and last_update > modtime:
return False, config
is_updated = self._cache is not None
try:
config = load_yaml(fname)
except FileNotFoundError:
raise ConfigNotFound from None
self._cache = (config, time.time())
return is_updated, config
async def async_save(self, config):
"""Save config."""
2019-07-31 19:25:30 +00:00
raise HomeAssistantError("Not supported")
def handle_yaml_errors(func):
"""Handle error with WebSocket calls."""
2019-07-31 19:25:30 +00:00
@wraps(func)
async def send_with_error_handling(hass, connection, msg):
error = None
try:
result = await func(hass, connection, msg)
except ConfigNotFound:
2019-07-31 19:25:30 +00:00
error = "config_not_found", "No config found."
except HomeAssistantError as err:
2019-07-31 19:25:30 +00:00
error = "error", str(err)
if error is not None:
2019-07-31 19:25:30 +00:00
connection.send_error(msg["id"], *error)
return
if msg is not None:
2019-07-31 19:25:30 +00:00
await connection.send_big_result(msg["id"], result)
else:
2019-07-31 19:25:30 +00:00
connection.send_result(msg["id"], result)
return send_with_error_handling
@websocket_api.async_response
@handle_yaml_errors
async def websocket_lovelace_config(hass, connection, msg):
"""Send Lovelace UI config over WebSocket configuration."""
2019-07-31 19:25:30 +00:00
return await hass.data[DOMAIN].async_load(msg["force"])
@websocket_api.async_response
@handle_yaml_errors
async def websocket_lovelace_save_config(hass, connection, msg):
"""Save Lovelace UI configuration."""
2019-07-31 19:25:30 +00:00
await hass.data[DOMAIN].async_save(msg["config"])
2019-01-30 20:57:56 +00:00
async def system_health_info(hass):
"""Get info for the info page."""
return await hass.data[DOMAIN].async_get_info()
def _config_info(mode, config):
"""Generate info about the config."""
return {
2019-07-31 19:25:30 +00:00
"mode": mode,
"resources": len(config.get("resources", [])),
"views": len(config.get("views", [])),
2019-01-30 20:57:56 +00:00
}