mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 02:37:08 +00:00
bug fixes
This commit is contained in:
parent
f3a093feda
commit
0ff00ae5e0
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
|
||||||
|
|
||||||
|
|
||||||
|
1
start.py
1
start.py
@ -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()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user