bug fixes

This commit is contained in:
Paulus Schoutsen 2013-09-19 14:07:49 -07:00
parent f3a093feda
commit 0ff00ae5e0
5 changed files with 74 additions and 54 deletions

View File

@ -3,7 +3,6 @@ import time
from app.StateMachine import StateMachine
from app.EventBus import EventBus
from app.Logging import EventLogger
from app.DeviceTracker import DeviceTracker
from app.observer.WeatherWatcher import WeatherWatcher
@ -72,7 +71,7 @@ class HomeAssistant:
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.huetrigger = HueTrigger(self.get_config(), self.get_event_bus(), self.get_state_machine(), self.devicetracker, self.setup_weather_watcher())
return self.huetrigger

View File

@ -1,15 +1,19 @@
import logging
from datetime import datetime
from datetime import datetime, timedelta
from phue import Bridge
from app.observer.WeatherWatcher import EVENT_PRE_SUN_SET_WARNING, STATE_CATEGORY_SUN, SOLAR_STATE_BELOW_HORIZON
from app.observer.WeatherWatcher import STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON, SUN_STATE_ABOVE_HORIZON
from app.StateMachine import track_state_change
from app.DeviceTracker import STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_HOME, STATE_DEVICE_NOT_HOME
LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD = timedelta(minutes=20)
class HueTrigger:
def __init__(self, config, eventbus, statemachine, device_tracker):
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()
@ -22,14 +26,17 @@ class HueTrigger:
# 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)
# Listen for when sun is about to set
eventbus.listen(EVENT_PRE_SUN_SET_WARNING, self.handle_sun_setting)
# 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()
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 == SOLAR_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
@ -52,16 +59,21 @@ class HueTrigger:
self.bridge.set_light([1,2,3], command)
def handle_sun_rising(self, event=None):
# 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)
# Gets called when darkness starts falling in, slowly turn on the lights
def handle_sun_setting(self, event):
def handle_sun_setting(self, now):
lights_are_on, light_needed = self.get_lights_status()
if light_needed 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.")
# We will start the lights now and by the time the sun sets
# the lights will be at full brightness
transitiontime = (event.data['sun_setting'] - datetime.now()).seconds * 10
transitiontime = (self.weather.next_sun_setting() - datetime.now()).seconds * 10
self.turn_lights_on(transitiontime)

View File

@ -1,21 +1,32 @@
import logging
import csv
import os
from datetime import datetime, timedelta
from threading import Lock
import requests
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
KNOWN_DEVICES_FILE = "tomato_known_devices.csv"
class TomatoDeviceScanner:
# self.logger
def __init__(self, config):
self.config = config
self.logger = logging.getLogger("TomatoDeviceScanner")
self.lock = Lock()
self.date_updated = None
self.last_results = None
# Read known devices
with open('tomato_known_devices.csv') as inp:
known_devices = { row['mac']: row for row in csv.DictReader(inp) }
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('tomato_known_devices.csv', 'a') as outp:
with open(KNOWN_DEVICES_FILE, 'a') as outp:
writer = csv.writer(outp)
# Query for new devices
@ -40,18 +51,24 @@ class TomatoDeviceScanner:
return self.devices_to_track
def scan_devices(self):
self.logger.info("Scanning for new devices")
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")
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]
except:
self.logger.error("Scanning failed")
# Query for new devices
try:
exec(self.tomato_request("devlist"))
return [mac for iface, mac, rssi, tx, rx, quality, unknown_num in wldev]
except:
self.logger.error("Scanning failed")
return []
self.lock.release()
return self.last_results
def tomato_request(self, action):
# Get router info

View File

@ -7,16 +7,10 @@ from app.EventBus import Event
from app.observer.Timer import track_time_change
PRE_SUN_SET_WARNING_TIME = 20 # minutes
STATE_CATEGORY_SUN = "weather.sun"
EVENT_PRE_SUN_SET_WARNING = "sun_set_soon"
STATE_CATEGORY_TEMPLATE_SOLAR = "solar.{}"
STATE_CATEGORY_SUN = STATE_CATEGORY_TEMPLATE_SOLAR.format("sun")
SOLAR_STATE_ABOVE_HORIZON = "above_horizon"
SOLAR_STATE_BELOW_HORIZON = "below_horizon"
SUN_STATE_ABOVE_HORIZON = "above_horizon"
SUN_STATE_BELOW_HORIZON = "below_horizon"
class WeatherWatcher:
def __init__(self, config, eventbus, statemachine):
@ -25,40 +19,39 @@ class WeatherWatcher:
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.sun = ephem.Sun()
statemachine.add_category(STATE_CATEGORY_SUN, SOLAR_STATE_BELOW_HORIZON)
self.update_sun_state()
def update_sun_state(self, now=datetime.now()):
self.update_solar_state(ephem.Sun(), STATE_CATEGORY_SUN, self.update_sun_state)
def next_sun_rising(self):
return ephem.localtime(self.observer.next_rising(self.sun))
def update_solar_state(self, solar_body, state_category, update_callback):
# We don't cache these objects because we use them so rarely
observer = ephem.Observer()
observer.lat = self.config.get('common','latitude')
observer.long = self.config.get('common','longitude')
def next_sun_setting(self):
return ephem.localtime(self.observer.next_setting(self.sun))
next_rising = ephem.localtime(observer.next_rising(solar_body))
next_setting = ephem.localtime(observer.next_setting(solar_body))
def update_sun_state(self, update_callback):
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 = SOLAR_STATE_ABOVE_HORIZON
new_state = SUN_STATE_ABOVE_HORIZON
next_change = next_setting
else:
new_state = SOLAR_STATE_BELOW_HORIZON
new_state = SUN_STATE_BELOW_HORIZON
next_change = next_rising
self.logger.info("Updating solar state for {} to {}. Next change: {}".format(state_category, new_state, next_change))
self.logger.info("Updating solar state for {} to {}. Next change: {}".format(STATE_CATEGORY_SUN, new_state, next_change))
self.statemachine.set_state(state_category, 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, update_callback, datetime=next_change + timedelta(seconds=10))
# If the sun is visible, schedule to fire an event X minutes before sun set
if solar_body.name == 'Sun' and new_state == SOLAR_STATE_ABOVE_HORIZON:
track_time_change(self.eventbus, lambda time: self.eventbus.fire(Event(EVENT_PRE_SUN_SET_WARNING, {'sun_setting':next_change})),
datetime=next_change - timedelta(minutes=PRE_SUN_SET_WARNING_TIME))

View File

@ -4,7 +4,6 @@ from app.observer.TomatoDeviceScanner import TomatoDeviceScanner
ha = HomeAssistant()
ha.setup_weather_watcher()
ha.setup_device_tracker(TomatoDeviceScanner(ha.get_config()))
ha.setup_hue_trigger()