Calling a service can now block till execution is done

This commit is contained in:
Paulus Schoutsen 2014-12-13 22:40:00 -08:00
parent f8223053bd
commit 78d5625ace
6 changed files with 85 additions and 29 deletions

View File

@ -18,7 +18,8 @@ import functools as ft
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, 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 import homeassistant.util as util
DOMAIN = "homeassistant" DOMAIN = "homeassistant"
@ -26,6 +27,9 @@ DOMAIN = "homeassistant"
# How often time_changed event should fire # How often time_changed event should fire
TIMER_INTERVAL = 10 # seconds TIMER_INTERVAL = 10 # seconds
# How long we wait for the result of a service call
SERVICE_CALL_LIMIT = 10 # seconds
# Number of worker threads # Number of worker threads
POOL_NUM_THREAD = 4 POOL_NUM_THREAD = 4
@ -227,6 +231,7 @@ class JobPriority(util.OrderedEnum):
""" Provides priorities for bus events. """ """ Provides priorities for bus events. """
# pylint: disable=no-init,too-few-public-methods # pylint: disable=no-init,too-few-public-methods
EVENT_CALLBACK = 0
EVENT_SERVICE = 1 EVENT_SERVICE = 1
EVENT_STATE = 2 EVENT_STATE = 2
EVENT_TIME = 3 EVENT_TIME = 3
@ -241,6 +246,8 @@ class JobPriority(util.OrderedEnum):
return JobPriority.EVENT_STATE return JobPriority.EVENT_STATE
elif event_type == EVENT_CALL_SERVICE: elif event_type == EVENT_CALL_SERVICE:
return JobPriority.EVENT_SERVICE return JobPriority.EVENT_SERVICE
elif event_type == EVENT_SERVICE_EXECUTED:
return JobPriority.EVENT_CALLBACK
else: else:
return JobPriority.EVENT_DEFAULT return JobPriority.EVENT_DEFAULT
@ -594,6 +601,7 @@ class ServiceRegistry(object):
self._lock = threading.Lock() self._lock = threading.Lock()
self._pool = pool or create_worker_pool() self._pool = pool or create_worker_pool()
self._bus = bus self._bus = bus
self._cur_id = 0
bus.listen(EVENT_CALL_SERVICE, self._event_to_service_call) bus.listen(EVENT_CALL_SERVICE, self._event_to_service_call)
@property @property
@ -615,9 +623,14 @@ class ServiceRegistry(object):
else: else:
self._services[domain] = {service: service_func} 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 method will fire an event to call the service.
This event will be picked up by this ServiceRegistry and any 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 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. the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data.
""" """
call_id = self._generate_unique_id()
event_data = service_data or {} event_data = service_data or {}
event_data[ATTR_DOMAIN] = domain event_data[ATTR_DOMAIN] = domain
event_data[ATTR_SERVICE] = service 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) 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): def _event_to_service_call(self, event):
""" Calls a service from an event. """ """ Calls a service from an event. """
service_data = dict(event.data) service_data = dict(event.data)
@ -642,9 +684,27 @@ class ServiceRegistry(object):
if domain in self._services and service in self._services[domain]: if domain in self._services and service in self._services[domain]:
service_call = ServiceCall(domain, service, service_data) 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._pool.add_job(JobPriority.EVENT_SERVICE,
(self._services[domain][service], (self._execute_service,
service_call)) (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): class Timer(threading.Thread):

View File

@ -484,7 +484,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
domain = path_match.group('domain') domain = path_match.group('domain')
service = path_match.group('service') 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)) self._json_message("Service {}/{} called.".format(domain, service))

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "b32f2de4e3336946fe68cd1e5cd0fe6f" VERSION = "5c3b2dd8a63197e380e798da8b057b0a"

File diff suppressed because one or more lines are too long

View File

@ -206,11 +206,18 @@
}, },
turn_on: function(entity_id) { 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) { 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) { set_state: function(entity_id, state, attributes) {
@ -243,23 +250,7 @@
// if we call a service on an entity_id, update the state // if we call a service on an entity_id, update the state
if(parameters && parameters.entity_id) { if(parameters && parameters.entity_id) {
var update_func; this.fetchStates();
// 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);
} }
}; };

View File

@ -20,7 +20,8 @@ EVENT_HOMEASSISTANT_START = "homeassistant_start"
EVENT_HOMEASSISTANT_STOP = "homeassistant_stop" EVENT_HOMEASSISTANT_STOP = "homeassistant_stop"
EVENT_STATE_CHANGED = "state_changed" EVENT_STATE_CHANGED = "state_changed"
EVENT_TIME_CHANGED = "time_changed" EVENT_TIME_CHANGED = "time_changed"
EVENT_CALL_SERVICE = "services.call" EVENT_CALL_SERVICE = "call_service"
EVENT_SERVICE_EXECUTED = "service_executed"
# #### STATES #### # #### STATES ####
STATE_ON = 'on' STATE_ON = 'on'
@ -28,7 +29,7 @@ STATE_OFF = 'off'
STATE_HOME = 'home' STATE_HOME = 'home'
STATE_NOT_HOME = 'not_home' STATE_NOT_HOME = 'not_home'
# #### STATE ATTRIBUTES #### # #### STATE AND EVENT ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event # Contains current time for a TIME_CHANGED event
ATTR_NOW = "now" ATTR_NOW = "now"
@ -36,6 +37,10 @@ ATTR_NOW = "now"
ATTR_DOMAIN = "domain" ATTR_DOMAIN = "domain"
ATTR_SERVICE = "service" 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 # Contains one string or a list of strings, each being an entity id
ATTR_ENTITY_ID = 'entity_id' ATTR_ENTITY_ID = 'entity_id'