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.StateMachine import StateMachine
from app.EventBus import EventBus from app.EventBus import EventBus
from app.Logging import EventLogger
from app.DeviceTracker import DeviceTracker from app.DeviceTracker import DeviceTracker
from app.observer.WeatherWatcher import WeatherWatcher from app.observer.WeatherWatcher import WeatherWatcher
@ -72,7 +71,7 @@ class HomeAssistant:
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.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

View File

@ -1,15 +1,19 @@
import logging import logging
from datetime import datetime from datetime import datetime, timedelta
from phue import Bridge 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.StateMachine import track_state_change
from app.DeviceTracker import STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_HOME, STATE_DEVICE_NOT_HOME 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: 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.statemachine = statemachine
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()
@ -22,14 +26,17 @@ class HueTrigger:
# 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)
# Listen for when sun is about to set # Track every time sun rises so we can schedule a time-based pre-sun set event
eventbus.listen(EVENT_PRE_SUN_SET_WARNING, self.handle_sun_setting) 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): 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 == 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 return lights_are_on, light_needed
@ -52,16 +59,21 @@ class HueTrigger:
self.bridge.set_light([1,2,3], command) 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 # 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() 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.") 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 = (event.data['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)

View File

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

View File

@ -7,16 +7,10 @@ from app.EventBus import Event
from app.observer.Timer import track_time_change 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" SUN_STATE_ABOVE_HORIZON = "above_horizon"
SUN_STATE_BELOW_HORIZON = "below_horizon"
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"
class WeatherWatcher: class WeatherWatcher:
def __init__(self, config, eventbus, statemachine): def __init__(self, config, eventbus, statemachine):
@ -25,40 +19,39 @@ class WeatherWatcher:
self.eventbus = eventbus self.eventbus = eventbus
self.statemachine = statemachine 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) statemachine.add_category(STATE_CATEGORY_SUN, SOLAR_STATE_BELOW_HORIZON)
self.update_sun_state() self.update_sun_state()
def update_sun_state(self, now=datetime.now()): def next_sun_rising(self):
self.update_solar_state(ephem.Sun(), STATE_CATEGORY_SUN, self.update_sun_state) return ephem.localtime(self.observer.next_rising(self.sun))
def update_solar_state(self, solar_body, state_category, update_callback): def next_sun_setting(self):
# We don't cache these objects because we use them so rarely return ephem.localtime(self.observer.next_setting(self.sun))
observer = ephem.Observer()
observer.lat = self.config.get('common','latitude')
observer.long = self.config.get('common','longitude')
next_rising = ephem.localtime(observer.next_rising(solar_body)) def update_sun_state(self, update_callback):
next_setting = ephem.localtime(observer.next_setting(solar_body)) 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: if next_rising > next_setting:
new_state = SOLAR_STATE_ABOVE_HORIZON new_state = SUN_STATE_ABOVE_HORIZON
next_change = next_setting next_change = next_setting
else: else:
new_state = SOLAR_STATE_BELOW_HORIZON new_state = SUN_STATE_BELOW_HORIZON
next_change = next_rising 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 # +10 seconds to be sure that the change has occured
track_time_change(self.eventbus, update_callback, datetime=next_change + timedelta(seconds=10)) 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 = HomeAssistant()
ha.setup_weather_watcher()
ha.setup_device_tracker(TomatoDeviceScanner(ha.get_config())) ha.setup_device_tracker(TomatoDeviceScanner(ha.get_config()))
ha.setup_hue_trigger() ha.setup_hue_trigger()