Cleaning up code

This commit is contained in:
Paulus Schoutsen 2013-09-20 10:00:31 -07:00
parent 90c343d16b
commit 7636092c7d
10 changed files with 403 additions and 423 deletions

View File

@ -16,70 +16,64 @@ STATE_CATEGORY_ALL_DEVICES = 'device.alldevices'
STATE_CATEGORY_DEVICE_FORMAT = 'device.{}' STATE_CATEGORY_DEVICE_FORMAT = 'device.{}'
class DeviceTracker: class DeviceTracker(object):
def __init__(self, eventbus, statemachine, device_scanner): def __init__(self, eventbus, statemachine, device_scanner):
self.statemachine = statemachine self.statemachine = statemachine
self.eventbus = eventbus self.eventbus = eventbus
self.device_scanner = device_scanner
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], self.devices_to_track = { device: { 'name': temp_devices_to_track[device],
'last_seen': default_last_seen, 'last_seen': default_last_seen,
'category': STATE_CATEGORY_DEVICE_FORMAT.format(temp_devices_to_track[device]) } 'category': STATE_CATEGORY_DEVICE_FORMAT.format(temp_devices_to_track[device]) }
for device in temp_devices_to_track } 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 for device in self.devices_to_track:
statemachine.add_category(STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_DEFAULT) self.statemachine.add_category(self.devices_to_track[device]['category'], STATE_DEVICE_DEFAULT)
for device in self.devices_to_track: track_time_change(eventbus, lambda time: self.update_devices(device_scanner.scan_devices()))
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()))
def device_state_categories(self): def device_state_categories(self):
for device in self.devices_to_track: for device in self.devices_to_track:
yield self.devices_to_track[device]['category'] yield self.devices_to_track[device]['category']
def set_state(self, device, state): def set_state(self, device, state):
if state == STATE_DEVICE_HOME: if state == STATE_DEVICE_HOME:
self.devices_to_track[device]['last_seen'] = datetime.now() 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): def update_devices(self, found_devices):
# Keep track of devices that are home, all that are not will be marked not home # Keep track of devices that are home, all that are not will be marked not home
temp_tracking_devices = self.devices_to_track.keys() temp_tracking_devices = self.devices_to_track.keys()
for device in found_devices: for device in found_devices:
# Are we tracking this device? # Are we tracking this device?
if device in temp_tracking_devices: if device in temp_tracking_devices:
temp_tracking_devices.remove(device) 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 # 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 # 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 # Because we do not want to have stuff happening when the device does
# not show up for 1 scan beacuse of reboot etc # not show up for 1 scan beacuse of reboot etc
for device in temp_tracking_devices: for device in temp_tracking_devices:
if self.statemachine.get_state(self.devices_to_track[device]['category']).state == STATE_DEVICE_HOME and \ if datetime.now() - self.devices_to_track[device]['last_seen'] > TIME_SPAN_FOR_ERROR_IN_SCANNING:
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 self.statemachine.set_state(STATE_CATEGORY_ALL_DEVICES, all_devices_state)
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)

View File

@ -6,59 +6,58 @@ from threading import Thread, RLock
ALL_EVENTS = '*' ALL_EVENTS = '*'
class EventBus: class EventBus(object):
def __init__(self): def __init__(self):
self.listeners = defaultdict(list) self.listeners = defaultdict(list)
self.lock = RLock() self.lock = RLock()
self.logger =logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
def fire(self, event):
assert isinstance(event, Event), "event needs to be an instance of Event"
# We dont want the eventbus to be blocking, def fire(self, event):
# We dont want the eventbus to crash when one of its listeners throws an Exception assert isinstance(event, Event), "event needs to be an instance of Event"
# So run in a thread
def run():
self.lock.acquire()
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]): self.logger.info("{} event received: {}".format(event.event_type, event.data))
callback(event)
if event.removeListener: for callback in chain(self.listeners[ALL_EVENTS], self.listeners[event.event_type]):
if callback in self.listeners[ALL_EVENTS]: callback(event)
self.listeners[ALL_EVENTS].remove(callback)
if callback in self.listeners[event.eventType]: if event.remove_listener:
self.listeners[event.eventType].remove(callback) 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: event.remove_listener = False
break
self.lock.release() if event.stop_propegating:
break
Thread(target=run).start() self.lock.release()
def listen(self, event_type, callback): Thread(target=run).start()
self.lock.acquire()
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: class Event(object):
def __init__(self, eventType, data): def __init__(self, event_type, data):
self.eventType = eventType self.event_type = event_type
self.data = data self.data = data
self.stopPropegating = False self.stop_propegating = False
self.removeListener = False self.remove_listener = False
def __str__(self):
return str([self.eventType, self.data])
def __str__(self):
return str([self.event_type, self.data])

View File

@ -4,103 +4,97 @@ import time
from app.StateMachine import StateMachine from app.StateMachine import StateMachine
from app.EventBus import EventBus from app.EventBus import EventBus
from app.DeviceTracker import DeviceTracker from app.DeviceTracker import DeviceTracker
from HttpInterface import HttpInterface from app.HttpInterface import HttpInterface
from app.observer.WeatherWatcher import WeatherWatcher from app.observer.WeatherWatcher import WeatherWatcher
from app.observer.TomatoDeviceScanner import TomatoDeviceScanner
from app.observer.Timer import Timer from app.observer.Timer import Timer
from app.actor.HueTrigger import HueTrigger from app.actor.HueTrigger import HueTrigger
class HomeAssistant: class HomeAssistant(object):
def __init__(self): def __init__(self):
self.config = None self.config = None
self.eventbus = None self.eventbus = None
self.statemachine = None self.statemachine = None
self.timer = None self.timer = None
self.weatherwatcher = None self.weatherwatcher = None
self.devicetracker = None self.devicetracker = None
self.huetrigger = None self.huetrigger = None
self.httpinterface = None self.httpinterface = None
def get_config(self): def get_config(self):
if self.config is None: if self.config is None:
self.config = SafeConfigParser() self.config = SafeConfigParser()
self.config.read("home-assistant.conf") self.config.read("home-assistant.conf")
return self.config return self.config
def get_event_bus(self): def get_event_bus(self):
if self.eventbus is None: if self.eventbus is None:
self.eventbus = EventBus() self.eventbus = EventBus()
return self.eventbus return self.eventbus
def get_state_machine(self): def get_state_machine(self):
if self.statemachine is None: if self.statemachine is None:
self.statemachine = StateMachine(self.get_event_bus()) self.statemachine = StateMachine(self.get_event_bus())
return self.statemachine return self.statemachine
def setup_timer(self): def setup_timer(self):
if self.timer is None: if self.timer is None:
self.timer = Timer(self.get_event_bus()) self.timer = Timer(self.get_event_bus())
return self.timer return self.timer
def setup_weather_watcher(self): def setup_weather_watcher(self):
if self.weatherwatcher is None: if self.weatherwatcher is None:
self.weatherwatcher = WeatherWatcher(self.get_config(), self.get_event_bus(), self.get_state_machine()) 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): def setup_device_tracker(self, device_scanner):
if self.devicetracker is None: if self.devicetracker is None:
self.devicetracker = DeviceTracker(self.get_event_bus(), self.get_state_machine(), device_scanner) self.devicetracker = DeviceTracker(self.get_event_bus(), self.get_state_machine(), device_scanner)
return self.devicetracker return self.devicetracker
def setup_hue_trigger(self): def setup_hue_trigger(self):
if self.huetrigger is None: if self.huetrigger is None:
assert self.devicetracker is not None, "Cannot setup Hue Trigger without a device tracker being setup" 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): def setup_http_interface(self):
self.httpinterface = HttpInterface(self.get_event_bus(), self.get_state_machine()) self.httpinterface = HttpInterface(self.get_event_bus(), self.get_state_machine())
self.httpinterface.start() self.httpinterface.start()
return self.httpinterface 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
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

View File

@ -5,85 +5,85 @@ import requests
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
SERVER_HOST= '127.0.0.1' SERVER_HOST = '127.0.0.1'
SERVER_PORT = 8080 SERVER_PORT = 8080
class RequestHandler(BaseHTTPRequestHandler): 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: #Handler for the GET requests
categories = [] def do_GET(self):
if self.path == "/":
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
write("<table>") write = self.wfile.write
write("<tr><th>Name</th><th>State</th><th>Last Changed</th></tr>")
for category, state, last_changed in self.server.statemachine.get_states(): # Describe state machine:
categories.append(category) categories = []
write("<tr><td>{}</td><td>{}</td><td>{}</td></tr>".format(category, state, last_changed.strftime("%H:%M:%S %d-%m-%Y"))) write("<table>")
write("<tr><th>Name</th><th>State</th><th>Last Changed</th></tr>")
write("</table>") for category, state, last_changed in self.server.statemachine.get_states():
categories.append(category)
# Small form to change the state write("<tr><td>{}</td><td>{}</td><td>{}</td></tr>".format(category, state, last_changed.strftime("%H:%M:%S %d-%m-%Y")))
write("<br />Change state:<br />")
write("<form action='change_state' method='POST'>")
write("<select name='category'>")
for category in categories: write("</table>")
write("<option>{}</option>".format(category))
write("</select>") # Small form to change the state
write("<br />Change state:<br />")
write("<form action='change_state' method='POST'>")
write("<select name='category'>")
write("<input name='new_state' />") for category in categories:
write("<input type='submit' value='set state' />") write("<option>{}</option>".format(category))
write("</form>")
else: write("</select>")
self.send_response(404)
write("<input name='new_state' />")
write("<input type='submit' value='set state' />")
write("</form>")
else:
self.send_response(404)
def do_POST(self): def do_POST(self):
length = int(self.headers['Content-Length']) length = int(self.headers['Content-Length'])
post_data = urlparse.parse_qs(self.rfile.read(length)) post_data = urlparse.parse_qs(self.rfile.read(length))
if self.path == "/change_state": if self.path == "/change_state":
self.server.statemachine.set_state(post_data['category'][0], post_data['new_state'][0]) self.server.statemachine.set_state(post_data['category'][0], post_data['new_state'][0])
self.send_response(301) self.send_response(301)
self.send_header("Location", "/") self.send_header("Location", "/")
self.end_headers() self.end_headers()
else: else:
self.send_response(404) self.send_response(404)
class HttpInterface(threading.Thread): class HttpInterface(threading.Thread):
def __init__(self, eventbus, statemachine): def __init__(self, eventbus, statemachine):
threading.Thread.__init__(self) 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.eventbus = eventbus
self.server.statemachine = statemachine self.server.statemachine = statemachine
self._stop = threading.Event() self._stop = threading.Event()
def run(self): def run(self):
while not self._stop.is_set(): while not self._stop.is_set():
self.server.handle_request() self.server.handle_request()
def stop(self): def stop(self):
self._stop.set() self._stop.set()
# Trigger a fake request to get the server to quit # Trigger a fake request to get the server to quit
requests.get("http://{}:{}".format(SERVER_HOST, SERVER_PORT)) requests.get("http://{}:{}".format(SERVER_HOST, SERVER_PORT))

View File

@ -1,4 +1,4 @@
from collections import defaultdict, namedtuple from collections import namedtuple
from threading import RLock from threading import RLock
from datetime import datetime from datetime import datetime
@ -7,54 +7,53 @@ from app.util import ensure_list, matcher
EVENT_STATE_CHANGED = "state_changed" 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): def __init__(self, eventbus):
self.states = dict() self.states = dict()
self.eventBus = eventBus self.eventbus = eventbus
self.lock = RLock() self.lock = RLock()
def add_category(self, category, initialState): def add_category(self, category, initial_state):
self.states[category] = state(initialState, datetime.now()) self.states[category] = State(initial_state, datetime.now())
def set_state(self, category, newState): def set_state(self, category, new_state):
self.lock.acquire() self.lock.acquire()
assert category in self.states, "Category does not exist: {}".format(category) assert category in self.states, "Category does not exist: {}".format(category)
oldState = self.states[category]
if oldState.state != newState: old_state = self.states[category]
self.states[category] = state(newState, datetime.now())
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): self.lock.release()
assert category in self.states, "Category does not exist: {}".format(category)
return self.states[category] def get_state(self, category):
assert category in self.states, "Category does not exist: {}".format(category)
def get_states(self): return self.states[category]
for category in sorted(self.states.keys()):
yield category, self.states[category].state, self.states[category].last_changed 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): def track_state_change(eventbus, category, from_state, to_state, action):
fromState = ensure_list(fromState) from_state = ensure_list(from_state)
toState = ensure_list(toState) to_state = ensure_list(to_state)
def listener(event): def listener(event):
assert isinstance(event, Event), "event needs to be of Event type" 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['oldState'].state, fromState) and \ matcher(event.data['old_state'].state, from_state) and \
matcher(event.data['newState'].state, toState): matcher(event.data['new_state'].state, to_state):
action(event.data['category'], event.data['oldState'], event.data['newState'])
eventBus.listen(EVENT_STATE_CHANGED, listener) action(event.data['category'], event.data['old_state'], event.data['new_state'])
eventbus.listen(EVENT_STATE_CHANGED, listener)

View File

@ -10,87 +10,85 @@ from app.observer.Timer import track_time_change
LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD = timedelta(minutes=30) LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD = timedelta(minutes=30)
class HueTrigger: class HueTrigger(object):
def __init__(self, config, eventbus, statemachine, device_tracker, weather): def __init__(self, config, eventbus, statemachine, device_tracker, weather):
self.eventbus = eventbus self.eventbus = eventbus
self.statemachine = statemachine self.statemachine = statemachine
self.weather = weather self.weather = weather
self.bridge = Bridge(config.get("hue","host")) self.bridge = Bridge(config.get("hue","host"))
self.lights = self.bridge.get_light_objects() self.lights = self.bridge.get_light_objects()
self.logger = logging.getLogger("HueTrigger") self.logger = logging.getLogger("HueTrigger")
# Track home coming of each seperate device # Track home coming of each seperate device
for category in device_tracker.device_state_categories(): 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_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 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_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 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_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 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: if statemachine.get_state(STATE_CATEGORY_SUN) == SUN_STATE_ABOVE_HORIZON:
self.handle_sun_rising(None, None, None) self.handle_sun_rising(None, None, None)
def get_lights_status(self): def get_lights_status(self):
lights_are_on = sum([1 for light in self.lights if light.on]) > 0 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): def turn_lights_on(self, transitiontime=None):
command = {'on': True, 'xy': [0.5119, 0.4147], 'bri':164} command = {'on': True, 'xy': [0.5119, 0.4147], 'bri':164}
if transitiontime is not None: if transitiontime is not None:
command['transitiontime'] = transitiontime 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): def turn_lights_off(self, transitiontime=None):
command = {'on': False} command = {'on': False}
if transitiontime is not None: if transitiontime is not None:
command['transitiontime'] = transitiontime 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): def handle_sun_rising(self, category, old_state, new_state):
# Schedule an event X minutes prior to sun setting # 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) 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 # Gets called when darkness starts falling in, slowly turn on the lights
def handle_sun_setting(self, now): def handle_sun_setting(self, now):
lights_are_on, light_needed = self.get_lights_status() 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: 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.") self.logger.info("Sun setting and devices home. Turning on lights.")
# We will start the lights now and by the time the sun sets # We will start the lights now and by the time the sun sets
# the lights will be at full brightness # the lights will be at full brightness
transitiontime = (self.weather.next_sun_setting() - datetime.now()).seconds * 10 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): def handle_device_state_change(self, category, old_state, new_state):
lights_are_on, light_needed = self.get_lights_status() 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()
# 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()

View File

@ -1,4 +1,4 @@
from datetime import datetime, timedelta from datetime import datetime
import threading import threading
import time import time
@ -12,53 +12,52 @@ assert 60 % TIME_INTERVAL == 0, "60 % TIME_INTERVAL should be 0!"
EVENT_TIME_CHANGED = "time_changed" EVENT_TIME_CHANGED = "time_changed"
class Timer(threading.Thread): class Timer(threading.Thread):
def __init__(self, eventbus): def __init__(self, eventbus):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.eventbus = eventbus self.eventbus = eventbus
self._stop = threading.Event() self._stop = threading.Event()
def stop(self): def stop(self):
self._stop.set() self._stop.set()
def run(self): def run(self):
now = datetime.now() now = datetime.now()
while True: while True:
if self._stop.isSet(): if self._stop.isSet():
break break
self.eventbus.fire(Event(EVENT_TIME_CHANGED, {'now':now})) self.eventbus.fire(Event(EVENT_TIME_CHANGED, {'now':now}))
while True: while True:
time.sleep(1) time.sleep(1)
now = datetime.now() now = datetime.now()
if self._stop.isSet() or now.second % TIME_INTERVAL == 0: if self._stop.isSet() or now.second % TIME_INTERVAL == 0:
break break
def track_time_change(eventBus, action, year='*', month='*', day='*', hour='*', minute='*', second='*', datetime=None, listen_once=False): 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) year, month, day = ensure_list(year), ensure_list(month), ensure_list(day)
hour, minute, second = ensure_list(hour), ensure_list(minute), ensure_list(second) hour, minute, second = ensure_list(hour), ensure_list(minute), ensure_list(second)
def listener(event): def listener(event):
assert isinstance(event, Event), "event needs to be of Event type" assert isinstance(event, Event), "event needs to be of Event type"
if (datetime is not None and event.data['now'] > datetime) or \ if (point_in_time is not None and event.data['now'] > point_in_time) or \
datetime is None and \ point_in_time is None and \
matcher(event.data['now'].year, year) and \ matcher(event.data['now'].year, year) and \
matcher(event.data['now'].month, month) and \ matcher(event.data['now'].month, month) and \
matcher(event.data['now'].day, day) and \ matcher(event.data['now'].day, day) and \
matcher(event.data['now'].hour, hour) and \ matcher(event.data['now'].hour, hour) and \
matcher(event.data['now'].minute, minute) and \ matcher(event.data['now'].minute, minute) and \
matcher(event.data['now'].second, second): matcher(event.data['now'].second, second):
# datetime are exact points in time so we always remove it after fire # point_in_time are exact points in time so we always remove it after fire
event.removeListener = listen_once or datetime is not None event.remove_listener = listen_once or point_in_time is not None
action(event.data['now']) action(event.data['now'])
eventBus.listen(EVENT_TIME_CHANGED, listener)
eventBus.listen(EVENT_TIME_CHANGED, listener)

View File

@ -10,93 +10,94 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
KNOWN_DEVICES_FILE = "tomato_known_devices.csv" KNOWN_DEVICES_FILE = "tomato_known_devices.csv"
class TomatoDeviceScanner: class TomatoDeviceScanner(object):
# self.logger # self.logger
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.lock = Lock() self.lock = Lock()
self.date_updated = None self.date_updated = None
self.last_results = None self.last_results = None
# Read known devices # Read known devices
if os.path.isfile(KNOWN_DEVICES_FILE): if os.path.isfile(KNOWN_DEVICES_FILE):
with open(KNOWN_DEVICES_FILE) as inp: with open(KNOWN_DEVICES_FILE) as inp:
known_devices = { row['mac']: row for row in csv.DictReader(inp) } known_devices = { row['mac']: row for row in csv.DictReader(inp) }
# Update known devices csv file for future use # Update known devices csv file for future use
with open(KNOWN_DEVICES_FILE, 'a') as outp: with open(KNOWN_DEVICES_FILE, 'a') as outp:
writer = csv.writer(outp) writer = csv.writer(outp)
# Query for new devices # Query for new devices
exec(self.tomato_request("devlist")) exec(self.tomato_request("devlist"))
for name, _, mac, _ in dhcpd_lease: for name, _, mac, _ in dhcpd_lease:
if mac not in known_devices: if mac not in known_devices:
writer.writerow((mac, name, 0)) writer.writerow((mac, name, 0))
# Create a dict with ID: NAME of the devices to track # Create a dict with ID: NAME of the devices to track
self.devices_to_track = dict() self.devices_to_track = dict()
for mac in [mac for mac in known_devices if known_devices[mac]['track'] == '1']: for mac in known_devices:
self.devices_to_track[mac] = known_devices[mac]['name'] 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 # Quicker way of the previous statement but it doesn't go together with exec:
# self.devices_to_track = {mac: known_devices[mac]['name'] for mac in known_devices if known_devices[mac]['track'] == '1'} # 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): def get_devices_to_track(self):
return self.devices_to_track return self.devices_to_track
def scan_devices(self): def scan_devices(self):
self.lock.acquire() self.lock.acquire()
# We don't want to hammer the router. Only update if MIN_TIME_BETWEEN_SCANS has passed # 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: if self.date_updated is None or datetime.now() - self.date_updated > MIN_TIME_BETWEEN_SCANS:
self.logger.info("Scanning for new devices") self.logger.info("Scanning for new devices")
try: try:
# Query for new devices # Query for new devices
exec(self.tomato_request("devlist")) 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: except Exception:
self.logger.exception("Scanning failed") self.logger.exception("Scanning failed")
self.lock.release()
return self.last_results
def tomato_request(self, action): self.lock.release()
# Get router info return self.last_results
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')))
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: for ip, mac, iface in arplist:
pass pass
# print wlnoise # print wlnoise
# print dhcpd_static # print dhcpd_static
for iface, mac, rssi, tx, rx, quality, unknown_num in wldev: for iface, mac, rssi, tx, rx, quality, unknown_num in wldev:
print mac, quality print mac, quality
print "" print ""
for name, ip, mac, lease in dhcpd_lease: for name, ip, mac, lease in dhcpd_lease:
if name: if name:
print name, ip print name, ip
else: else:
print ip print ip
""" """

View File

@ -1,10 +1,8 @@
import logging import logging
from datetime import datetime, timedelta from datetime import timedelta
import ephem import ephem
from app.EventBus import Event
from app.observer.Timer import track_time_change from app.observer.Timer import track_time_change
STATE_CATEGORY_SUN = "weather.sun" STATE_CATEGORY_SUN = "weather.sun"
@ -12,46 +10,44 @@ STATE_CATEGORY_SUN = "weather.sun"
SUN_STATE_ABOVE_HORIZON = "above_horizon" SUN_STATE_ABOVE_HORIZON = "above_horizon"
SUN_STATE_BELOW_HORIZON = "below_horizon" SUN_STATE_BELOW_HORIZON = "below_horizon"
class WeatherWatcher: class WeatherWatcher(object):
def __init__(self, config, eventbus, statemachine): def __init__(self, config, eventbus, statemachine):
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.config = config self.config = config
self.eventbus = eventbus self.eventbus = eventbus
self.statemachine = statemachine self.statemachine = statemachine
self.observer = ephem.Observer() self.observer = ephem.Observer()
self.observer.lat = self.config.get('common','latitude') self.observer.lat = self.config.get('common','latitude')
self.observer.long = self.config.get('common','longitude') 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): def next_sun_rising(self):
return ephem.localtime(self.observer.next_rising(self.sun)) return ephem.localtime(self.observer.next_rising(self.sun))
def next_sun_setting(self): def next_sun_setting(self):
return ephem.localtime(self.observer.next_setting(self.sun)) return ephem.localtime(self.observer.next_setting(self.sun))
def update_sun_state(self, now=None): def update_sun_state(self, now=None):
next_rising = ephem.localtime(self.observer.next_rising(self.sun)) next_rising = ephem.localtime(self.observer.next_rising(self.sun))
next_setting = ephem.localtime(self.observer.next_setting(self.sun)) next_setting = ephem.localtime(self.observer.next_setting(self.sun))
if next_rising > next_setting: if next_rising > next_setting:
new_state = SUN_STATE_ABOVE_HORIZON new_state = SUN_STATE_ABOVE_HORIZON
next_change = next_setting next_change = next_setting
else: else:
new_state = SUN_STATE_BELOW_HORIZON new_state = SUN_STATE_BELOW_HORIZON
next_change = next_rising 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) 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))
# +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))

View File

@ -1,5 +1,5 @@
def ensure_list(parameter): def ensure_list(parameter):
return parameter if isinstance(parameter, list) else [parameter] return parameter if isinstance(parameter, list) else [parameter]
def matcher(subject, pattern): def matcher(subject, pattern):
return '*' in pattern or subject in pattern return '*' in pattern or subject in pattern