Reorganizing the code + sun set puts lights on
This commit is contained in:
parent
37e5840173
commit
b20bd6c9c0
9 changed files with 150 additions and 95 deletions
|
@ -1,34 +0,0 @@
|
||||||
from ConfigParser import SafeConfigParser
|
|
||||||
|
|
||||||
from app.StateMachine import StateMachine
|
|
||||||
from app.EventBus import EventBus
|
|
||||||
from app.Logging import EventLogger
|
|
||||||
|
|
||||||
class Dependencies:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.config = None
|
|
||||||
self.eventbus = None
|
|
||||||
self.statemachine = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_config(self):
|
|
||||||
if self.config is None:
|
|
||||||
self.config = SafeConfigParser()
|
|
||||||
self.config.read("home-assistant.conf")
|
|
||||||
|
|
||||||
return self.config
|
|
||||||
|
|
||||||
def get_event_bus(self):
|
|
||||||
if self.eventbus is None:
|
|
||||||
self.eventbus = EventBus()
|
|
||||||
#EventLogger(self.eventbus)
|
|
||||||
|
|
||||||
return self.eventbus
|
|
||||||
|
|
||||||
def get_state_machine(self):
|
|
||||||
if self.statemachine is None:
|
|
||||||
self.statemachine = StateMachine(self.get_event_bus())
|
|
||||||
|
|
||||||
return self.statemachine
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from threading import Thread, RLock
|
from threading import Thread, RLock
|
||||||
|
@ -19,7 +19,7 @@ class EventBus:
|
||||||
def run():
|
def run():
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
|
|
||||||
self.logger.info("[{}] {} event received: {}".format(time.strftime("%H:%M:%S"), event.eventType, event.data))
|
self.logger.info("{} event received: {}".format(event.eventType, event.data))
|
||||||
|
|
||||||
for callback in chain(self.listeners[ALL_EVENTS], self.listeners[event.eventType]):
|
for callback in chain(self.listeners[ALL_EVENTS], self.listeners[event.eventType]):
|
||||||
callback(event)
|
callback(event)
|
||||||
|
|
96
app/HomeAssistant.py
Normal file
96
app/HomeAssistant.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
from ConfigParser import SafeConfigParser
|
||||||
|
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
|
||||||
|
from app.observer.TomatoDeviceScanner import TomatoDeviceScanner
|
||||||
|
from app.observer.Timer import Timer
|
||||||
|
|
||||||
|
from app.actor.HueTrigger import HueTrigger
|
||||||
|
|
||||||
|
class HomeAssistant:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.config = None
|
||||||
|
self.eventbus = None
|
||||||
|
self.statemachine = None
|
||||||
|
|
||||||
|
self.timer = None
|
||||||
|
self.weatherwatcher = None
|
||||||
|
self.devicetracker = None
|
||||||
|
|
||||||
|
self.huetrigger = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
if self.config is None:
|
||||||
|
self.config = SafeConfigParser()
|
||||||
|
self.config.read("home-assistant.conf")
|
||||||
|
|
||||||
|
return self.config
|
||||||
|
|
||||||
|
|
||||||
|
def get_event_bus(self):
|
||||||
|
if self.eventbus is None:
|
||||||
|
self.eventbus = EventBus()
|
||||||
|
|
||||||
|
return self.eventbus
|
||||||
|
|
||||||
|
|
||||||
|
def get_state_machine(self):
|
||||||
|
if self.statemachine is None:
|
||||||
|
self.statemachine = StateMachine(self.get_event_bus())
|
||||||
|
|
||||||
|
return self.statemachine
|
||||||
|
|
||||||
|
|
||||||
|
def setup_timer(self):
|
||||||
|
if self.timer is None:
|
||||||
|
self.timer = Timer(self.get_event_bus())
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
self.huetrigger = HueTrigger(self.get_config(), self.get_event_bus(), self.get_state_machine(), self.devicetracker)
|
||||||
|
|
||||||
|
return self.huetrigger
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from app.EventBus import ALL_EVENTS
|
|
||||||
|
|
||||||
|
|
||||||
class EventLogger:
|
|
||||||
|
|
||||||
def __init__(self, eventbus):
|
|
||||||
eventbus.listen(ALL_EVENTS, self.log)
|
|
||||||
self.logger = logging.getLogger("EventLogger")
|
|
||||||
|
|
||||||
def log(self, event):
|
|
||||||
self.logger.info("[{}] {} event received: {}".format(time.strftime("%H:%M:%S"), event.eventType, event.data))
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
|
|
||||||
from phue import Bridge
|
from phue import Bridge
|
||||||
|
|
||||||
from app.observer.WeatherWatcher import STATE_CATEGORY_SUN, SOLAR_STATE_BELOW_HORIZON
|
from app.observer.WeatherWatcher import STATE_CATEGORY_SUN, SOLAR_STATE_BELOW_HORIZON, SOLAR_STATE_ABOVE_HORIZON
|
||||||
from app.StateMachine import track_state_change
|
from app.StateMachine import track_state_change
|
||||||
from app.observer.DeviceTracker import STATE_CATEGORY_ALL_DEVICES, STATE_H, STATE_H5, STATE_NH
|
from app.observer.DeviceTracker import STATE_CATEGORY_ALL_DEVICES, STATE_H, STATE_H5, STATE_NH
|
||||||
|
|
||||||
|
@ -14,25 +14,51 @@ class HueTrigger:
|
||||||
self.logger = logging.getLogger("HueTrigger")
|
self.logger = logging.getLogger("HueTrigger")
|
||||||
|
|
||||||
for category in device_tracker.device_state_categories():
|
for category in device_tracker.device_state_categories():
|
||||||
track_state_change(eventbus, category, '*', STATE_H, self.handle_state_change)
|
track_state_change(eventbus, category, '*', STATE_H, self.handle_device_state_change)
|
||||||
|
|
||||||
# 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_H,STATE_H5], STATE_NH, self.handle_state_change)
|
track_state_change(eventbus, STATE_CATEGORY_ALL_DEVICES, [STATE_H,STATE_H5], STATE_NH, self.handle_device_state_change)
|
||||||
|
|
||||||
def handle_state_change(self, category, oldState, newState):
|
# Track when sun sets
|
||||||
# print "Hue Trigger - {}: {}->{}".format(category, oldState, newState)
|
track_state_change(eventbus, STATE_CATEGORY_SUN, SOLAR_STATE_ABOVE_HORIZON, SOLAR_STATE_BELOW_HORIZON, self.handle_sun_state_change)
|
||||||
|
|
||||||
|
|
||||||
|
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) == SOLAR_STATE_BELOW_HORIZON
|
light_needed = not lights_are_on and self.statemachine.get_state(STATE_CATEGORY_SUN) == SOLAR_STATE_BELOW_HORIZON
|
||||||
|
|
||||||
if newState == STATE_H and light_needed:
|
return lights_are_on, light_needed
|
||||||
self.logger.info("Home coming event for {}. Turning lights on".format(category))
|
|
||||||
|
|
||||||
|
def turn_lights_on(self):
|
||||||
self.bridge.set_light([1,2,3], 'on', True)
|
self.bridge.set_light([1,2,3], 'on', True)
|
||||||
self.bridge.set_light([1,2,3], 'xy', [0.4595, 0.4105])
|
self.bridge.set_light([1,2,3], 'xy', [0.4595, 0.4105])
|
||||||
|
|
||||||
elif newState == STATE_NH and lights_are_on:
|
|
||||||
self.logger.info("Everyone has left. Turning lights off")
|
def turn_lights_off(self):
|
||||||
self.bridge.set_light([1,2,3], 'on', False)
|
self.bridge.set_light([1,2,3], 'on', False)
|
||||||
|
|
||||||
|
|
||||||
|
# If sun sets, the lights are
|
||||||
|
def handle_sun_state_change(self, category, oldState, newState):
|
||||||
|
lights_are_on, light_needed = self.get_lights_status()
|
||||||
|
|
||||||
|
if light_needed and self.statemachine.get_state(STATE_CATEGORY_ALL_DEVICES) in [STATE_H, STATE_H5]:
|
||||||
|
self.turn_lights_on()
|
||||||
|
|
||||||
|
|
||||||
|
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_H 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_NH and lights_are_on:
|
||||||
|
self.logger.info("Everyone has left. Turning lights off")
|
||||||
|
self.turn_lights_off()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ class TomatoDeviceScanner:
|
||||||
for mac in [mac for mac in known_devices if known_devices[mac]['track'] == '1']:
|
for mac in [mac for mac in known_devices if known_devices[mac]['track'] == '1']:
|
||||||
self.devices_to_track[mac] = known_devices[mac]['name']
|
self.devices_to_track[mac] = known_devices[mac]['name']
|
||||||
|
|
||||||
# Doesn't go together with exec: unqualified exec is not allowed in function '__init__' it contains a nested function with free variables
|
# 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'}
|
# self.devices_to_track = {mac: known_devices[mac]['name'] for mac in known_devices if known_devices[mac]['track'] == '1'}
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,10 +43,15 @@ class TomatoDeviceScanner:
|
||||||
self.logger.info("Scanning for new devices")
|
self.logger.info("Scanning for new devices")
|
||||||
|
|
||||||
# Query for new devices
|
# Query for new devices
|
||||||
|
try:
|
||||||
exec(self.tomato_request("devlist"))
|
exec(self.tomato_request("devlist"))
|
||||||
|
|
||||||
return [mac for iface, mac, rssi, tx, rx, quality, unknown_num in wldev]
|
return [mac for iface, mac, rssi, tx, rx, quality, unknown_num in wldev]
|
||||||
|
|
||||||
|
except:
|
||||||
|
self.logger.error("Scanning failed")
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
def tomato_request(self, action):
|
def tomato_request(self, action):
|
||||||
# Get router info
|
# Get router info
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from EventBus import ALL_EVENTS
|
|
||||||
|
|
||||||
def ensure_list(parameter):
|
def ensure_list(parameter):
|
||||||
return parameter if isinstance(parameter, list) else [parameter]
|
return parameter if isinstance(parameter, list) else [parameter]
|
||||||
|
|
||||||
|
|
35
start.py
35
start.py
|
@ -1,34 +1,11 @@
|
||||||
import time
|
from app.HomeAssistant import HomeAssistant
|
||||||
|
|
||||||
from app.Dependencies import Dependencies
|
|
||||||
|
|
||||||
from app.observer.WeatherWatcher import WeatherWatcher
|
|
||||||
from app.observer.DeviceTracker import DeviceTracker
|
|
||||||
from app.observer.TomatoDeviceScanner import TomatoDeviceScanner
|
from app.observer.TomatoDeviceScanner import TomatoDeviceScanner
|
||||||
from app.observer.Timer import Timer
|
|
||||||
|
|
||||||
from app.actor.HueTrigger import HueTrigger
|
ha = HomeAssistant()
|
||||||
|
|
||||||
deps = Dependencies()
|
ha.setup_weather_watcher()
|
||||||
|
ha.setup_device_tracker(TomatoDeviceScanner(ha.get_config()))
|
||||||
|
ha.setup_hue_trigger()
|
||||||
|
|
||||||
weather = WeatherWatcher(deps.get_config(), deps.get_event_bus(), deps.get_state_machine())
|
ha.start()
|
||||||
|
|
||||||
tomato = TomatoDeviceScanner(deps.get_config())
|
|
||||||
|
|
||||||
device_tracker = DeviceTracker(deps.get_event_bus(), deps.get_state_machine(), tomato)
|
|
||||||
|
|
||||||
HueTrigger(deps.get_config(), deps.get_event_bus(), deps.get_state_machine(), device_tracker)
|
|
||||||
|
|
||||||
|
|
||||||
timer = Timer(deps.get_event_bus())
|
|
||||||
timer.start()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
except:
|
|
||||||
print ""
|
|
||||||
print "Interrupt received. Wrapping up and quiting.."
|
|
||||||
timer.stop()
|
|
||||||
break
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue