mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
Calling a service can now block till execution is done
This commit is contained in:
parent
f8223053bd
commit
78d5625ace
@ -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):
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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'
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user