Cleaned up the core.

This commit is contained in:
Paulus Schoutsen 2013-10-08 18:50:30 -07:00
parent f1042cd136
commit 71bd03ed8c
3 changed files with 37 additions and 59 deletions

View File

@ -13,6 +13,8 @@ from collections import defaultdict, namedtuple
from itertools import chain from itertools import chain
from datetime import datetime from datetime import datetime
logging.basicConfig(level=logging.INFO)
ALL_EVENTS = '*' ALL_EVENTS = '*'
EVENT_START = "start" EVENT_START = "start"
EVENT_SHUTDOWN = "shutdown" EVENT_SHUTDOWN = "shutdown"
@ -32,7 +34,7 @@ def start_home_assistant(eventbus):
""" Start home assistant. """ """ Start home assistant. """
Timer(eventbus) Timer(eventbus)
eventbus.fire(Event(EVENT_START)) eventbus.fire(EVENT_START)
while True: while True:
try: try:
@ -40,7 +42,7 @@ def start_home_assistant(eventbus):
except KeyboardInterrupt: except KeyboardInterrupt:
print "" print ""
eventbus.fire(Event(EVENT_SHUTDOWN)) eventbus.fire(EVENT_SHUTDOWN)
break break
@ -62,8 +64,6 @@ def track_state_change(eventbus, category, from_state, to_state, action):
def listener(event): def listener(event):
""" State change listener that listens for specific state changes. """ """ State change listener that listens for specific state changes. """
assert isinstance(event, Event), "event needs to be of Event type"
if category == event.data['category'] and \ if category == event.data['category'] and \
matcher(event.data['old_state'].state, from_state) and \ matcher(event.data['old_state'].state, from_state) and \
matcher(event.data['new_state'].state, to_state): matcher(event.data['new_state'].state, to_state):
@ -86,8 +86,6 @@ def track_time_change(eventbus, action,
def listener(event): def listener(event):
""" Listens for matching time_changed events. """ """ Listens for matching time_changed events. """
assert isinstance(event, Event), "event needs to be of Event type"
if (point_in_time and event.data['now'] > point_in_time) or \ if (point_in_time and event.data['now'] > point_in_time) or \
(not point_in_time and \ (not point_in_time and \
matcher(event.data['now'].year, year) and \ matcher(event.data['now'].year, year) and \
@ -99,80 +97,60 @@ def track_time_change(eventbus, action,
# point_in_time are exact points in time # point_in_time are exact points in time
# so we always remove it after fire # so we always remove it after fire
event.remove_listener = listen_once or point_in_time is not None if listen_once or point_in_time:
event.eventbus.remove_listener(EVENT_TIME_CHANGED, listener)
action(event.data['now']) action(event.data['now'])
eventbus.listen(EVENT_TIME_CHANGED, listener) eventbus.listen(EVENT_TIME_CHANGED, listener)
Event = namedtuple("Event", ["eventbus", "event_type", "data"])
class EventBus(object): class EventBus(object):
""" Class that allows code to listen for- and fire events. """ """ Class that allows code to listen for- and fire events. """
def __init__(self): def __init__(self):
self.listeners = defaultdict(list) self.listeners = defaultdict(list)
self.lock = threading.RLock()
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
def fire(self, event): def fire(self, event_type, event_data=None):
""" Fire an event. """ """ Fire an event. """
assert isinstance(event, Event), \
"event needs to be an instance of Event"
def run(): if not event_data:
""" We dont want the eventbus to be blocking - run in a thread. """ event_data = {}
self.lock.acquire()
self.logger.info("EventBus:Event {}: {}".format( self.logger.info("EventBus:Event {}: {}".format(
event.event_type, event.data)) event_type, event_data))
for callback in chain(self.listeners[ALL_EVENTS], def run():
""" Fire listeners for event. """
event = Event(self, event_type, event_data)
for listener in chain(self.listeners[ALL_EVENTS],
self.listeners[event.event_type]): self.listeners[event.event_type]):
try: try:
callback(event) listener(event)
except Exception: #pylint: disable=broad-except except Exception: #pylint: disable=broad-except
self.logger.exception("EventBus:Exception in listener") self.logger.exception("EventBus:Exception in listener")
if event.remove_listener: # We dont want the eventbus to be blocking - run in a thread.
if callback in self.listeners[ALL_EVENTS]:
self.listeners[ALL_EVENTS].remove(callback)
if callback in self.listeners[event.event_type]:
self.listeners[event.event_type].remove(callback)
event.remove_listener = False
if event.stop_propegating:
break
self.lock.release()
threading.Thread(target=run).start() threading.Thread(target=run).start()
def listen(self, event_type, callback): def listen(self, event_type, listener):
""" Listen for all events or events of a specific type. """ Listen for all events or events of a specific type.
To listen to all events specify the constant ``ALL_EVENTS`` To listen to all events specify the constant ``ALL_EVENTS``
as event_type. as event_type.
""" """
self.lock.acquire() self.listeners[event_type].append(listener)
self.listeners[event_type].append(callback) def remove_listener(self, event_type, listener):
""" Removes a listener of a specific event_type. """
self.lock.release() try:
self.listeners[event_type].remove(listener)
# pylint: disable=too-few-public-methods except ValueError:
class Event(object): pass
""" An event to be sent over the eventbus. """
def __init__(self, event_type, data=None):
self.event_type = event_type
self.data = {} if data is None else data
self.stop_propegating = False
self.remove_listener = False
def __str__(self):
return str([self.event_type, self.data])
class StateMachine(object): class StateMachine(object):
""" Helper class that tracks the state of different objects. """ """ Helper class that tracks the state of different objects. """
@ -180,7 +158,7 @@ class StateMachine(object):
def __init__(self, eventbus): def __init__(self, eventbus):
self.states = dict() self.states = dict()
self.eventbus = eventbus self.eventbus = eventbus
self.lock = threading.RLock() self.lock = threading.Lock()
def set_state(self, category, new_state): def set_state(self, category, new_state):
""" Set the state of a category, add category is it does not exist. """ """ Set the state of a category, add category is it does not exist. """
@ -198,10 +176,10 @@ class StateMachine(object):
if old_state.state != new_state: if old_state.state != new_state:
self.states[category] = State(new_state, datetime.now()) self.states[category] = State(new_state, datetime.now())
self.eventbus.fire(Event(EVENT_STATE_CHANGED, self.eventbus.fire(EVENT_STATE_CHANGED,
{'category':category, {'category':category,
'old_state':old_state, 'old_state':old_state,
'new_state':self.states[category]})) 'new_state':self.states[category]})
self.lock.release() self.lock.release()
@ -265,7 +243,7 @@ class Timer(threading.Thread):
if self._stop.isSet(): if self._stop.isSet():
break break
self.eventbus.fire(Event(EVENT_TIME_CHANGED, {'now':now})) self.eventbus.fire(EVENT_TIME_CHANGED, {'now':now})
class HomeAssistantException(Exception): class HomeAssistantException(Exception):
""" General Home Assistant exception occured. """ """ General Home Assistant exception occured. """

View File

@ -33,7 +33,7 @@ from urlparse import urlparse, parse_qs
import requests import requests
from . import EVENT_START, EVENT_SHUTDOWN, Event from . import EVENT_START, EVENT_SHUTDOWN
SERVER_PORT = 8123 SERVER_PORT = 8123
@ -229,7 +229,7 @@ class RequestHandler(BaseHTTPRequestHandler):
else: else:
event_data = json.loads(post_data['event_data'][0]) event_data = json.loads(post_data['event_data'][0])
self.server.eventbus.fire(Event(event_name, event_data)) self.server.eventbus.fire(event_name, event_data)
self._message(use_json, "Event {} fired.". self._message(use_json, "Event {} fired.".
format(event_name)) format(event_name))

View File

@ -11,7 +11,7 @@ import time
import requests import requests
from . import EventBus, StateMachine, Event, EVENT_START, EVENT_SHUTDOWN from . import EventBus, StateMachine, EVENT_START, EVENT_SHUTDOWN
from .httpinterface import HTTPInterface, SERVER_PORT from .httpinterface import HTTPInterface, SERVER_PORT
@ -34,7 +34,7 @@ class TestHTTPInterface(unittest.TestCase):
self.statemachine.set_state("test", "INIT_STATE") self.statemachine.set_state("test", "INIT_STATE")
self.eventbus.fire(Event(EVENT_START)) self.eventbus.fire(EVENT_START)
# Give objects time to startup # Give objects time to startup
time.sleep(1) time.sleep(1)
@ -48,7 +48,7 @@ class TestHTTPInterface(unittest.TestCase):
@classmethod @classmethod
def tearDownClass(cls): # pylint: disable=invalid-name def tearDownClass(cls): # pylint: disable=invalid-name
""" things to be run when tests are done. """ """ things to be run when tests are done. """
cls.eventbus.fire(Event(EVENT_SHUTDOWN)) cls.eventbus.fire(EVENT_SHUTDOWN)
time.sleep(1) time.sleep(1)