core/homeassistant/components/configurator.py

193 lines
5.7 KiB
Python
Raw Normal View History

2015-01-20 05:39:24 +00:00
"""
2016-03-07 17:49:31 +00:00
Support to allow pieces of code to request configuration from the user.
2015-01-20 05:39:24 +00:00
Initiate a request by calling the `request_config` method with a callback.
This will return a request id that has to be used for future calls.
A callback has to be provided to `request_config` which will be called when
the user has submitted configuration information.
"""
import asyncio
import logging
from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
ATTR_ENTITY_PICTURE
2016-02-19 05:27:50 +00:00
from homeassistant.helpers.entity import generate_entity_id
_LOGGER = logging.getLogger(__name__)
_REQUESTS = {}
_KEY_INSTANCE = 'configurator'
ATTR_CONFIGURE_ID = 'configure_id'
ATTR_DESCRIPTION = 'description'
ATTR_DESCRIPTION_IMAGE = 'description_image'
ATTR_ERRORS = 'errors'
ATTR_FIELDS = 'fields'
ATTR_LINK_NAME = 'link_name'
ATTR_LINK_URL = 'link_url'
ATTR_SUBMIT_CAPTION = 'submit_caption'
DOMAIN = 'configurator'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SERVICE_CONFIGURE = 'configure'
STATE_CONFIGURE = 'configure'
STATE_CONFIGURED = 'configured'
def request_config(
hass, name, callback, description=None, description_image=None,
submit_caption=None, fields=None, link_name=None, link_url=None,
entity_picture=None):
2016-03-07 17:49:31 +00:00
"""Create a new request for configuration.
2016-03-08 16:55:57 +00:00
2016-03-07 17:49:31 +00:00
Will return an ID to be used for sequent calls.
"""
2015-01-20 05:39:24 +00:00
instance = _get_instance(hass)
request_id = instance.request_config(
name, callback,
description, description_image, submit_caption,
fields, link_name, link_url, entity_picture)
2015-01-20 05:39:24 +00:00
_REQUESTS[request_id] = instance
return request_id
2015-01-20 05:39:24 +00:00
def notify_errors(request_id, error):
2016-03-07 17:49:31 +00:00
"""Add errors to a config request."""
2015-01-20 05:39:24 +00:00
try:
_REQUESTS[request_id].notify_errors(request_id, error)
except KeyError:
# If request_id does not exist
pass
2015-01-20 05:39:24 +00:00
def request_done(request_id):
2016-03-07 17:49:31 +00:00
"""Mark a configuration request as done."""
2015-01-20 05:39:24 +00:00
try:
_REQUESTS.pop(request_id).request_done(request_id)
except KeyError:
# If request_id does not exist
pass
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the configurator component."""
return True
def _get_instance(hass):
2016-03-07 17:49:31 +00:00
"""Get an instance per hass object."""
instance = hass.data.get(_KEY_INSTANCE)
if instance is None:
instance = hass.data[_KEY_INSTANCE] = Configurator(hass)
return instance
class Configurator(object):
2016-03-08 16:55:57 +00:00
"""The class to keep track of current configuration requests."""
def __init__(self, hass):
2016-03-08 16:55:57 +00:00
"""Initialize the configurator."""
self.hass = hass
self._cur_id = 0
self._requests = {}
hass.services.register(
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call)
def request_config(
self, name, callback,
description, description_image, submit_caption,
fields, link_name, link_url, entity_picture):
"""Set up a request for configuration."""
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass)
if fields is None:
fields = []
request_id = self._generate_unique_id()
self._requests[request_id] = (entity_id, fields, callback)
data = {
ATTR_CONFIGURE_ID: request_id,
ATTR_FIELDS: fields,
2016-06-11 02:45:15 +00:00
ATTR_FRIENDLY_NAME: name,
ATTR_ENTITY_PICTURE: entity_picture,
}
data.update({
key: value for key, value in [
(ATTR_DESCRIPTION, description),
(ATTR_DESCRIPTION_IMAGE, description_image),
(ATTR_SUBMIT_CAPTION, submit_caption),
(ATTR_LINK_NAME, link_name),
(ATTR_LINK_URL, link_url),
] if value is not None
})
self.hass.states.set(entity_id, STATE_CONFIGURE, data)
return request_id
def notify_errors(self, request_id, error):
2016-03-07 17:49:31 +00:00
"""Update the state with errors."""
if not self._validate_request_id(request_id):
return
entity_id = self._requests[request_id][0]
state = self.hass.states.get(entity_id)
2016-02-10 07:27:01 +00:00
new_data = dict(state.attributes)
new_data[ATTR_ERRORS] = error
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
def request_done(self, request_id):
2016-03-07 17:49:31 +00:00
"""Remove the configuration request."""
if not self._validate_request_id(request_id):
return
entity_id = self._requests.pop(request_id)[0]
2015-01-20 05:39:24 +00:00
# If we remove the state right away, it will not be included with
2015-01-20 06:40:30 +00:00
# the result fo the service call (current design limitation).
# Instead, we will set it to configured to give as feedback but delete
2015-01-20 05:39:24 +00:00
# it shortly after so that it is deleted when the client updates.
2015-01-20 06:40:30 +00:00
self.hass.states.set(entity_id, STATE_CONFIGURED)
def deferred_remove(event):
2016-03-07 17:49:31 +00:00
"""Remove the request state."""
2015-01-20 06:40:30 +00:00
self.hass.states.remove(entity_id)
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
2015-01-20 05:39:24 +00:00
def handle_service_call(self, call):
2016-03-07 17:49:31 +00:00
"""Handle a configure service call."""
request_id = call.data.get(ATTR_CONFIGURE_ID)
if not self._validate_request_id(request_id):
return
2015-01-20 05:39:24 +00:00
# pylint: disable=unused-variable
entity_id, fields, callback = self._requests[request_id]
2015-01-20 05:39:24 +00:00
# field validation goes here?
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
def _generate_unique_id(self):
2016-03-08 16:55:57 +00:00
"""Generate a unique configurator ID."""
self._cur_id += 1
return "{}-{}".format(id(self), self._cur_id)
def _validate_request_id(self, request_id):
2016-03-07 17:49:31 +00:00
"""Validate that the request belongs to this instance."""
2015-01-20 05:39:24 +00:00
return request_id in self._requests