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.{}'
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)

View File

@ -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])

View File

@ -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

View File

@ -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("<table>")
write("<tr><th>Name</th><th>State</th><th>Last Changed</th></tr>")
write = self.wfile.write
for category, state, last_changed in self.server.statemachine.get_states():
categories.append(category)
# Describe state machine:
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("<br />Change state:<br />")
write("<form action='change_state' method='POST'>")
write("<select name='category'>")
write("<tr><td>{}</td><td>{}</td><td>{}</td></tr>".format(category, state, last_changed.strftime("%H:%M:%S %d-%m-%Y")))
for category in categories:
write("<option>{}</option>".format(category))
write("</table>")
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' />")
write("<input type='submit' value='set state' />")
write("</form>")
for category in categories:
write("<option>{}</option>".format(category))
else:
self.send_response(404)
write("</select>")
write("<input name='new_state' />")
write("<input type='submit' value='set state' />")
write("</form>")
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))
# Trigger a fake request to get the server to quit
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 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)

View File

@ -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()

View File

@ -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)

View File

@ -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
"""
else:
print ip
"""

View File

@ -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))

View File

@ -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