mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +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 (
|
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):
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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
@ -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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user