diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index b7b0f8ef439..a2be3780619 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -18,7 +18,8 @@ import functools as ft from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, - EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL) + EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL, + EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID) import homeassistant.util as util DOMAIN = "homeassistant" @@ -26,6 +27,9 @@ DOMAIN = "homeassistant" # How often time_changed event should fire TIMER_INTERVAL = 10 # seconds +# How long we wait for the result of a service call +SERVICE_CALL_LIMIT = 10 # seconds + # Number of worker threads POOL_NUM_THREAD = 4 @@ -227,6 +231,7 @@ class JobPriority(util.OrderedEnum): """ Provides priorities for bus events. """ # pylint: disable=no-init,too-few-public-methods + EVENT_CALLBACK = 0 EVENT_SERVICE = 1 EVENT_STATE = 2 EVENT_TIME = 3 @@ -241,6 +246,8 @@ class JobPriority(util.OrderedEnum): return JobPriority.EVENT_STATE elif event_type == EVENT_CALL_SERVICE: return JobPriority.EVENT_SERVICE + elif event_type == EVENT_SERVICE_EXECUTED: + return JobPriority.EVENT_CALLBACK else: return JobPriority.EVENT_DEFAULT @@ -594,6 +601,7 @@ class ServiceRegistry(object): self._lock = threading.Lock() self._pool = pool or create_worker_pool() self._bus = bus + self._cur_id = 0 bus.listen(EVENT_CALL_SERVICE, self._event_to_service_call) @property @@ -615,9 +623,14 @@ class ServiceRegistry(object): else: self._services[domain] = {service: service_func} - def call(self, domain, service, service_data=None): + def call(self, domain, service, service_data=None, blocking=False): """ - Fires event to call specified service. + Calls specified service. + Specify blocking=True to wait till service is executed. + Waits a maximum of SERVICE_CALL_LIMIT. + + If blocking = True, will return boolean if service executed + succesfully within SERVICE_CALL_LIMIT. This method will fire an event to call the service. This event will be picked up by this ServiceRegistry and any @@ -626,12 +639,41 @@ class ServiceRegistry(object): Because the service is sent as an event you are not allowed to use the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data. """ + call_id = self._generate_unique_id() event_data = service_data or {} event_data[ATTR_DOMAIN] = domain event_data[ATTR_SERVICE] = service + event_data[ATTR_SERVICE_CALL_ID] = call_id + + if blocking: + executed_event = threading.Event() + + def service_executed(call): + """ + Called when a service is executed. + Will set the event if matches our service call. + """ + if call.data[ATTR_SERVICE_CALL_ID] == call_id: + executed_event.set() + + self._bus.remove_listener( + EVENT_SERVICE_EXECUTED, service_executed) + + self._bus.listen(EVENT_SERVICE_EXECUTED, service_executed) self._bus.fire(EVENT_CALL_SERVICE, event_data) + if blocking: + # wait will return False if event not set after our limit has + # passed. If not set, clean up the listener + if not executed_event.wait(SERVICE_CALL_LIMIT): + self._bus.remove_listener( + EVENT_SERVICE_EXECUTED, service_executed) + + return False + + return True + def _event_to_service_call(self, event): """ Calls a service from an event. """ service_data = dict(event.data) @@ -642,9 +684,27 @@ class ServiceRegistry(object): if domain in self._services and service in self._services[domain]: service_call = ServiceCall(domain, service, service_data) + # Add a job to the pool that calls _execute_service self._pool.add_job(JobPriority.EVENT_SERVICE, - (self._services[domain][service], - service_call)) + (self._execute_service, + (self._services[domain][service], + service_call))) + + def _execute_service(self, service_and_call): + """ Executes a service and fires a SERVICE_EXECUTED event. """ + service, call = service_and_call + + service(call) + + self._bus.fire( + EVENT_SERVICE_EXECUTED, { + ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID] + }) + + def _generate_unique_id(self): + """ Generates a unique service call id. """ + self._cur_id += 1 + return "{}-{}".format(id(self), self._cur_id) class Timer(threading.Thread): diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 815f732d8ab..5bb139b2123 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -484,7 +484,7 @@ class RequestHandler(SimpleHTTPRequestHandler): domain = path_match.group('domain') service = path_match.group('service') - self.server.hass.services.call(domain, service, data) + self.server.hass.services.call(domain, service, data, True) self._json_message("Service {}/{} called.".format(domain, service)) diff --git a/homeassistant/components/http/frontend.py b/homeassistant/components/http/frontend.py index 5190b87f741..2feb9bc20fc 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 = "b32f2de4e3336946fe68cd1e5cd0fe6f" +VERSION = "5c3b2dd8a63197e380e798da8b057b0a" diff --git a/homeassistant/components/http/www_static/frontend.html b/homeassistant/components/http/www_static/frontend.html index a49e7c83367..ab03282f023 100644 --- a/homeassistant/components/http/www_static/frontend.html +++ b/homeassistant/components/http/www_static/frontend.html @@ -66,6 +66,6 @@ if(this.removeAttribute(a),d)return j(this,a,c);var e=c,f=m(this,a,e);return j(t + diff --git a/homeassistant/components/http/www_static/polymer/home-assistant-api.html b/homeassistant/components/http/www_static/polymer/home-assistant-api.html index 6bd7918fd4e..6f10dbaa84c 100644 --- a/homeassistant/components/http/www_static/polymer/home-assistant-api.html +++ b/homeassistant/components/http/www_static/polymer/home-assistant-api.html @@ -206,11 +206,18 @@ }, turn_on: function(entity_id) { - this.call_service("homeassistant", "turn_on", {entity_id: entity_id}); + // we call the turn_on method on the domain of the entity_id + // because the call to homeassistant.turn_on does not wait + // till the call is done. + var parts = entity_id.split("."); + + this.call_service(parts[0], "turn_on", {entity_id: entity_id}); }, turn_off: function(entity_id) { - this.call_service("homeassistant", "turn_off", {entity_id: entity_id}); + var parts = entity_id.split("."); + + this.call_service(parts[0], "turn_off", {entity_id: entity_id}); }, set_state: function(entity_id, state, attributes) { @@ -243,23 +250,7 @@ // if we call a service on an entity_id, update the state if(parameters && parameters.entity_id) { - var update_func; - - // if entity_id is a string, update 1 state, else all. - if(typeof(parameters.entity_id === "string")) { - // if it is a group, fetch all - if(parameters.entity_id.slice(0,6) == "group.") { - update_func = this.fetchStates; - } else { - update_func = function() { - this.fetchState(parameters.entity_id); - }; - } - } else { - update_func = this.fetchStates; - } - - setTimeout(update_func.bind(this), 1000); + this.fetchStates(); } }; diff --git a/homeassistant/const.py b/homeassistant/const.py index 0dc4e7881e5..b8183edc2d0 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -20,7 +20,8 @@ EVENT_HOMEASSISTANT_START = "homeassistant_start" EVENT_HOMEASSISTANT_STOP = "homeassistant_stop" EVENT_STATE_CHANGED = "state_changed" EVENT_TIME_CHANGED = "time_changed" -EVENT_CALL_SERVICE = "services.call" +EVENT_CALL_SERVICE = "call_service" +EVENT_SERVICE_EXECUTED = "service_executed" # #### STATES #### STATE_ON = 'on' @@ -28,7 +29,7 @@ STATE_OFF = 'off' STATE_HOME = 'home' STATE_NOT_HOME = 'not_home' -# #### STATE ATTRIBUTES #### +# #### STATE AND EVENT ATTRIBUTES #### # Contains current time for a TIME_CHANGED event ATTR_NOW = "now" @@ -36,6 +37,10 @@ ATTR_NOW = "now" ATTR_DOMAIN = "domain" ATTR_SERVICE = "service" +# Data for a SERVICE_EXECUTED event +ATTR_SERVICE_CALL_ID = "service_call_id" +ATTR_RESULT = "result" + # Contains one string or a list of strings, each being an entity id ATTR_ENTITY_ID = 'entity_id'