Code cleanup and new sun set transition

This commit is contained in:
Paulus Schoutsen 2013-09-20 19:29:15 -07:00
parent 9732bf99ac
commit 5f4ddfe92c
4 changed files with 50 additions and 38 deletions

View File

@ -9,8 +9,7 @@ STATE_DEVICE_DEFAULT = STATE_DEVICE_NOT_HOME
# After how much time do we consider a device not home if # After how much time do we consider a device not home if
# it does not show up on scans # it does not show up on scans
# 70 seconds is to ensure 2 scans TIME_SPAN_FOR_ERROR_IN_SCANNING = timedelta(seconds=60)
TIME_SPAN_FOR_ERROR_IN_SCANNING = timedelta(seconds=70)
STATE_CATEGORY_ALL_DEVICES = 'device.alldevices' STATE_CATEGORY_ALL_DEVICES = 'device.alldevices'
STATE_CATEGORY_DEVICE_FORMAT = 'device.{}' STATE_CATEGORY_DEVICE_FORMAT = 'device.{}'
@ -27,9 +26,9 @@ class DeviceTracker(object):
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 }
# Add categories to state machine # Add categories to state machine
statemachine.add_category(STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_DEFAULT) statemachine.add_category(STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_DEFAULT)
@ -41,8 +40,7 @@ class DeviceTracker(object):
def device_state_categories(self): def device_state_categories(self):
for device in self.devices_to_track: return [self.devices_to_track[device]['category'] for device in self.devices_to_track]
yield self.devices_to_track[device]['category']
def set_state(self, device, state): def set_state(self, device, state):
@ -53,7 +51,8 @@ class DeviceTracker(object):
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:
@ -71,7 +70,7 @@ class DeviceTracker(object):
if datetime.now() - self.devices_to_track[device]['last_seen'] > TIME_SPAN_FOR_ERROR_IN_SCANNING: 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 # Get the currently used statuses
states_of_devices = [self.statemachine.get_state(self.devices_to_track[device]['category']).state for device in self.devices_to_track] 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 all_devices_state = STATE_DEVICE_HOME if STATE_DEVICE_HOME in states_of_devices else STATE_DEVICE_NOT_HOME

View File

@ -33,14 +33,18 @@ class StateMachine(object):
self.lock.release() self.lock.release()
def is_state(self, category, state):
assert category in self.states, "Category does not exist: {}".format(category)
return self.get_state(category).state == state
def get_state(self, category): def get_state(self, category):
assert category in self.states, "Category does not exist: {}".format(category) assert category in self.states, "Category does not exist: {}".format(category)
return self.states[category] return self.states[category]
def get_states(self): def get_states(self):
for category in sorted(self.states.keys()): return [(category, self.states[category].state, self.states[category].last_changed) for category in sorted(self.states.keys())]
yield category, self.states[category].state, self.states[category].last_changed
def track_state_change(eventbus, category, from_state, to_state, action): def track_state_change(eventbus, category, from_state, to_state, action):

View File

@ -1,5 +1,5 @@
import logging import logging
from datetime import datetime, timedelta from datetime import timedelta
from phue import Bridge from phue import Bridge
@ -10,6 +10,9 @@ from app.observer.Timer import track_time_change
LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD = timedelta(minutes=15) LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD = timedelta(minutes=15)
LIGHT_TRANSITION_TIME_HUE = 9000 # 1/10th seconds
LIGHT_TRANSITION_TIME = timedelta(seconds=LIGHT_TRANSITION_TIME_HUE/10)
class HueTrigger(object): 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
@ -31,57 +34,63 @@ class HueTrigger(object):
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).state == SUN_STATE_ABOVE_HORIZON: if statemachine.is_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.is_state(STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON)
return lights_are_on, light_needed return lights_are_on, light_needed
def turn_light_on(self, light_id=None, transitiontime=None):
if light_id is None:
light_id = [light.light_id for light in self.lights]
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(light_id, command)
def turn_lights_off(self, transitiontime=None): def turn_light_off(self, light_id=None, transitiontime=None):
if light_id is None:
light_id = [light.light_id for light in self.lights]
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(light_id, command)
def handle_sun_rising(self, category, old_state, new_state): def handle_sun_rising(self, category, old_state, new_state):
# Schedule an event X minutes prior to sun setting """The moment sun sets we want to have all the lights on.
point_in_time = self.weather.next_sun_setting()-LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD We will schedule to have each light start after one another
and slowly transition in."""
self.logger.info("Will put lights on at {} to compensate less light from setting sun.".format(point_in_time)) start_point = self.weather.next_sun_setting() - LIGHT_TRANSITION_TIME * len(self.lights)
track_time_change(self.eventbus, self.handle_sun_setting, point_in_time=point_in_time) # Lambda can keep track of function parameters, not from local parameters
# If we put the lambda directly in the below statement only the last light
# would be turned on..
def turn_on(light_id):
return lambda now: self.turn_light_on_before_sunset(light_id)
for index, light in enumerate(self.lights):
track_time_change(self.eventbus, turn_on(light.light_id),
point_in_time=start_point + index * LIGHT_TRANSITION_TIME)
# Gets called when darkness starts falling in, slowly turn on the lights def turn_light_on_before_sunset(self, light_id=None):
def handle_sun_setting(self, now): """Helper function to turn on lights slowly if there are devices home and the light is not on yet."""
lights_are_on, light_needed = self.get_lights_status() if self.statemachine.is_state(STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_HOME) and not self.bridge.get_light(light_id, 'on'):
self.turn_light_on(light_id, LIGHT_TRANSITION_TIME_HUE)
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
self.turn_lights_on(transitiontime)
def handle_device_state_change(self, category, old_state, new_state): def handle_device_state_change(self, category, old_state, new_state):
@ -90,9 +99,9 @@ class HueTrigger(object):
# Specific device came home ? # Specific device came home ?
if category != STATE_CATEGORY_ALL_DEVICES and new_state.state == STATE_DEVICE_HOME and light_needed: 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.logger.info("Home coming event for {}. Turning lights on".format(category))
self.turn_lights_on() self.turn_light_on()
# Did all devices leave the house? # Did all devices leave the house?
elif category == STATE_CATEGORY_ALL_DEVICES and new_state.state == STATE_DEVICE_NOT_HOME and lights_are_on: 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.logger.info("Everyone has left. Turning lights off")
self.turn_lights_off() self.turn_light_off()

View File

@ -39,7 +39,7 @@ class Timer(threading.Thread):
break break
def track_time_change(eventBus, action, year='*', month='*', day='*', hour='*', minute='*', second='*', point_in_time=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)
@ -60,4 +60,4 @@ def track_time_change(eventBus, action, year='*', month='*', day='*', hour='*',
action(event.data['now']) action(event.data['now'])
eventBus.listen(EVENT_TIME_CHANGED, listener) eventbus.listen(EVENT_TIME_CHANGED, listener)