191 lines
5.4 KiB
Python
191 lines
5.4 KiB
Python
"""
|
|
homeassistant.components.configurator
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
A component to allow pieces of code to request configuration from the user.
|
|
|
|
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 logging
|
|
|
|
from homeassistant.helpers import generate_entity_id
|
|
from homeassistant.const import EVENT_TIME_CHANGED
|
|
|
|
DOMAIN = "configurator"
|
|
DEPENDENCIES = []
|
|
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
|
|
|
SERVICE_CONFIGURE = "configure"
|
|
|
|
STATE_CONFIGURE = "configure"
|
|
STATE_CONFIGURED = "configured"
|
|
|
|
ATTR_CONFIGURE_ID = "configure_id"
|
|
ATTR_DESCRIPTION = "description"
|
|
ATTR_DESCRIPTION_IMAGE = "description_image"
|
|
ATTR_SUBMIT_CAPTION = "submit_caption"
|
|
ATTR_FIELDS = "fields"
|
|
ATTR_ERRORS = "errors"
|
|
|
|
_REQUESTS = {}
|
|
_INSTANCES = {}
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
# pylint: disable=too-many-arguments
|
|
def request_config(
|
|
hass, name, callback, description=None, description_image=None,
|
|
submit_caption=None, fields=None):
|
|
""" Create a new request for config.
|
|
Will return an ID to be used for sequent calls. """
|
|
|
|
instance = _get_instance(hass)
|
|
|
|
request_id = instance.request_config(
|
|
name, callback,
|
|
description, description_image, submit_caption, fields)
|
|
|
|
_REQUESTS[request_id] = instance
|
|
|
|
return request_id
|
|
|
|
|
|
def notify_errors(request_id, error):
|
|
""" Add errors to a config request. """
|
|
try:
|
|
_REQUESTS[request_id].notify_errors(request_id, error)
|
|
except KeyError:
|
|
# If request_id does not exist
|
|
pass
|
|
|
|
|
|
def request_done(request_id):
|
|
""" Mark a config request as done. """
|
|
try:
|
|
_REQUESTS.pop(request_id).request_done(request_id)
|
|
except KeyError:
|
|
# If request_id does not exist
|
|
pass
|
|
|
|
|
|
def setup(hass, config):
|
|
""" Set up Configurator. """
|
|
return True
|
|
|
|
|
|
def _get_instance(hass):
|
|
""" Get an instance per hass object. """
|
|
try:
|
|
return _INSTANCES[hass]
|
|
except KeyError:
|
|
_INSTANCES[hass] = Configurator(hass)
|
|
|
|
if DOMAIN not in hass.config.components:
|
|
hass.config.components.append(DOMAIN)
|
|
|
|
return _INSTANCES[hass]
|
|
|
|
|
|
class Configurator(object):
|
|
"""
|
|
Class to keep track of current configuration requests.
|
|
"""
|
|
|
|
def __init__(self, hass):
|
|
self.hass = hass
|
|
self._cur_id = 0
|
|
self._requests = {}
|
|
hass.services.register(
|
|
DOMAIN, SERVICE_CONFIGURE, self.handle_service_call)
|
|
|
|
# pylint: disable=too-many-arguments
|
|
def request_config(
|
|
self, name, callback,
|
|
description, description_image, submit_caption, fields):
|
|
""" Setup 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,
|
|
}
|
|
|
|
data.update({
|
|
key: value for key, value in [
|
|
(ATTR_DESCRIPTION, description),
|
|
(ATTR_DESCRIPTION_IMAGE, description_image),
|
|
(ATTR_SUBMIT_CAPTION, submit_caption),
|
|
] if value is not None
|
|
})
|
|
|
|
self.hass.states.set(entity_id, STATE_CONFIGURE, data)
|
|
|
|
return request_id
|
|
|
|
def notify_errors(self, request_id, error):
|
|
""" 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)
|
|
|
|
new_data = state.attributes
|
|
new_data[ATTR_ERRORS] = error
|
|
|
|
self.hass.states.set(entity_id, STATE_CONFIGURE, new_data)
|
|
|
|
def request_done(self, request_id):
|
|
""" Remove the config request. """
|
|
if not self._validate_request_id(request_id):
|
|
return
|
|
|
|
entity_id = self._requests.pop(request_id)[0]
|
|
|
|
# If we remove the state right away, it will not be included with
|
|
# the result fo the service call (current design limitation).
|
|
# Instead, we will set it to configured to give as feedback but delete
|
|
# it shortly after so that it is deleted when the client updates.
|
|
self.hass.states.set(entity_id, STATE_CONFIGURED)
|
|
|
|
def deferred_remove(event):
|
|
""" Remove the request state. """
|
|
self.hass.states.remove(entity_id)
|
|
|
|
self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove)
|
|
|
|
def handle_service_call(self, call):
|
|
""" Handle a configure service call. """
|
|
request_id = call.data.get(ATTR_CONFIGURE_ID)
|
|
|
|
if not self._validate_request_id(request_id):
|
|
return
|
|
|
|
# pylint: disable=unused-variable
|
|
entity_id, fields, callback = self._requests[request_id]
|
|
|
|
# field validation goes here?
|
|
|
|
callback(call.data.get(ATTR_FIELDS, {}))
|
|
|
|
def _generate_unique_id(self):
|
|
""" Generates a unique configurator id. """
|
|
self._cur_id += 1
|
|
return "{}-{}".format(id(self), self._cur_id)
|
|
|
|
def _validate_request_id(self, request_id):
|
|
""" Validate that the request belongs to this instance. """
|
|
return request_id in self._requests
|