From 5f4ddfe92c6922d3bb87ce6c418163f48426e44b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 20 Sep 2013 19:29:15 -0700 Subject: [PATCH] Code cleanup and new sun set transition --- app/DeviceTracker.py | 17 ++++++------ app/StateMachine.py | 8 ++++-- app/actor/HueTrigger.py | 59 ++++++++++++++++++++++++----------------- app/observer/Timer.py | 4 +-- 4 files changed, 50 insertions(+), 38 deletions(-) diff --git a/app/DeviceTracker.py b/app/DeviceTracker.py index 8ad5146d7cc..b8f4f401e25 100644 --- a/app/DeviceTracker.py +++ b/app/DeviceTracker.py @@ -9,8 +9,7 @@ STATE_DEVICE_DEFAULT = STATE_DEVICE_NOT_HOME # After how much time do we consider a device not home if # it does not show up on scans -# 70 seconds is to ensure 2 scans -TIME_SPAN_FOR_ERROR_IN_SCANNING = timedelta(seconds=70) +TIME_SPAN_FOR_ERROR_IN_SCANNING = timedelta(seconds=60) STATE_CATEGORY_ALL_DEVICES = 'device.alldevices' STATE_CATEGORY_DEVICE_FORMAT = 'device.{}' @@ -27,9 +26,9 @@ class DeviceTracker(object): 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 } + 'last_seen': default_last_seen, + 'category': STATE_CATEGORY_DEVICE_FORMAT.format(temp_devices_to_track[device]) } + for device in temp_devices_to_track } # Add categories to state machine statemachine.add_category(STATE_CATEGORY_ALL_DEVICES, STATE_DEVICE_DEFAULT) @@ -41,8 +40,7 @@ class DeviceTracker(object): def device_state_categories(self): - for device in self.devices_to_track: - yield self.devices_to_track[device]['category'] + return [self.devices_to_track[device]['category'] for device in self.devices_to_track] def set_state(self, device, state): @@ -53,7 +51,8 @@ class DeviceTracker(object): 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() 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: 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] all_devices_state = STATE_DEVICE_HOME if STATE_DEVICE_HOME in states_of_devices else STATE_DEVICE_NOT_HOME diff --git a/app/StateMachine.py b/app/StateMachine.py index 83685c32763..5c59c9b89ed 100644 --- a/app/StateMachine.py +++ b/app/StateMachine.py @@ -33,14 +33,18 @@ class StateMachine(object): 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): assert category in self.states, "Category does not exist: {}".format(category) 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 + return [(category, self.states[category].state, self.states[category].last_changed) for category in sorted(self.states.keys())] def track_state_change(eventbus, category, from_state, to_state, action): diff --git a/app/actor/HueTrigger.py b/app/actor/HueTrigger.py index ade2cad7c81..3a7952e08b7 100644 --- a/app/actor/HueTrigger.py +++ b/app/actor/HueTrigger.py @@ -1,5 +1,5 @@ import logging -from datetime import datetime, timedelta +from datetime import timedelta 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) +LIGHT_TRANSITION_TIME_HUE = 9000 # 1/10th seconds +LIGHT_TRANSITION_TIME = timedelta(seconds=LIGHT_TRANSITION_TIME_HUE/10) + class HueTrigger(object): def __init__(self, config, eventbus, statemachine, device_tracker, weather): 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) # 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) 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.is_state(STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON) 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} if transitiontime is not None: 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} if transitiontime is not None: 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): - # Schedule an event X minutes prior to sun setting - point_in_time = self.weather.next_sun_setting()-LIGHTS_TURNING_ON_BEFORE_SUN_SET_PERIOD + """The moment sun sets we want to have all the lights on. + 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 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.") - - # 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 turn_light_on_before_sunset(self, light_id=None): + """Helper function to turn on lights slowly if there are devices home and the light is not on yet.""" + 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) def handle_device_state_change(self, category, old_state, new_state): @@ -90,9 +99,9 @@ class HueTrigger(object): # 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() + self.turn_light_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() + self.turn_light_off() diff --git a/app/observer/Timer.py b/app/observer/Timer.py index c4af0249bd7..5defc2f8210 100644 --- a/app/observer/Timer.py +++ b/app/observer/Timer.py @@ -39,7 +39,7 @@ class Timer(threading.Thread): 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) 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']) - eventBus.listen(EVENT_TIME_CHANGED, listener) + eventbus.listen(EVENT_TIME_CHANGED, listener)