diff --git a/app/DeviceTracker.py b/app/DeviceTracker.py index 05f96a5eb43..8ad5146d7cc 100644 --- a/app/DeviceTracker.py +++ b/app/DeviceTracker.py @@ -16,70 +16,64 @@ STATE_CATEGORY_ALL_DEVICES = 'device.alldevices' STATE_CATEGORY_DEVICE_FORMAT = 'device.{}' -class DeviceTracker: +class DeviceTracker(object): - def __init__(self, eventbus, statemachine, device_scanner): - self.statemachine = statemachine - self.eventbus = eventbus - self.device_scanner = device_scanner + def __init__(self, eventbus, statemachine, device_scanner): + self.statemachine = statemachine + self.eventbus = eventbus - default_last_seen = datetime(1990, 1, 1) + default_last_seen = datetime(1990, 1, 1) - temp_devices_to_track = device_scanner.get_devices_to_track() + temp_devices_to_track = device_scanner.get_devices_to_track() - self.devices_to_track = { device: { 'name': temp_devices_to_track[device], - 'last_seen': default_last_seen, - 'category': STATE_CATEGORY_DEVICE_FORMAT.format(temp_devices_to_track[device]) } - for device in temp_devices_to_track } + self.devices_to_track = { device: { 'name': temp_devices_to_track[device], + 'last_seen': default_last_seen, + 'category': STATE_CATEGORY_DEVICE_FORMAT.format(temp_devices_to_track[device]) } + for device in temp_devices_to_track } - self.all_devices_state = STATE_DEVICE_DEFAULT + # Add categories to state machine + statemachine.add_category(STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_DEFAULT) - # Add categories to state machine - statemachine.add_category(STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_DEFAULT) + for device in self.devices_to_track: + self.statemachine.add_category(self.devices_to_track[device]['category'], STATE_DEVICE_DEFAULT) - for device in self.devices_to_track: - self.statemachine.add_category(self.devices_to_track[device]['category'], STATE_DEVICE_DEFAULT) - - track_time_change(eventbus, lambda time: self.update_devices(device_scanner.scan_devices())) + track_time_change(eventbus, lambda time: self.update_devices(device_scanner.scan_devices())) - def device_state_categories(self): - for device in self.devices_to_track: - yield self.devices_to_track[device]['category'] + def device_state_categories(self): + for device in self.devices_to_track: + yield self.devices_to_track[device]['category'] - def set_state(self, device, state): - if state == STATE_DEVICE_HOME: - self.devices_to_track[device]['last_seen'] = datetime.now() + def set_state(self, device, state): + if state == STATE_DEVICE_HOME: + self.devices_to_track[device]['last_seen'] = datetime.now() - self.statemachine.set_state(self.devices_to_track[device]['category'], state) + self.statemachine.set_state(self.devices_to_track[device]['category'], state) - def update_devices(self, found_devices): - # Keep track of devices that are home, all that are not will be marked not home - temp_tracking_devices = self.devices_to_track.keys() + def update_devices(self, found_devices): + # Keep track of devices that are home, all that are not will be marked not home + temp_tracking_devices = self.devices_to_track.keys() - for device in found_devices: - # Are we tracking this device? - if device in temp_tracking_devices: - temp_tracking_devices.remove(device) + for device in found_devices: + # Are we tracking this device? + if device in temp_tracking_devices: + temp_tracking_devices.remove(device) - self.set_state(device, STATE_DEVICE_HOME) + self.set_state(device, STATE_DEVICE_HOME) - # For all devices we did not find, set state to NH - # But only if they have been gone for longer then the error time span - # Because we do not want to have stuff happening when the device does - # not show up for 1 scan beacuse of reboot etc - for device in temp_tracking_devices: - if self.statemachine.get_state(self.devices_to_track[device]['category']).state == STATE_DEVICE_HOME and \ - datetime.now() - self.devices_to_track[device]['last_seen'] > TIME_SPAN_FOR_ERROR_IN_SCANNING: + # For all devices we did not find, set state to NH + # But only if they have been gone for longer then the error time span + # Because we do not want to have stuff happening when the device does + # not show up for 1 scan beacuse of reboot etc + for device in temp_tracking_devices: + if datetime.now() - self.devices_to_track[device]['last_seen'] > TIME_SPAN_FOR_ERROR_IN_SCANNING: + self.set_state(device, STATE_DEVICE_NOT_HOME) - self.set_state(device, STATE_DEVICE_NOT_HOME) + # Get the set of currently used statuses + states_of_devices = [self.statemachine.get_state(self.devices_to_track[device]['category']).state for device in self.devices_to_track] + all_devices_state = STATE_DEVICE_HOME if STATE_DEVICE_HOME in states_of_devices else STATE_DEVICE_NOT_HOME - # Get the set of currently used statuses - states_of_devices = [self.statemachine.get_state(self.devices_to_track[device]['category']).state for device in self.devices_to_track] - - self.all_devices_state = STATE_DEVICE_HOME if STATE_DEVICE_HOME in states_of_devices else STATE_DEVICE_NOT_HOME - - self.statemachine.set_state(STATE_CATEGORY_ALL_DEVICES, self.all_devices_state) + self.statemachine.set_state(STATE_CATEGORY_ALL_DEVICES, all_devices_state) diff --git a/app/EventBus.py b/app/EventBus.py index 6d30041cf1d..dd10c2f63c0 100644 --- a/app/EventBus.py +++ b/app/EventBus.py @@ -6,59 +6,58 @@ from threading import Thread, RLock ALL_EVENTS = '*' -class EventBus: - def __init__(self): - self.listeners = defaultdict(list) - self.lock = RLock() - self.logger =logging.getLogger(__name__) - - def fire(self, event): - assert isinstance(event, Event), "event needs to be an instance of Event" +class EventBus(object): + def __init__(self): + self.listeners = defaultdict(list) + self.lock = RLock() + self.logger = logging.getLogger(__name__) - # We dont want the eventbus to be blocking, - # We dont want the eventbus to crash when one of its listeners throws an Exception - # So run in a thread - def run(): - self.lock.acquire() + def fire(self, event): + assert isinstance(event, Event), "event needs to be an instance of Event" - self.logger.info("{} event received: {}".format(event.eventType, event.data)) + # We dont want the eventbus to be blocking, + # We dont want the eventbus to crash when one of its listeners throws an Exception + # So run in a thread + def run(): + self.lock.acquire() - for callback in chain(self.listeners[ALL_EVENTS], self.listeners[event.eventType]): - callback(event) + self.logger.info("{} event received: {}".format(event.event_type, event.data)) - if event.removeListener: - if callback in self.listeners[ALL_EVENTS]: - self.listeners[ALL_EVENTS].remove(callback) + for callback in chain(self.listeners[ALL_EVENTS], self.listeners[event.event_type]): + callback(event) - if callback in self.listeners[event.eventType]: - self.listeners[event.eventType].remove(callback) + if event.remove_listener: + if callback in self.listeners[ALL_EVENTS]: + self.listeners[ALL_EVENTS].remove(callback) - event.removeListener = False + if callback in self.listeners[event.event_type]: + self.listeners[event.event_type].remove(callback) - if event.stopPropegating: - break + event.remove_listener = False - self.lock.release() + if event.stop_propegating: + break - Thread(target=run).start() + self.lock.release() - def listen(self, event_type, callback): - self.lock.acquire() + Thread(target=run).start() - self.listeners[event_type].append(callback) + def listen(self, event_type, callback): + self.lock.acquire() - self.logger.info("New listener added for event {}. Total: {}".format(event_type, len(self.listeners[event_type]))) + self.listeners[event_type].append(callback) - self.lock.release() + self.logger.info("New listener added for event {}. Total: {}".format(event_type, len(self.listeners[event_type]))) + + self.lock.release() -class Event: - def __init__(self, eventType, data): - self.eventType = eventType - self.data = data - self.stopPropegating = False - self.removeListener = False - - def __str__(self): - return str([self.eventType, self.data]) +class Event(object): + def __init__(self, event_type, data): + self.event_type = event_type + self.data = data + self.stop_propegating = False + self.remove_listener = False + def __str__(self): + return str([self.event_type, self.data]) diff --git a/app/HomeAssistant.py b/app/HomeAssistant.py index 5a9f3ad0ed2..342a7e59bee 100644 --- a/app/HomeAssistant.py +++ b/app/HomeAssistant.py @@ -4,103 +4,97 @@ import time from app.StateMachine import StateMachine from app.EventBus import EventBus from app.DeviceTracker import DeviceTracker -from HttpInterface import HttpInterface +from app.HttpInterface import HttpInterface from app.observer.WeatherWatcher import WeatherWatcher -from app.observer.TomatoDeviceScanner import TomatoDeviceScanner from app.observer.Timer import Timer from app.actor.HueTrigger import HueTrigger -class HomeAssistant: +class HomeAssistant(object): - def __init__(self): - self.config = None - self.eventbus = None - self.statemachine = None + def __init__(self): + self.config = None + self.eventbus = None + self.statemachine = None - self.timer = None - self.weatherwatcher = None - self.devicetracker = None + self.timer = None + self.weatherwatcher = None + self.devicetracker = None - self.huetrigger = None - self.httpinterface = None + self.huetrigger = None + self.httpinterface = None - def get_config(self): - if self.config is None: - self.config = SafeConfigParser() - self.config.read("home-assistant.conf") + def get_config(self): + if self.config is None: + self.config = SafeConfigParser() + self.config.read("home-assistant.conf") - return self.config + return self.config - def get_event_bus(self): - if self.eventbus is None: - self.eventbus = EventBus() + def get_event_bus(self): + if self.eventbus is None: + self.eventbus = EventBus() - return self.eventbus + return self.eventbus - def get_state_machine(self): - if self.statemachine is None: - self.statemachine = StateMachine(self.get_event_bus()) + def get_state_machine(self): + if self.statemachine is None: + self.statemachine = StateMachine(self.get_event_bus()) - return self.statemachine + return self.statemachine - def setup_timer(self): - if self.timer is None: - self.timer = Timer(self.get_event_bus()) + def setup_timer(self): + if self.timer is None: + self.timer = Timer(self.get_event_bus()) - return self.timer + return self.timer - def setup_weather_watcher(self): - if self.weatherwatcher is None: - self.weatherwatcher = WeatherWatcher(self.get_config(), self.get_event_bus(), self.get_state_machine()) + def setup_weather_watcher(self): + if self.weatherwatcher is None: + self.weatherwatcher = WeatherWatcher(self.get_config(), self.get_event_bus(), self.get_state_machine()) - return self.weatherwatcher + return self.weatherwatcher - def setup_device_tracker(self, device_scanner): - if self.devicetracker is None: - self.devicetracker = DeviceTracker(self.get_event_bus(), self.get_state_machine(), device_scanner) + def setup_device_tracker(self, device_scanner): + if self.devicetracker is None: + self.devicetracker = DeviceTracker(self.get_event_bus(), self.get_state_machine(), device_scanner) - return self.devicetracker + return self.devicetracker - def setup_hue_trigger(self): - if self.huetrigger is None: - assert self.devicetracker is not None, "Cannot setup Hue Trigger without a device tracker being setup" + def setup_hue_trigger(self): + if self.huetrigger is None: + assert self.devicetracker is not None, "Cannot setup Hue Trigger without a device tracker being setup" - self.huetrigger = HueTrigger(self.get_config(), self.get_event_bus(), self.get_state_machine(), self.devicetracker, self.setup_weather_watcher()) + self.huetrigger = HueTrigger(self.get_config(), self.get_event_bus(), self.get_state_machine(), self.devicetracker, self.setup_weather_watcher()) - return self.huetrigger + return self.huetrigger - def setup_http_interface(self): - self.httpinterface = HttpInterface(self.get_event_bus(), self.get_state_machine()) - self.httpinterface.start() + def setup_http_interface(self): + self.httpinterface = HttpInterface(self.get_event_bus(), self.get_state_machine()) + self.httpinterface.start() - return self.httpinterface - - def start(self): - self.setup_timer().start() - - while True: - try: - time.sleep(1) - - except: - print "" - print "Interrupt received. Wrapping up and quiting.." - self.timer.stop() - - if self.httpinterface is not None: - self.httpinterface.stop() - - break + return self.httpinterface + def start(self): + self.setup_timer().start() + while True: + try: + time.sleep(1) + except KeyboardInterrupt: + print "" + print "Interrupt received. Wrapping up and quiting.." + self.timer.stop() + if self.httpinterface is not None: + self.httpinterface.stop() + break diff --git a/app/HttpInterface.py b/app/HttpInterface.py index 36bc34f269e..bbd31d4a27a 100644 --- a/app/HttpInterface.py +++ b/app/HttpInterface.py @@ -5,85 +5,85 @@ import requests from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -SERVER_HOST= '127.0.0.1' +SERVER_HOST = '127.0.0.1' SERVER_PORT = 8080 class RequestHandler(BaseHTTPRequestHandler): - - #Handler for the GET requests - def do_GET(self): - if self.path == "/": - self.send_response(200) - self.send_header('Content-type','text/html') - self.end_headers() - - write = self.wfile.write - # Describe state machine: - categories = [] + #Handler for the GET requests + def do_GET(self): + if self.path == "/": + self.send_response(200) + self.send_header('Content-type','text/html') + self.end_headers() - write("") - write("") + write = self.wfile.write - for category, state, last_changed in self.server.statemachine.get_states(): - categories.append(category) + # Describe state machine: + categories = [] - write("".format(category, state, last_changed.strftime("%H:%M:%S %d-%m-%Y"))) + write("
NameStateLast Changed
{}{}{}
") + write("") - write("
NameStateLast Changed
") + for category, state, last_changed in self.server.statemachine.get_states(): + categories.append(category) - # Small form to change the state - write("
Change state:
") - write("
") - write("") + # Small form to change the state + write("
Change state:
") + write("") + write("") - write("") - write("
") + for category in categories: + write("".format(category)) - else: - self.send_response(404) + write("") + + write("") + write("") + write("") + + else: + self.send_response(404) - def do_POST(self): - length = int(self.headers['Content-Length']) - post_data = urlparse.parse_qs(self.rfile.read(length)) + def do_POST(self): + length = int(self.headers['Content-Length']) + post_data = urlparse.parse_qs(self.rfile.read(length)) - if self.path == "/change_state": - self.server.statemachine.set_state(post_data['category'][0], post_data['new_state'][0]) + if self.path == "/change_state": + self.server.statemachine.set_state(post_data['category'][0], post_data['new_state'][0]) - self.send_response(301) - self.send_header("Location", "/") - self.end_headers() + self.send_response(301) + self.send_header("Location", "/") + self.end_headers() - else: - self.send_response(404) + else: + self.send_response(404) class HttpInterface(threading.Thread): - def __init__(self, eventbus, statemachine): - threading.Thread.__init__(self) + def __init__(self, eventbus, statemachine): + threading.Thread.__init__(self) - self.server = HTTPServer((SERVER_HOST, SERVER_PORT), RequestHandler) + self.server = HTTPServer((SERVER_HOST, SERVER_PORT), RequestHandler) - self.server.eventbus = eventbus - self.server.statemachine = statemachine + self.server.eventbus = eventbus + self.server.statemachine = statemachine - self._stop = threading.Event() + self._stop = threading.Event() - def run(self): - while not self._stop.is_set(): - self.server.handle_request() + def run(self): + while not self._stop.is_set(): + self.server.handle_request() - def stop(self): - self._stop.set() + def stop(self): + self._stop.set() - # Trigger a fake request to get the server to quit - requests.get("http://{}:{}".format(SERVER_HOST, SERVER_PORT)) \ No newline at end of file + # Trigger a fake request to get the server to quit + requests.get("http://{}:{}".format(SERVER_HOST, SERVER_PORT)) diff --git a/app/StateMachine.py b/app/StateMachine.py index 8b6347e2bb7..83685c32763 100644 --- a/app/StateMachine.py +++ b/app/StateMachine.py @@ -1,4 +1,4 @@ -from collections import defaultdict, namedtuple +from collections import namedtuple from threading import RLock from datetime import datetime @@ -7,54 +7,53 @@ from app.util import ensure_list, matcher EVENT_STATE_CHANGED = "state_changed" -state = namedtuple("State", ['state','last_changed']) +State = namedtuple("State", ['state','last_changed']) -class StateMachine: +class StateMachine(object): - def __init__(self, eventBus): - self.states = dict() - self.eventBus = eventBus - self.lock = RLock() + def __init__(self, eventbus): + self.states = dict() + self.eventbus = eventbus + self.lock = RLock() - def add_category(self, category, initialState): - self.states[category] = state(initialState, datetime.now()) + def add_category(self, category, initial_state): + self.states[category] = State(initial_state, datetime.now()) - def set_state(self, category, newState): - self.lock.acquire() + def set_state(self, category, new_state): + self.lock.acquire() - assert category in self.states, "Category does not exist: {}".format(category) - - oldState = self.states[category] + assert category in self.states, "Category does not exist: {}".format(category) - if oldState.state != newState: - self.states[category] = state(newState, datetime.now()) + old_state = self.states[category] - self.eventBus.fire(Event(EVENT_STATE_CHANGED, {'category':category, 'oldState':oldState, 'newState':self.states[category]})) + if old_state.state != new_state: + self.states[category] = State(new_state, datetime.now()) - self.lock.release() + self.eventbus.fire(Event(EVENT_STATE_CHANGED, {'category':category, 'old_state':old_state, 'new_state':self.states[category]})) - def get_state(self, category): - assert category in self.states, "Category does not exist: {}".format(category) + self.lock.release() - return self.states[category] + def get_state(self, category): + assert category in self.states, "Category does not exist: {}".format(category) - def get_states(self): - for category in sorted(self.states.keys()): - yield category, self.states[category].state, self.states[category].last_changed + return self.states[category] + + def get_states(self): + for category in sorted(self.states.keys()): + yield category, self.states[category].state, self.states[category].last_changed -def track_state_change(eventBus, category, fromState, toState, action): - fromState = ensure_list(fromState) - toState = ensure_list(toState) +def track_state_change(eventbus, category, from_state, to_state, action): + from_state = ensure_list(from_state) + to_state = ensure_list(to_state) - def listener(event): - assert isinstance(event, Event), "event needs to be of Event type" + def listener(event): + assert isinstance(event, Event), "event needs to be of Event type" - if category == event.data['category'] and \ - matcher(event.data['oldState'].state, fromState) and \ - matcher(event.data['newState'].state, toState): - - action(event.data['category'], event.data['oldState'], event.data['newState']) + if category == event.data['category'] and \ + matcher(event.data['old_state'].state, from_state) and \ + matcher(event.data['new_state'].state, to_state): - eventBus.listen(EVENT_STATE_CHANGED, listener) + action(event.data['category'], event.data['old_state'], event.data['new_state']) + eventbus.listen(EVENT_STATE_CHANGED, listener) diff --git a/app/actor/HueTrigger.py b/app/actor/HueTrigger.py index 94ac5b42066..419d46142ac 100644 --- a/app/actor/HueTrigger.py +++ b/app/actor/HueTrigger.py @@ -10,87 +10,85 @@ from app.observer.Timer import track_time_change LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD = timedelta(minutes=30) -class HueTrigger: - def __init__(self, config, eventbus, statemachine, device_tracker, weather): - self.eventbus = eventbus - self.statemachine = statemachine - self.weather = weather +class HueTrigger(object): + def __init__(self, config, eventbus, statemachine, device_tracker, weather): + self.eventbus = eventbus + self.statemachine = statemachine + self.weather = weather - self.bridge = Bridge(config.get("hue","host")) - self.lights = self.bridge.get_light_objects() - self.logger = logging.getLogger("HueTrigger") + self.bridge = Bridge(config.get("hue","host")) + self.lights = self.bridge.get_light_objects() + self.logger = logging.getLogger("HueTrigger") - # Track home coming of each seperate device - for category in device_tracker.device_state_categories(): - track_state_change(eventbus, category, STATE_DEVICE_NOT_HOME, STATE_DEVICE_HOME, self.handle_device_state_change) + # Track home coming of each seperate device + for category in device_tracker.device_state_categories(): + track_state_change(eventbus, category, STATE_DEVICE_NOT_HOME, STATE_DEVICE_HOME, self.handle_device_state_change) - # Track when all devices are gone to shut down lights - track_state_change(eventbus, STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_HOME, STATE_DEVICE_NOT_HOME, self.handle_device_state_change) + # Track when all devices are gone to shut down lights + track_state_change(eventbus, STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_HOME, STATE_DEVICE_NOT_HOME, self.handle_device_state_change) - # Track every time sun rises so we can schedule a time-based pre-sun set event - track_state_change(eventbus, STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON, SUN_STATE_ABOVE_HORIZON, self.handle_sun_rising) + # Track every time sun rises so we can schedule a time-based pre-sun set event + track_state_change(eventbus, STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON, SUN_STATE_ABOVE_HORIZON, self.handle_sun_rising) - # If the sun is already above horizon schedule the time-based pre-sun set event - if statemachine.get_state(STATE_CATEGORY_SUN) == SUN_STATE_ABOVE_HORIZON: - self.handle_sun_rising(None, None, None) + # If the sun is already above horizon schedule the time-based pre-sun set event + if statemachine.get_state(STATE_CATEGORY_SUN) == SUN_STATE_ABOVE_HORIZON: + self.handle_sun_rising(None, None, None) - def get_lights_status(self): - lights_are_on = sum([1 for light in self.lights if light.on]) > 0 + def get_lights_status(self): + lights_are_on = sum([1 for light in self.lights if light.on]) > 0 - light_needed = not lights_are_on and self.statemachine.get_state(STATE_CATEGORY_SUN).state == SUN_STATE_BELOW_HORIZON + light_needed = not lights_are_on and self.statemachine.get_state(STATE_CATEGORY_SUN).state == SUN_STATE_BELOW_HORIZON - return lights_are_on, light_needed + return lights_are_on, light_needed - def turn_lights_on(self, transitiontime=None): - command = {'on': True, 'xy': [0.5119, 0.4147], 'bri':164} + def turn_lights_on(self, transitiontime=None): + command = {'on': True, 'xy': [0.5119, 0.4147], 'bri':164} - if transitiontime is not None: - command['transitiontime'] = transitiontime + if transitiontime is not None: + command['transitiontime'] = transitiontime - self.bridge.set_light([1,2,3], command) + self.bridge.set_light([1, 2, 3], command) - def turn_lights_off(self, transitiontime=None): - command = {'on': False} + def turn_lights_off(self, transitiontime=None): + command = {'on': False} - if transitiontime is not None: - command['transitiontime'] = transitiontime + if transitiontime is not None: + command['transitiontime'] = transitiontime - self.bridge.set_light([1,2,3], command) + self.bridge.set_light([1, 2, 3], command) - def handle_sun_rising(self, category, oldState, newState): - # Schedule an event X minutes prior to sun setting - track_time_change(self.eventbus, self.handle_sun_setting, datetime=self.weather.next_sun_setting()-LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD) + def handle_sun_rising(self, category, old_state, new_state): + # Schedule an event X minutes prior to sun setting + track_time_change(self.eventbus, self.handle_sun_setting, point_in_time=self.weather.next_sun_setting()-LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD) - # Gets called when darkness starts falling in, slowly turn on the lights - def handle_sun_setting(self, now): - lights_are_on, light_needed = self.get_lights_status() + # Gets called when darkness starts falling in, slowly turn on the lights + def handle_sun_setting(self, now): + lights_are_on, light_needed = self.get_lights_status() - if not lights_are_on and self.statemachine.get_state(STATE_CATEGORY_ALL_DEVICES).state == STATE_DEVICE_HOME: - self.logger.info("Sun setting and devices home. Turning on lights.") + if not lights_are_on and self.statemachine.get_state(STATE_CATEGORY_ALL_DEVICES).state == STATE_DEVICE_HOME: + self.logger.info("Sun setting and devices home. Turning on lights.") - # We will start the lights now and by the time the sun sets - # the lights will be at full brightness - transitiontime = (self.weather.next_sun_setting() - datetime.now()).seconds * 10 + # We will start the lights now and by the time the sun sets + # the lights will be at full brightness + transitiontime = (self.weather.next_sun_setting() - datetime.now()).seconds * 10 - self.turn_lights_on(transitiontime) + self.turn_lights_on(transitiontime) - def handle_device_state_change(self, category, oldState, newState): - lights_are_on, light_needed = self.get_lights_status() - - # Specific device came home ? - if category != STATE_CATEGORY_ALL_DEVICES and newState.state == STATE_DEVICE_HOME and light_needed: - self.logger.info("Home coming event for {}. Turning lights on".format(category)) - self.turn_lights_on() - - # Did all devices leave the house? - elif category == STATE_CATEGORY_ALL_DEVICES and newState.state == STATE_DEVICE_NOT_HOME and lights_are_on: - self.logger.info("Everyone has left. Turning lights off") - self.turn_lights_off() + def handle_device_state_change(self, category, old_state, new_state): + lights_are_on, light_needed = self.get_lights_status() + # Specific device came home ? + if category != STATE_CATEGORY_ALL_DEVICES and new_state.state == STATE_DEVICE_HOME and light_needed: + self.logger.info("Home coming event for {}. Turning lights on".format(category)) + self.turn_lights_on() + # Did all devices leave the house? + elif category == STATE_CATEGORY_ALL_DEVICES and new_state.state == STATE_DEVICE_NOT_HOME and lights_are_on: + self.logger.info("Everyone has left. Turning lights off") + self.turn_lights_off() diff --git a/app/observer/Timer.py b/app/observer/Timer.py index e679f61b035..c4af0249bd7 100644 --- a/app/observer/Timer.py +++ b/app/observer/Timer.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime import threading import time @@ -12,53 +12,52 @@ assert 60 % TIME_INTERVAL == 0, "60 % TIME_INTERVAL should be 0!" EVENT_TIME_CHANGED = "time_changed" class Timer(threading.Thread): - def __init__(self, eventbus): - threading.Thread.__init__(self) + def __init__(self, eventbus): + threading.Thread.__init__(self) - self.eventbus = eventbus - self._stop = threading.Event() + self.eventbus = eventbus + self._stop = threading.Event() - def stop(self): - self._stop.set() + def stop(self): + self._stop.set() - def run(self): - now = datetime.now() + def run(self): + now = datetime.now() - while True: - if self._stop.isSet(): - break + while True: + if self._stop.isSet(): + break - self.eventbus.fire(Event(EVENT_TIME_CHANGED, {'now':now})) + self.eventbus.fire(Event(EVENT_TIME_CHANGED, {'now':now})) - while True: - time.sleep(1) + while True: + time.sleep(1) - now = datetime.now() + now = datetime.now() - if self._stop.isSet() or now.second % TIME_INTERVAL == 0: - break + if self._stop.isSet() or now.second % TIME_INTERVAL == 0: + break -def track_time_change(eventBus, action, year='*', month='*', day='*', hour='*', minute='*', second='*', datetime=None, listen_once=False): - year, month, day = ensure_list(year), ensure_list(month), ensure_list(day) - hour, minute, second = ensure_list(hour), ensure_list(minute), ensure_list(second) +def track_time_change(eventBus, action, year='*', month='*', day='*', hour='*', minute='*', second='*', point_in_time=None, listen_once=False): + year, month, day = ensure_list(year), ensure_list(month), ensure_list(day) + hour, minute, second = ensure_list(hour), ensure_list(minute), ensure_list(second) - def listener(event): - assert isinstance(event, Event), "event needs to be of Event type" + def listener(event): + assert isinstance(event, Event), "event needs to be of Event type" - if (datetime is not None and event.data['now'] > datetime) or \ - datetime is None and \ - matcher(event.data['now'].year, year) and \ - matcher(event.data['now'].month, month) and \ - matcher(event.data['now'].day, day) and \ - matcher(event.data['now'].hour, hour) and \ - matcher(event.data['now'].minute, minute) and \ - matcher(event.data['now'].second, second): + if (point_in_time is not None and event.data['now'] > point_in_time) or \ + point_in_time is None and \ + matcher(event.data['now'].year, year) and \ + matcher(event.data['now'].month, month) and \ + matcher(event.data['now'].day, day) and \ + matcher(event.data['now'].hour, hour) and \ + matcher(event.data['now'].minute, minute) and \ + matcher(event.data['now'].second, second): - # datetime are exact points in time so we always remove it after fire - event.removeListener = listen_once or datetime is not None + # point_in_time are exact points in time so we always remove it after fire + event.remove_listener = listen_once or point_in_time is not None - action(event.data['now']) - - eventBus.listen(EVENT_TIME_CHANGED, listener) + action(event.data['now']) + eventBus.listen(EVENT_TIME_CHANGED, listener) diff --git a/app/observer/TomatoDeviceScanner.py b/app/observer/TomatoDeviceScanner.py index d350149d566..21746c4fc52 100644 --- a/app/observer/TomatoDeviceScanner.py +++ b/app/observer/TomatoDeviceScanner.py @@ -10,93 +10,94 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) KNOWN_DEVICES_FILE = "tomato_known_devices.csv" -class TomatoDeviceScanner: - # self.logger +class TomatoDeviceScanner(object): + # self.logger - def __init__(self, config): - self.config = config - self.logger = logging.getLogger(__name__) - self.lock = Lock() - self.date_updated = None - self.last_results = None + def __init__(self, config): + self.config = config + self.logger = logging.getLogger(__name__) + self.lock = Lock() + self.date_updated = None + self.last_results = None - # Read known devices - if os.path.isfile(KNOWN_DEVICES_FILE): - with open(KNOWN_DEVICES_FILE) as inp: - known_devices = { row['mac']: row for row in csv.DictReader(inp) } + # Read known devices + if os.path.isfile(KNOWN_DEVICES_FILE): + with open(KNOWN_DEVICES_FILE) as inp: + known_devices = { row['mac']: row for row in csv.DictReader(inp) } - # Update known devices csv file for future use - with open(KNOWN_DEVICES_FILE, 'a') as outp: - writer = csv.writer(outp) + # Update known devices csv file for future use + with open(KNOWN_DEVICES_FILE, 'a') as outp: + writer = csv.writer(outp) - # Query for new devices - exec(self.tomato_request("devlist")) + # Query for new devices + exec(self.tomato_request("devlist")) - for name, _, mac, _ in dhcpd_lease: - if mac not in known_devices: - writer.writerow((mac, name, 0)) + for name, _, mac, _ in dhcpd_lease: + if mac not in known_devices: + writer.writerow((mac, name, 0)) - # Create a dict with ID: NAME of the devices to track - self.devices_to_track = dict() + # Create a dict with ID: NAME of the devices to track + self.devices_to_track = dict() - for mac in [mac for mac in known_devices if known_devices[mac]['track'] == '1']: - self.devices_to_track[mac] = known_devices[mac]['name'] - - # Quicker way of the previous statement but it doesn't go together with exec: - # unqualified exec is not allowed in function '__init__' it contains a nested function with free variables - # self.devices_to_track = {mac: known_devices[mac]['name'] for mac in known_devices if known_devices[mac]['track'] == '1'} + for mac in known_devices: + if known_devices[mac]['track'] == '1': + self.devices_to_track[mac] = known_devices[mac]['name'] + + # Quicker way of the previous statement but it doesn't go together with exec: + # unqualified exec is not allowed in function '__init__' it contains a nested function with free variables + # self.devices_to_track = {mac: known_devices[mac]['name'] for mac in known_devices if known_devices[mac]['track'] == '1'} - def get_devices_to_track(self): - return self.devices_to_track + def get_devices_to_track(self): + return self.devices_to_track - def scan_devices(self): - self.lock.acquire() + def scan_devices(self): + self.lock.acquire() - # We don't want to hammer the router. Only update if MIN_TIME_BETWEEN_SCANS has passed - if self.date_updated is None or datetime.now() - self.date_updated > MIN_TIME_BETWEEN_SCANS: - self.logger.info("Scanning for new devices") + # We don't want to hammer the router. Only update if MIN_TIME_BETWEEN_SCANS has passed + if self.date_updated is None or datetime.now() - self.date_updated > MIN_TIME_BETWEEN_SCANS: + self.logger.info("Scanning for new devices") - try: - # Query for new devices - exec(self.tomato_request("devlist")) + try: + # Query for new devices + exec(self.tomato_request("devlist")) - self.last_results = [mac for iface, mac, rssi, tx, rx, quality, unknown_num in wldev] + self.last_results = [mac for iface, mac, rssi, tx, rx, quality, unknown_num in wldev] - except: - self.logger.exception("Scanning failed") + except Exception: + self.logger.exception("Scanning failed") - - self.lock.release() - return self.last_results - def tomato_request(self, action): - # Get router info - r = requests.post('http://{}/update.cgi'.format(self.config.get('tomato','host')), - data={'_http_id':self.config.get('tomato','http_id'), 'exec':action}, - auth=requests.auth.HTTPBasicAuth(self.config.get('tomato','username'), self.config.get('tomato','password'))) + self.lock.release() + return self.last_results - return r.text + def tomato_request(self, action): + # Get router info + req = requests.post('http://{}/update.cgi'.format(self.config.get('tomato','host')), + data={'_http_id':self.config.get('tomato','http_id'), 'exec':action}, + auth=requests.auth.HTTPBasicAuth(self.config.get('tomato','username'), self.config.get('tomato','password'))) + + return req.text """ for ip, mac, iface in arplist: - pass + pass # print wlnoise # print dhcpd_static for iface, mac, rssi, tx, rx, quality, unknown_num in wldev: - print mac, quality + print mac, quality print "" for name, ip, mac, lease in dhcpd_lease: - if name: - print name, ip + if name: + print name, ip - else: - print ip -""" \ No newline at end of file + else: + print ip +""" diff --git a/app/observer/WeatherWatcher.py b/app/observer/WeatherWatcher.py index 8aa55965a57..a5b29854aa5 100644 --- a/app/observer/WeatherWatcher.py +++ b/app/observer/WeatherWatcher.py @@ -1,10 +1,8 @@ import logging -from datetime import datetime, timedelta +from datetime import timedelta import ephem -from app.EventBus import Event - from app.observer.Timer import track_time_change STATE_CATEGORY_SUN = "weather.sun" @@ -12,46 +10,44 @@ STATE_CATEGORY_SUN = "weather.sun" SUN_STATE_ABOVE_HORIZON = "above_horizon" SUN_STATE_BELOW_HORIZON = "below_horizon" -class WeatherWatcher: - def __init__(self, config, eventbus, statemachine): - self.logger = logging.getLogger(__name__) - self.config = config - self.eventbus = eventbus - self.statemachine = statemachine +class WeatherWatcher(object): + def __init__(self, config, eventbus, statemachine): + self.logger = logging.getLogger(__name__) + self.config = config + self.eventbus = eventbus + self.statemachine = statemachine - self.observer = ephem.Observer() - self.observer.lat = self.config.get('common','latitude') - self.observer.long = self.config.get('common','longitude') + self.observer = ephem.Observer() + self.observer.lat = self.config.get('common','latitude') + self.observer.long = self.config.get('common','longitude') - self.sun = ephem.Sun() + self.sun = ephem.Sun() - statemachine.add_category(STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON) + statemachine.add_category(STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON) - self.update_sun_state() + self.update_sun_state() - def next_sun_rising(self): - return ephem.localtime(self.observer.next_rising(self.sun)) + def next_sun_rising(self): + return ephem.localtime(self.observer.next_rising(self.sun)) - def next_sun_setting(self): - return ephem.localtime(self.observer.next_setting(self.sun)) + def next_sun_setting(self): + return ephem.localtime(self.observer.next_setting(self.sun)) - def update_sun_state(self, now=None): - next_rising = ephem.localtime(self.observer.next_rising(self.sun)) - next_setting = ephem.localtime(self.observer.next_setting(self.sun)) + def update_sun_state(self, now=None): + next_rising = ephem.localtime(self.observer.next_rising(self.sun)) + next_setting = ephem.localtime(self.observer.next_setting(self.sun)) - if next_rising > next_setting: - new_state = SUN_STATE_ABOVE_HORIZON - next_change = next_setting + if next_rising > next_setting: + new_state = SUN_STATE_ABOVE_HORIZON + next_change = next_setting - else: - new_state = SUN_STATE_BELOW_HORIZON - next_change = next_rising + else: + new_state = SUN_STATE_BELOW_HORIZON + next_change = next_rising - self.logger.info("Updating sun state to {}. Next change: {}".format(new_state, next_change)) + self.logger.info("Updating sun state to {}. Next change: {}".format(new_state, next_change)) - self.statemachine.set_state(STATE_CATEGORY_SUN, new_state) - - # +10 seconds to be sure that the change has occured - track_time_change(self.eventbus, self.update_sun_state, datetime=next_change + timedelta(seconds=10)) - + self.statemachine.set_state(STATE_CATEGORY_SUN, new_state) + # +10 seconds to be sure that the change has occured + track_time_change(self.eventbus, self.update_sun_state, point_in_time=next_change + timedelta(seconds=10)) diff --git a/app/util.py b/app/util.py index 3adac6ea053..78fa8da6959 100644 --- a/app/util.py +++ b/app/util.py @@ -1,5 +1,5 @@ def ensure_list(parameter): - return parameter if isinstance(parameter, list) else [parameter] + return parameter if isinstance(parameter, list) else [parameter] def matcher(subject, pattern): - return '*' in pattern or subject in pattern + return '*' in pattern or subject in pattern