Initial commit
commit
d55e4d53cc
|
@ -0,0 +1,53 @@
|
|||
home-assistant.conf
|
||||
tomato_known_devices.csv
|
||||
|
||||
# Hide sublime text stuff
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Hide some OS X stuff
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
|
||||
# GITHUB Proposed Python stuff:
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
nosetests.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
|
@ -0,0 +1,46 @@
|
|||
Home Assistant
|
||||
==============
|
||||
|
||||
Home Assistant is a framework I wrote in Python to automate switching the lights on and off based on devices at home and the position of the sun.
|
||||
|
||||
It is currently able to do the following things:
|
||||
* Turn on the lights when one of the tracked devices gets home
|
||||
* Turn off the lights when everybody leaves home
|
||||
* Turn on the lights when the sun sets and one of the tracked devices is home (in progress)
|
||||
|
||||
It currently works with any wireless router with Tomato firmware and Philips Hue lightning system. The system is built modular so support for other wireless routers or other actions can be implemented easily.
|
||||
|
||||
Installation instructions
|
||||
-------------------------
|
||||
|
||||
* install python modules [PyEphem](http://rhodesmill.org/pyephem/), [Requests](http://python-requests.org) and [PHue](https://github.com/studioimaginaire/phue)
|
||||
* Clone the repository `git clone https://github.com/balloob/home-assistant.git`
|
||||
* Copy home-assistant.conf.default to home-assistant.conf and adjust the config values to match your setup
|
||||
* For Tomato you will have to not only setup your host, username and password but also a http_id. This one can be found by inspecting your request to the tomato server.
|
||||
* Setup PHue by running [this example script](https://github.com/studioimaginaire/phue/blob/master/examples/random_colors.py) with `b.connect()` uncommented
|
||||
* The first time the script will start it will create a file called `tomato_known_devices.csv` which will contain the detected devices. Adjust the track variable for the devices you want the script to act on and restart the script.
|
||||
|
||||
Done. Start it now by running `python start.py`
|
||||
|
||||
Home Assistent Architecture and how to customize
|
||||
------------------------------------------------
|
||||
|
||||
Home Assistent has been built from the ground up with extensibility and modularity in mind. It should be easy to swap in a different device tracker if I would move away from using a Tomato based firmware for my router for example. That is why all of Home Assistant has been built up on top of an event bus.
|
||||
|
||||
Different modules are able to fire and listen for specific events. On top of this is a state machine that allows modules to track the state of different things. Each device that is being tracked will have a state. Either home, home for 5 minutes, not home or not home for 5 minutes. On top of that there is also a state that is a combined state of all tracked devices.
|
||||
|
||||
This allows us to implement simple business rules to easily customize or extend functionality:
|
||||
|
||||
In the event that the state of device 'Paulus Nexus 4' changes to the 'Home' state:
|
||||
If the sun has set and the lights are not on:
|
||||
Turn on the lights
|
||||
|
||||
In the event that the combined state of all tracked devices changes to 'Not Home':
|
||||
If the lights are on:
|
||||
Turn off the lights
|
||||
|
||||
In the event of the sun setting:
|
||||
If the lights are off and the combined state of all tracked device is either 'Home' or 'Home for 5+ minutes':
|
||||
Turn on the lights
|
||||
|
||||
These rules are currently implemented in the file [HueTrigger.py](https://github.com/balloob/home-assistant/blob/master/app/actor/HueTrigger.py).
|
|
@ -0,0 +1,34 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import logging
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
from threading import Thread, RLock
|
||||
|
||||
ALL_EVENTS = '*'
|
||||
|
||||
class EventBus:
|
||||
def __init__(self):
|
||||
self.listeners = defaultdict(list)
|
||||
self.lock = RLock()
|
||||
self.logger =logging.getLogger("EventBus")
|
||||
|
||||
def fire(self, event):
|
||||
assert isinstance(event, Event), "event needs to be an instance of Event"
|
||||
|
||||
# We dont want the eventbus to be blocking, run in a thread
|
||||
def run():
|
||||
self.lock.acquire()
|
||||
|
||||
self.logger.info("[{}] {} event received: {}".format(time.strftime("%H:%M:%S"), event.eventType, event.data))
|
||||
|
||||
for callback in chain(self.listeners[ALL_EVENTS], self.listeners[event.eventType]):
|
||||
callback(event)
|
||||
|
||||
if event.removeListener:
|
||||
if callback in self.listeners[ALL_EVENTS]:
|
||||
self.listeners[ALL_EVENTS].remove(callback)
|
||||
|
||||
if callback in self.listeners[event.eventType]:
|
||||
self.listeners[event.eventType].remove(callback)
|
||||
|
||||
event.removeListener = False
|
||||
|
||||
if event.stopPropegating:
|
||||
break
|
||||
|
||||
self.lock.release()
|
||||
|
||||
Thread(target=run).start()
|
||||
|
||||
def listen(self, event_type, callback):
|
||||
self.lock.acquire()
|
||||
|
||||
self.listeners[event_type].append(callback)
|
||||
|
||||
self.logger.info("New listener added for event {}. Total: {}".format(event_type, len(self.listeners[event_type])))
|
||||
|
||||
self.lock.release()
|
||||
|
||||
|
||||
class Event:
|
||||
def __init__(self, eventType, data):
|
||||
self.eventType = eventType
|
||||
self.data = data
|
||||
self.stopPropegating = False
|
||||
self.removeListener = False
|
||||
|
||||
def __str__(self):
|
||||
return str([self.eventType, self.data])
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
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))
|
|
@ -0,0 +1,53 @@
|
|||
from collections import defaultdict
|
||||
from threading import RLock
|
||||
|
||||
from app.EventBus import Event
|
||||
from app.util import ensure_list, matcher
|
||||
|
||||
EVENT_STATE_CHANGED = "state_changed"
|
||||
|
||||
class StateMachine:
|
||||
|
||||
def __init__(self, eventBus):
|
||||
self.states = dict()
|
||||
self.eventBus = eventBus
|
||||
self.lock = RLock()
|
||||
|
||||
def add_category(self, category, initialState):
|
||||
self.states[category] = initialState
|
||||
|
||||
def set_state(self, category, newState):
|
||||
self.lock.acquire()
|
||||
|
||||
assert category in self.states, "Category does not exist: {}".format(category)
|
||||
|
||||
oldState = self.states[category]
|
||||
|
||||
if oldState != newState:
|
||||
self.states[category] = newState
|
||||
|
||||
self.eventBus.fire(Event(EVENT_STATE_CHANGED, {'category':category, 'oldState':oldState, 'newState':newState}))
|
||||
|
||||
self.lock.release()
|
||||
|
||||
def get_state(self, category):
|
||||
assert category in self.states, "Category does not exist: {}".format(category)
|
||||
|
||||
return self.states[category]
|
||||
|
||||
|
||||
def track_state_change(eventBus, category, fromState, toState, action):
|
||||
fromState = ensure_list(fromState)
|
||||
toState = ensure_list(toState)
|
||||
|
||||
def listener(event):
|
||||
assert isinstance(event, Event), "event needs to be of Event type"
|
||||
|
||||
if category == event.data['category'] and \
|
||||
matcher(event.data['oldState'], fromState) and \
|
||||
matcher(event.data['newState'], toState):
|
||||
|
||||
action(event.data['category'], event.data['oldState'], event.data['newState'])
|
||||
|
||||
eventBus.listen(EVENT_STATE_CHANGED, listener)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import logging
|
||||
|
||||
from phue import Bridge
|
||||
|
||||
from app.observer.WeatherWatcher import STATE_CATEGORY_SUN, SOLAR_STATE_BELOW_HORIZON
|
||||
from app.StateMachine import track_state_change
|
||||
from app.observer.DeviceTracker import STATE_CATEGORY_ALL_DEVICES, STATE_H, STATE_H5, STATE_NH
|
||||
|
||||
class HueTrigger:
|
||||
def __init__(self, config, eventbus, statemachine, device_tracker):
|
||||
self.statemachine = statemachine
|
||||
self.bridge = Bridge(config.get("hue","host"))
|
||||
self.lights = self.bridge.get_light_objects()
|
||||
self.logger = logging.getLogger("HueTrigger")
|
||||
|
||||
for category in device_tracker.device_state_categories():
|
||||
track_state_change(eventbus, category, '*', STATE_H, self.handle_state_change)
|
||||
|
||||
# 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)
|
||||
|
||||
def handle_state_change(self, category, oldState, newState):
|
||||
# print "Hue Trigger - {}: {}->{}".format(category, oldState, newState)
|
||||
|
||||
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
|
||||
|
||||
if newState == STATE_H and light_needed:
|
||||
self.logger.info("Home coming event for {}. Turning lights on".format(category))
|
||||
self.bridge.set_light([1,2,3], 'on', True)
|
||||
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")
|
||||
self.bridge.set_light([1,2,3], 'on', False)
|
||||
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from app.observer.Timer import track_time_change
|
||||
|
||||
STATE_NH = 'NH'
|
||||
STATE_NH5 = 'NH5'
|
||||
STATE_H = 'H'
|
||||
STATE_H5 = 'H5'
|
||||
|
||||
STATE_DEFAULT = STATE_NH5
|
||||
|
||||
# After how much time will we switch form NH to NH5 and H to H5 ?
|
||||
TIME_SPAN_FOR_EXTRA_STATE = timedelta(minutes=5)
|
||||
|
||||
# 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)
|
||||
|
||||
STATE_CATEGORY_ALL_DEVICES = 'device.alldevices'
|
||||
STATE_CATEGORY_DEVICE_FORMAT = 'device.{}'
|
||||
|
||||
|
||||
|
||||
class DeviceTracker:
|
||||
|
||||
def __init__(self, eventbus, statemachine, device_scanner):
|
||||
self.statemachine = statemachine
|
||||
self.eventbus = eventbus
|
||||
self.device_scanner = device_scanner
|
||||
|
||||
default_last_seen = datetime(1990, 1, 1)
|
||||
now = datetime.now()
|
||||
|
||||
temp_devices_to_track = device_scanner.get_devices_to_track()
|
||||
|
||||
self.devices_to_track = { device: { 'name': temp_devices_to_track[device],
|
||||
'state': STATE_DEFAULT,
|
||||
'last_seen': default_last_seen,
|
||||
'state_changed': now }
|
||||
for device in temp_devices_to_track }
|
||||
|
||||
self.all_devices_state = STATE_DEFAULT
|
||||
self.all_devices_state_changed = datetime.now()
|
||||
|
||||
# Add categories to state machine
|
||||
statemachine.add_category(STATE_CATEGORY_ALL_DEVICES, STATE_DEFAULT)
|
||||
|
||||
for device in self.devices_to_track:
|
||||
self.statemachine.add_category(STATE_CATEGORY_DEVICE_FORMAT.format(self.devices_to_track[device]['name']), STATE_DEFAULT)
|
||||
|
||||
|
||||
|
||||
track_time_change(eventbus, lambda time: self.update_devices(device_scanner.scan_devices(time)))
|
||||
|
||||
|
||||
|
||||
def device_state_categories(self):
|
||||
for device in self.devices_to_track:
|
||||
yield STATE_CATEGORY_DEVICE_FORMAT.format(self.devices_to_track[device]['name'])
|
||||
|
||||
|
||||
def set_state(self, device, state):
|
||||
if self.devices_to_track[device]['state'] != state:
|
||||
self.devices_to_track[device]['state'] = state
|
||||
self.devices_to_track[device]['state_changed'] = datetime.now()
|
||||
|
||||
if state in [STATE_H]:
|
||||
self.devices_to_track[device]['last_seen'] = self.devices_to_track[device]['state_changed']
|
||||
|
||||
self.statemachine.set_state(STATE_CATEGORY_DEVICE_FORMAT.format(self.devices_to_track[device]['name']), state)
|
||||
|
||||
|
||||
def update_devices(self, found_devices):
|
||||
temp_tracking_devices = self.devices_to_track.keys()
|
||||
|
||||
for device in found_devices:
|
||||
# Are we tracking this device?
|
||||
if device in temp_tracking_devices:
|
||||
temp_tracking_devices.remove(device)
|
||||
|
||||
# If home, check if for 5+ minutes, then change state to H5
|
||||
if self.devices_to_track[device]['state'] == STATE_H and \
|
||||
datetime.now() - self.devices_to_track[device]['state_changed'] > TIME_SPAN_FOR_EXTRA_STATE:
|
||||
|
||||
self.set_state(device, STATE_H5)
|
||||
|
||||
elif not self.devices_to_track[device]['state'] in [STATE_H, STATE_H5]:
|
||||
self.set_state(device, STATE_H)
|
||||
|
||||
# For all devices we did not find, set state to NH
|
||||
# But only if they have been gone for longer then the error time span
|
||||
# Because we do not want to have stuff happening when the device does
|
||||
# not show up for 1 scan beacuse of reboot etc
|
||||
for device in temp_tracking_devices:
|
||||
if self.devices_to_track[device]['state'] in [STATE_H, STATE_H5] and \
|
||||
datetime.now() - self.devices_to_track[device]['last_seen'] > TIME_SPAN_FOR_ERROR_IN_SCANNING:
|
||||
|
||||
self.set_state(device, STATE_NH)
|
||||
|
||||
elif self.devices_to_track[device]['state'] == STATE_NH and \
|
||||
datetime.now() - self.devices_to_track[device]['last_seen'] > TIME_SPAN_FOR_EXTRA_STATE:
|
||||
|
||||
self.set_state(device, STATE_NH5)
|
||||
|
||||
|
||||
# Get the set of currently used statuses
|
||||
states_of_devices = set( [self.devices_to_track[device]['state'] for device in self.devices_to_track] )
|
||||
|
||||
# If there is only one status in use, that is the status of all devices
|
||||
if len(states_of_devices) == 1:
|
||||
self.all_devices_state = states_of_devices.pop()
|
||||
|
||||
# Else if there is atleast 1 device home, the status is HOME
|
||||
elif STATE_H in states_of_devices or STATE_H5 in states_of_devices:
|
||||
self.all_devices_state = STATE_H
|
||||
|
||||
# Else status is not home
|
||||
else:
|
||||
self.all_devices_state = STATE_NH
|
||||
|
||||
self.statemachine.set_state(STATE_CATEGORY_ALL_DEVICES, self.all_devices_state)
|
|
@ -0,0 +1,64 @@
|
|||
from datetime import datetime, timedelta
|
||||
import threading
|
||||
import time
|
||||
|
||||
from app.EventBus import Event
|
||||
from app.util import ensure_list, matcher
|
||||
|
||||
TIME_INTERVAL = 10 # seconds
|
||||
|
||||
assert 60 % TIME_INTERVAL == 0, "60 % TIME_INTERVAL should be 0!"
|
||||
|
||||
EVENT_TIME_CHANGED = "time_changed"
|
||||
|
||||
class Timer(threading.Thread):
|
||||
def __init__(self, eventbus):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self.eventbus = eventbus
|
||||
self._stop = threading.Event()
|
||||
|
||||
def stop(self):
|
||||
self._stop.set()
|
||||
|
||||
def run(self):
|
||||
now = datetime.now()
|
||||
|
||||
while True:
|
||||
if self._stop.isSet():
|
||||
break
|
||||
|
||||
self.eventbus.fire(Event(EVENT_TIME_CHANGED, {'now':now}))
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
if self._stop.isSet() or now.second % TIME_INTERVAL == 0:
|
||||
break
|
||||
|
||||
|
||||
def track_time_change(eventBus, action, year='*', month='*', day='*', hour='*', minute='*', second='*', datetime=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)
|
||||
|
||||
def listener(event):
|
||||
assert isinstance(event, Event), "event needs to be of Event type"
|
||||
|
||||
if (datetime is not None and event.data['now'] > datetime) or \
|
||||
datetime is None and \
|
||||
matcher(event.data['now'].year, year) and \
|
||||
matcher(event.data['now'].month, month) and \
|
||||
matcher(event.data['now'].day, day) and \
|
||||
matcher(event.data['now'].hour, hour) and \
|
||||
matcher(event.data['now'].minute, minute) and \
|
||||
matcher(event.data['now'].second, second):
|
||||
|
||||
# datetime are exact points in time so we always remove it after fire
|
||||
event.removeListener = listen_once or datetime is not None
|
||||
|
||||
action(event.data['now'])
|
||||
|
||||
eventBus.listen(EVENT_TIME_CHANGED, listener)
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import logging
|
||||
import csv
|
||||
|
||||
import requests
|
||||
|
||||
class TomatoDeviceScanner:
|
||||
# self.logger
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.logger = logging.getLogger("TomatoDeviceScanner")
|
||||
|
||||
# Read known devices
|
||||
with open('tomato_known_devices.csv') 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:
|
||||
writer = csv.writer(outp)
|
||||
|
||||
# Query for new devices
|
||||
exec(self.tomato_request("devlist"))
|
||||
|
||||
for name, _, mac, _ in dhcpd_lease:
|
||||
if mac not in known_devices:
|
||||
writer.writerow((mac, name, 0))
|
||||
|
||||
# Create a dict with ID: NAME of the devices to track
|
||||
self.devices_to_track = dict()
|
||||
|
||||
for mac in [mac for mac in known_devices if known_devices[mac]['track'] == '1']:
|
||||
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
|
||||
# self.devices_to_track = {mac: known_devices[mac]['name'] for mac in known_devices if known_devices[mac]['track'] == '1'}
|
||||
|
||||
|
||||
def get_devices_to_track(self):
|
||||
return self.devices_to_track
|
||||
|
||||
def scan_devices(self, triggered_time):
|
||||
self.logger.info("Scanning for new devices")
|
||||
|
||||
# Query for new devices
|
||||
exec(self.tomato_request("devlist"))
|
||||
|
||||
return [mac for iface, mac, rssi, tx, rx, quality, unknown_num in wldev]
|
||||
|
||||
|
||||
def tomato_request(self, action):
|
||||
# Get router info
|
||||
r = requests.post('http://{}/update.cgi'.format(self.config.get('tomato','host')),
|
||||
data={'_http_id':self.config.get('tomato','http_id'), 'exec':action},
|
||||
auth=requests.auth.HTTPBasicAuth(self.config.get('tomato','username'), self.config.get('tomato','password')))
|
||||
|
||||
return r.text
|
||||
|
||||
|
||||
|
||||
"""
|
||||
for ip, mac, iface in arplist:
|
||||
pass
|
||||
|
||||
# print wlnoise
|
||||
|
||||
# print dhcpd_static
|
||||
|
||||
for iface, mac, rssi, tx, rx, quality, unknown_num in wldev:
|
||||
print mac, quality
|
||||
|
||||
print ""
|
||||
|
||||
for name, ip, mac, lease in dhcpd_lease:
|
||||
if name:
|
||||
print name, ip
|
||||
|
||||
else:
|
||||
print ip
|
||||
"""
|
|
@ -0,0 +1,51 @@
|
|||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import ephem
|
||||
|
||||
from app.observer.Timer import track_time_change
|
||||
|
||||
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:
|
||||
def __init__(self, config, eventbus, statemachine):
|
||||
self.logger = logging.getLogger("WeatherWatcher")
|
||||
self.config = config
|
||||
self.eventbus = eventbus
|
||||
self.statemachine = statemachine
|
||||
|
||||
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 update_solar_state(self, solar_object, 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')
|
||||
|
||||
next_rising = ephem.localtime(observer.next_rising(solar_object))
|
||||
next_setting = ephem.localtime(observer.next_setting(solar_object))
|
||||
|
||||
if next_rising > next_setting:
|
||||
new_state = SOLAR_STATE_ABOVE_HORIZON
|
||||
next_change = next_setting
|
||||
|
||||
else:
|
||||
new_state = SOLAR_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.statemachine.set_state(state_category, 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))
|
|
@ -0,0 +1,7 @@
|
|||
from EventBus import ALL_EVENTS
|
||||
|
||||
def ensure_list(parameter):
|
||||
return parameter if isinstance(parameter, list) else [parameter]
|
||||
|
||||
def matcher(subject, pattern):
|
||||
return '*' in pattern or subject in pattern
|
|
@ -0,0 +1,12 @@
|
|||
[common]
|
||||
latitude=32.87336
|
||||
longitude=-117.22743
|
||||
|
||||
[tomato]
|
||||
host=192.168.1.1
|
||||
username=admin
|
||||
password=PASSWORD
|
||||
http_id=aaaaaaaaaaaaaaa
|
||||
|
||||
[hue]
|
||||
host=192.168.1.107
|
|
@ -0,0 +1,34 @@
|
|||
import time
|
||||
|
||||
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.Timer import Timer
|
||||
|
||||
from app.actor.HueTrigger import HueTrigger
|
||||
|
||||
deps = Dependencies()
|
||||
|
||||
weather = WeatherWatcher(deps.get_config(), deps.get_event_bus(), deps.get_state_machine())
|
||||
|
||||
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…
Reference in New Issue