diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py new file mode 100644 index 00000000000..8b465abb9e9 --- /dev/null +++ b/homeassistant/components/configurator.py @@ -0,0 +1,155 @@ +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" + +_INSTANCES = {} +_LOGGER = logging.getLogger(__name__) + + +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. """ + + return _get_instance(hass).request_config( + name, callback, + description, description_image, submit_caption, fields) + + +def notify_errors(hass, request_id, error): + _get_instance(hass).notify_errors(request_id, error) + + +def request_done(hass, request_id): + _get_instance(hass).request_done(request_id) + + +def setup(hass, config): + return True + + +def _get_instance(hass): + """ Get an instance per hass object. """ + try: + return _INSTANCES[hass] + except KeyError: + print("Creating instance") + _INSTANCES[hass] = Configurator(hass) + + if DOMAIN not in hass.components: + hass.components.append(DOMAIN) + + return _INSTANCES[hass] + + +class Configurator(object): + def __init__(self, hass): + 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): + """ 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 passed down + # with the service request (limitation current design). + # Instead we will set it to configured right away and remove it soon. + def deferred_remove(event): + self.hass.states.remove(entity_id) + + self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove) + + self.hass.states.set(entity_id, STATE_CONFIGURED) + + def handle_service_call(self, call): + request_id = call.data.get(ATTR_CONFIGURE_ID) + + if not self._validate_request_id(request_id): + return + + entity_id, fields, callback = self._requests[request_id] + + # TODO field validation? + + 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): + if request_id not in self._requests: + _LOGGER.error("Invalid configure id received: %s", request_id) + return False + + return True diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 4076ca63159..62dafffa9d6 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -5,6 +5,7 @@ homeassistant.components.demo Sets up a demo environment that mimics interaction with devices """ import random +import time import homeassistant as ha import homeassistant.loader as loader @@ -28,6 +29,7 @@ DEPENDENCIES = [] def setup(hass, config): """ Setup a demo environment. """ group = loader.get_component('group') + configurator = loader.get_component('configurator') config.setdefault(ha.DOMAIN, {}) config.setdefault(DOMAIN, {}) @@ -170,4 +172,30 @@ def setup(hass, config): ATTR_AWAY_MODE: STATE_OFF }) + configurator_ids = [] + + def hue_configuration_callback(data): + """ Fake callback, mark config as done. """ + time.sleep(2) + + # First time it is called, pretend it failed. + if len(configurator_ids) == 1: + configurator.notify_errors( + hass, configurator_ids[0], + "Failed to register, please try again.") + + configurator_ids.append(0) + else: + configurator.request_done(hass, configurator_ids[0]) + + request_id = configurator.request_config( + hass, "Philips Hue", hue_configuration_callback, + description=("Press the button on the bridge to register Philips Hue " + "with Home Assistant."), + description_image="/static/images/config_philips_hue.jpg", + submit_caption="I have pressed the button" + ) + + configurator_ids.append(request_id) + return True diff --git a/homeassistant/components/http/frontend.py b/homeassistant/components/http/frontend.py index 48100f97ac7..df97ffe708f 100644 --- a/homeassistant/components/http/frontend.py +++ b/homeassistant/components/http/frontend.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "951c0a4e0adb70ec1f0f7e4c76955ed9" +VERSION = "f299ce624d1641191f6f6a9b4b4d05bc" diff --git a/homeassistant/components/http/www_static/frontend.html b/homeassistant/components/http/www_static/frontend.html index 273f01f46bb..7129040444c 100644 --- a/homeassistant/components/http/www_static/frontend.html +++ b/homeassistant/components/http/www_static/frontend.html @@ -50,7 +50,7 @@ if(this.removeAttribute(a),d)return j(this,a,c);var e=c,f=m(this,a,e);return j(t
+
It looks like we have nothing to show you right now. It could be that we have not yet discovered all your devices but it is more likely that you have not configured Home Assistant yet.
Please see the Getting Started section on how to setup your devices. @@ -71,7 +71,7 @@ if(this.removeAttribute(a),d)return j(this,a,c);var e=c,f=m(this,a,e);return j(t
This dialog will update the representation of the device within Home Assistant.
This will not communicate with the actual device.
-
+ {{stateObj.attributes.description}} +
+ Errors: {{stateObj.attributes.errors}} +