"
"
"
"
"
"
"
"Events
"
"
"
- "Event Type | Listeners |
"))
+ "Event | Listeners |
"))
- for event_type, count in sorted(
- self.server.eventbus.listeners.items()):
- write("{} | {} |
".format(event_type, count))
+ for event, listener_count in sorted(
+ self.server.bus.event_listeners.items()):
+ write("{} | {} |
".format(
+ event, listener_count))
write(("
"
@@ -483,7 +558,7 @@ class RequestHandler(BaseHTTPRequestHandler):
# Happens if key 'event_data' does not exist
event_data = None
- self.server.eventbus.fire(event_type, event_data)
+ self.server.bus.fire_event(event_type, event_data)
self._message("Event {} fired.".format(event_type))
@@ -496,6 +571,41 @@ class RequestHandler(BaseHTTPRequestHandler):
self._message(
"Invalid JSON for event_data", HTTP_UNPROCESSABLE_ENTITY)
+ def _handle_call_service(self, path_match, data):
+ """ Handles calling a service.
+
+ This handles the following paths:
+ /call_service
+ /api/services/
/
+ """
+ try:
+ try:
+ domain = path_match.group('domain')
+ service = path_match.group('service')
+ except IndexError:
+ # If group domain or service does not exist in path_match
+ domain = data['domain'][0]
+ service = data['service'][0]
+
+ try:
+ service_data = json.loads(data['service_data'][0])
+ except KeyError:
+ # Happens if key 'service_data' does not exist
+ service_data = None
+
+ self.server.bus.call_service(domain, service, service_data)
+
+ self._message("Service {}/{} called.".format(domain, service))
+
+ except KeyError:
+ # Occurs if domain or service does not exist in data
+ self._message("No domain or service received.", HTTP_BAD_REQUEST)
+
+ except ValueError:
+ # Occurs during error parsing json
+ self._message(
+ "Invalid JSON for service_data", HTTP_UNPROCESSABLE_ENTITY)
+
# pylint: disable=unused-argument
def _handle_get_api_states(self, path_match, data):
""" Returns the categories which state is being tracked. """
@@ -519,7 +629,11 @@ class RequestHandler(BaseHTTPRequestHandler):
def _handle_get_api_events(self, path_match, data):
""" Handles getting overview of event listeners. """
- self._write_json({'listeners': self.server.eventbus.listeners})
+ self._write_json({'event_listeners': self.server.bus.event_listeners})
+
+ def _handle_get_api_services(self, path_match, data):
+ """ Handles getting overview of services. """
+ self._write_json({'services': self.server.bus.services})
def _handle_get_static(self, path_match, data):
""" Returns a static file. """
diff --git a/homeassistant/observers.py b/homeassistant/observers.py
index 6f68cff1a2c..ecc2ee03619 100644
--- a/homeassistant/observers.py
+++ b/homeassistant/observers.py
@@ -19,7 +19,8 @@ import requests
import homeassistant as ha
-EVENT_DEVICE_TRACKER_RELOAD = "device_tracker.reload_devices_csv"
+DOMAIN_DEVICE_TRACKER = "device_tracker"
+SERVICE_DEVICE_TRACKER_RELOAD = "reload_devices_csv"
STATE_CATEGORY_SUN = "weather.sun"
STATE_ATTRIBUTE_NEXT_SUN_RISING = "next_rising"
@@ -44,7 +45,7 @@ TOMATO_MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
KNOWN_DEVICES_FILE = "known_devices.csv"
-def track_sun(eventbus, statemachine, latitude, longitude):
+def track_sun(bus, statemachine, latitude, longitude):
""" Tracks the state of the sun. """
logger = logging.getLogger(__name__)
@@ -86,7 +87,7 @@ def track_sun(eventbus, statemachine, latitude, longitude):
statemachine.set_state(STATE_CATEGORY_SUN, new_state, state_attributes)
# +10 seconds to be sure that the change has occured
- ha.track_time_change(eventbus, update_sun_state,
+ ha.track_time_change(bus, update_sun_state,
point_in_time=next_change + timedelta(seconds=10))
update_sun_state(None)
@@ -97,9 +98,9 @@ def track_sun(eventbus, statemachine, latitude, longitude):
class DeviceTracker(object):
""" Class that tracks which devices are home and which are not. """
- def __init__(self, eventbus, statemachine, device_scanner):
+ def __init__(self, bus, statemachine, device_scanner):
self.statemachine = statemachine
- self.eventbus = eventbus
+ self.bus = bus
self.device_scanner = device_scanner
self.logger = logging.getLogger(__name__)
@@ -113,13 +114,14 @@ class DeviceTracker(object):
self._read_known_devices_file()
- ha.track_time_change(eventbus,
+ ha.track_time_change(bus,
lambda time:
self.update_devices(
device_scanner.scan_devices()))
- eventbus.listen(EVENT_DEVICE_TRACKER_RELOAD,
- lambda event: self._read_known_devices_file())
+ bus.register_service(DOMAIN_DEVICE_TRACKER,
+ SERVICE_DEVICE_TRACKER_RELOAD,
+ lambda service: self._read_known_devices_file())
@property
def device_state_categories(self):
diff --git a/homeassistant/remote.py b/homeassistant/remote.py
index 238633cddfc..b905509fe9a 100644
--- a/homeassistant/remote.py
+++ b/homeassistant/remote.py
@@ -36,28 +36,58 @@ def _setup_call_api(host, port, api_password):
url = urlparse.urljoin(base_url, path)
- if method == METHOD_GET:
- return requests.get(url, params=data)
- else:
- return requests.request(method, url, data=data)
+ try:
+ if method == METHOD_GET:
+ return requests.get(url, params=data)
+ else:
+ return requests.request(method, url, data=data)
+
+ except requests.exceptions.ConnectionError:
+ logging.getLogger(__name__).exception("Error connecting to server")
+ raise ha.HomeAssistantException("Error connecting to server")
return _call_api
-class EventBus(ha.EventBus):
- """ Drop-in replacement for a normal eventbus that will forward events to
- a remote eventbus.
+class Bus(ha.Bus):
+ """ Drop-in replacement for a normal bus that will forward interaction to
+ a remote bus.
"""
def __init__(self, host, api_password, port=None):
- ha.EventBus.__init__(self)
-
- self._call_api = _setup_call_api(host, port, api_password)
+ ha.Bus.__init__(self)
self.logger = logging.getLogger(__name__)
+ self._call_api = _setup_call_api(host, port, api_password)
+
@property
- def listeners(self):
+ def services(self):
+ """ List the available services. """
+ try:
+ req = self._call_api(METHOD_GET, hah.URL_API_SERVICES)
+
+ if req.status_code == 200:
+ data = req.json()
+
+ return data['services']
+
+ else:
+ raise ha.HomeAssistantException(
+ "Got unexpected result (3): {}.".format(req.text))
+
+ except ValueError: # If req.json() can't parse the json
+ self.logger.exception("Bus:Got unexpected result")
+ raise ha.HomeAssistantException(
+ "Got unexpected result: {}".format(req.text))
+
+ except KeyError: # If not all expected keys are in the returned JSON
+ self.logger.exception("Bus:Got unexpected result (2)")
+ raise ha.HomeAssistantException(
+ "Got unexpected result (2): {}".format(req.text))
+
+ @property
+ def event_listeners(self):
""" List of events that is being listened for. """
try:
req = self._call_api(METHOD_GET, hah.URL_API_EVENTS)
@@ -65,54 +95,72 @@ class EventBus(ha.EventBus):
if req.status_code == 200:
data = req.json()
- return data['listeners']
+ return data['event_listeners']
else:
raise ha.HomeAssistantException(
"Got unexpected result (3): {}.".format(req.text))
- except requests.exceptions.ConnectionError:
- self.logger.exception("EventBus:Error connecting to server")
- raise ha.HomeAssistantException("Error connecting to server")
-
except ValueError: # If req.json() can't parse the json
- self.logger.exception("EventBus:Got unexpected result")
+ self.logger.exception("Bus:Got unexpected result")
raise ha.HomeAssistantException(
"Got unexpected result: {}".format(req.text))
except KeyError: # If not all expected keys are in the returned JSON
- self.logger.exception("EventBus:Got unexpected result (2)")
+ self.logger.exception("Bus:Got unexpected result (2)")
raise ha.HomeAssistantException(
"Got unexpected result (2): {}".format(req.text))
- def fire(self, event_type, event_data=None):
- """ Fire an event. """
+ def call_service(self, domain, service, service_data=None):
+ """ Calls a service. """
- data = {'event_data': json.dumps(event_data)} if event_data else None
+ if service_data:
+ data = {'service_data': json.dumps(service_data)}
+ else:
+ data = None
- try:
- req = self._call_api(METHOD_POST,
- hah.URL_API_EVENTS_EVENT.format(event_type),
- data)
+ req = self._call_api(METHOD_POST,
+ hah.URL_API_SERVICES_SERVICE.format(
+ domain, service),
+ data)
- if req.status_code != 200:
- error = "Error firing event: {} - {}".format(
- req.status_code, req.text)
+ if req.status_code != 200:
+ error = "Error calling service: {} - {}".format(
+ req.status_code, req.text)
- self.logger.error("EventBus:{}".format(error))
- raise ha.HomeAssistantException(error)
+ self.logger.error("Bus:{}".format(error))
+ raise ha.HomeAssistantException(error)
- except requests.exceptions.ConnectionError:
- self.logger.exception("EventBus:Error connecting to server")
-
- def listen(self, event_type, listener):
- """ Not implemented for remote eventbus.
+ def register_service(self, domain, service, service_callback):
+ """ Not implemented for remote bus.
Will throw NotImplementedError. """
raise NotImplementedError
- def remove_listener(self, event_type, listener):
- """ Not implemented for remote eventbus.
+ def fire_event(self, event_type, event_data=None):
+ """ Fire an event. """
+
+ data = {'event_data': json.dumps(event_data)} if event_data else None
+
+ req = self._call_api(METHOD_POST,
+ hah.URL_API_EVENTS_EVENT.format(event_type),
+ data)
+
+ if req.status_code != 200:
+ error = "Error firing event: {} - {}".format(
+ req.status_code, req.text)
+
+ self.logger.error("Bus:{}".format(error))
+ raise ha.HomeAssistantException(error)
+
+ def listen_event(self, event_type, listener):
+ """ Not implemented for remote bus.
+
+ Will throw NotImplementedError. """
+ raise NotImplementedError
+
+ def remove_event_listener(self, event_type, listener):
+ """ Not implemented for remote bus.
Will throw NotImplementedError. """
diff --git a/homeassistant/test.py b/homeassistant/test.py
index f6c937d1156..ec6030e551f 100644
--- a/homeassistant/test.py
+++ b/homeassistant/test.py
@@ -34,23 +34,23 @@ def ensure_homeassistant_started():
""" Ensures home assistant is started. """
if not HAHelper.core:
- core = {'eventbus': ha.EventBus()}
- core['statemachine'] = ha.StateMachine(core['eventbus'])
+ core = {'bus': ha.Bus()}
+ core['statemachine'] = ha.StateMachine(core['bus'])
- core['eventbus'].listen('test_event', len)
+ core['bus'].listen_event('test_event', len)
core['statemachine'].set_state('test', 'a_state')
- hah.HTTPInterface(core['eventbus'], core['statemachine'],
+ hah.HTTPInterface(core['bus'], core['statemachine'],
API_PASSWORD)
- core['eventbus'].fire(ha.EVENT_HOMEASSISTANT_START)
+ core['bus'].fire_event(ha.EVENT_HOMEASSISTANT_START)
# Give objects time to startup
time.sleep(1)
HAHelper.core = core
- return HAHelper.core['eventbus'], HAHelper.core['statemachine']
+ return HAHelper.core['bus'], HAHelper.core['statemachine']
# pylint: disable=too-many-public-methods
@@ -60,7 +60,7 @@ class TestHTTPInterface(unittest.TestCase):
@classmethod
def setUpClass(cls): # pylint: disable=invalid-name
""" things to be run when tests are started. """
- cls.eventbus, cls.statemachine = ensure_homeassistant_started()
+ cls.bus, cls.statemachine = ensure_homeassistant_started()
def test_debug_interface(self):
""" Test if we can login by comparing not logged in screen to
@@ -109,7 +109,7 @@ class TestHTTPInterface(unittest.TestCase):
if "test" in event.data:
test_value.append(1)
- self.eventbus.listen_once("test_event_with_data", listener)
+ self.bus.listen_once_event("test_event_with_data", listener)
requests.post(
_url(hah.URL_FIRE_EVENT),
@@ -195,7 +195,7 @@ class TestHTTPInterface(unittest.TestCase):
""" Helper method that will verify our event got called. """
test_value.append(1)
- self.eventbus.listen_once("test.event_no_data", listener)
+ self.bus.listen_once_event("test.event_no_data", listener)
requests.post(
_url(hah.URL_API_EVENTS_EVENT.format("test.event_no_data")),
@@ -217,7 +217,7 @@ class TestHTTPInterface(unittest.TestCase):
if "test" in event.data:
test_value.append(1)
- self.eventbus.listen_once("test_event_with_data", listener)
+ self.bus.listen_once_event("test_event_with_data", listener)
requests.post(
_url(hah.URL_API_EVENTS_EVENT.format("test_event_with_data")),
@@ -238,7 +238,7 @@ class TestHTTPInterface(unittest.TestCase):
""" Helper method that will verify our event got called. """
test_value.append(1)
- self.eventbus.listen_once("test_event_with_bad_data", listener)
+ self.bus.listen_once_event("test_event_with_bad_data", listener)
req = requests.post(
_url(hah.URL_API_EVENTS_EVENT.format("test_event")),
@@ -258,7 +258,59 @@ class TestHTTPInterface(unittest.TestCase):
data = req.json()
- self.assertEqual(data['listeners'], self.eventbus.listeners)
+ self.assertEqual(data['event_listeners'], self.bus.event_listeners)
+
+ def test_api_get_services(self):
+ """ Test if we can get a dict describing current services. """
+ req = requests.get(_url(hah.URL_API_SERVICES),
+ params={"api_password": API_PASSWORD})
+
+ data = req.json()
+
+ self.assertEqual(data['services'], self.bus.services)
+
+ def test_api_call_service_no_data(self):
+ """ Test if the API allows us to call a service. """
+ test_value = []
+
+ def listener(service_call): # pylint: disable=unused-argument
+ """ Helper method that will verify that our service got called. """
+ test_value.append(1)
+
+ self.bus.register_service("test_domain", "test_service", listener)
+
+ requests.post(
+ _url(hah.URL_API_SERVICES_SERVICE.format(
+ "test_domain", "test_service")),
+ data={"api_password": API_PASSWORD})
+
+ # Allow the event to take place
+ time.sleep(1)
+
+ self.assertEqual(len(test_value), 1)
+
+ def test_api_call_service_with_data(self):
+ """ Test if the API allows us to call a service. """
+ test_value = []
+
+ def listener(service_call): # pylint: disable=unused-argument
+ """ Helper method that will verify that our service got called and
+ that test if our data came through. """
+ if "test" in service_call.data:
+ test_value.append(1)
+
+ self.bus.register_service("test_domain", "test_service", listener)
+
+ requests.post(
+ _url(hah.URL_API_SERVICES_SERVICE.format(
+ "test_domain", "test_service")),
+ data={"service_data": '{"test": 1}',
+ "api_password": API_PASSWORD})
+
+ # Allow the event to take place
+ time.sleep(1)
+
+ self.assertEqual(len(test_value), 1)
class TestRemote(unittest.TestCase):
@@ -267,10 +319,10 @@ class TestRemote(unittest.TestCase):
@classmethod
def setUpClass(cls): # pylint: disable=invalid-name
""" things to be run when tests are started. """
- cls.eventbus, cls.statemachine = ensure_homeassistant_started()
+ cls.bus, cls.statemachine = ensure_homeassistant_started()
cls.remote_sm = remote.StateMachine("127.0.0.1", API_PASSWORD)
- cls.remote_eb = remote.EventBus("127.0.0.1", API_PASSWORD)
+ cls.remote_eb = remote.Bus("127.0.0.1", API_PASSWORD)
cls.sm_with_remote_eb = ha.StateMachine(cls.remote_eb)
cls.sm_with_remote_eb.set_state("test", "a_state")
@@ -307,20 +359,21 @@ class TestRemote(unittest.TestCase):
def test_remote_eb_listening_for_same(self):
""" Test if remote EB correctly reports listener overview. """
- self.assertEqual(self.eventbus.listeners, self.remote_eb.listeners)
+ self.assertEqual(self.bus.event_listeners,
+ self.remote_eb.event_listeners)
# pylint: disable=invalid-name
def test_remote_eb_fire_event_with_no_data(self):
- """ Test if the remote eventbus allows us to fire an event. """
+ """ Test if the remote bus allows us to fire an event. """
test_value = []
def listener(event): # pylint: disable=unused-argument
""" Helper method that will verify our event got called. """
test_value.append(1)
- self.eventbus.listen_once("test_event_no_data", listener)
+ self.bus.listen_once_event("test_event_no_data", listener)
- self.remote_eb.fire("test_event_no_data")
+ self.remote_eb.fire_event("test_event_no_data")
# Allow the event to take place
time.sleep(1)
@@ -329,7 +382,7 @@ class TestRemote(unittest.TestCase):
# pylint: disable=invalid-name
def test_remote_eb_fire_event_with_data(self):
- """ Test if the remote eventbus allows us to fire an event. """
+ """ Test if the remote bus allows us to fire an event. """
test_value = []
def listener(event): # pylint: disable=unused-argument
@@ -337,9 +390,46 @@ class TestRemote(unittest.TestCase):
if event.data["test"] == 1:
test_value.append(1)
- self.eventbus.listen_once("test_event_with_data", listener)
+ self.bus.listen_once_event("test_event_with_data", listener)
- self.remote_eb.fire("test_event_with_data", {"test": 1})
+ self.remote_eb.fire_event("test_event_with_data", {"test": 1})
+
+ # Allow the event to take place
+ time.sleep(1)
+
+ self.assertEqual(len(test_value), 1)
+
+ # pylint: disable=invalid-name
+ def test_remote_eb_call_service_with_no_data(self):
+ """ Test if the remote bus allows us to fire a service. """
+ test_value = []
+
+ def listener(service_call): # pylint: disable=unused-argument
+ """ Helper method that will verify our service got called. """
+ test_value.append(1)
+
+ self.bus.register_service("test_domain", "test_service", listener)
+
+ self.remote_eb.call_service("test_domain", "test_service")
+
+ # Allow the service call to take place
+ time.sleep(1)
+
+ self.assertEqual(len(test_value), 1)
+
+ # pylint: disable=invalid-name
+ def test_remote_eb_call_service_with_data(self):
+ """ Test if the remote bus allows us to fire an event. """
+ test_value = []
+
+ def listener(service_call): # pylint: disable=unused-argument
+ """ Helper method that will verify our service got called. """
+ if service_call.data["test"] == 1:
+ test_value.append(1)
+
+ self.bus.register_service("test_domain", "test_service", listener)
+
+ self.remote_eb.call_service("test_domain", "test_service", {"test": 1})
# Allow the event to take place
time.sleep(1)
@@ -348,14 +438,14 @@ class TestRemote(unittest.TestCase):
def test_local_sm_with_remote_eb(self):
""" Test if we get the event if we change a state on a
- StateMachine connected to a remote eventbus. """
+ StateMachine connected to a remote bus. """
test_value = []
def listener(event): # pylint: disable=unused-argument
""" Helper method that will verify our event got called. """
test_value.append(1)
- self.eventbus.listen_once(ha.EVENT_STATE_CHANGED, listener)
+ self.bus.listen_once_event(ha.EVENT_STATE_CHANGED, listener)
self.sm_with_remote_eb.set_state("test", "local sm with remote eb")