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 (
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._execute_service,
(self._services[domain][service],
service_call))
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):

View File

@ -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))

View File

@ -1,2 +1,2 @@
""" 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) {
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();
}
};

View File

@ -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'