Files
2018-03-08 09:47:23 -05:00

234 lines
6.2 KiB
Python
Executable File

#!/bin/python
"""
This program uses CyberPower's pwrstatd daemon and the Phue Python library to turn
off your Hue lights after a brief power outage.
The program routinely query's your Hue bridge to a list of lights and stores the ones
that are currently off. It also queries pwrstat to determine if we are running on
MAINS or BATTERY power. When it detects that we are running on BATTERY power, it
waits for MAINS power to return, then turns off the Hue lights that were off prior
to the outage.
Created By:
Rich Oberholtzer
rich@rickelobe.com
https://gitlab.rickelobe.com
"""
import sys
import getopt
from phue import Bridge
import subprocess
import time
import gevent
def main(argv):
# IP address of your Hue Bridge
bridgeIP = "0"
createnew = False
global off_lights
off_lights = []
powerfailure = False
prev_status = []
username = ""
mode = "" # Mode Options: cyberpower, nut
logfile = ""
# How often (in seconds) to check the pwrstatd log for events
check_interval = 60
# Parse the options
try:
opts, args = getopt.getopt(argv,"hb:c:i:m:l",["bridge=","createnew=","interval=","mode=","logfile="])
except getopt.GetoptError:
optUsage()
sys.exit(2)
except ValueError:
print("Incorrect option usage!")
opUsage()
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
optUsage()
sys.exit()
elif opt in ("-b", "--bridge"):
bridgeIP = arg
elif opt in ("-c", "--createnew"):
createnew = True
bridgeIP = arg
elif opt in ("-i", "--interval"):
check_interval = float(arg)
elif opt in ("-m", "--mode"):
mode = arg.lower()
elif opt in ("-l", "--logfile"):
logfile = arg
'''
# Debugging
print ("Bridge IP: %s" % bridgeIP)
print ("Createnew: %s" % createnew)
print ("Username: %s" % username)
print ("Mode: %s" % mode)
print ("Logfile: %s" % logfile)
input("Press enter to continue")
'''
try:
bridge = Bridge(bridgeIP)
except:
print("Error, App not registered. Press Bridge button and try again.")
quit()
if createnew:
print("You've requested to connect to a new Bridge. Connecting...")
print("Press the button on your Bridge, then press Enter.")
bridge = Bridge(bridgeIP)
try:
bridge.connect()
except:
print("Bridge connect failed")
bridge.get_api()
# Turn off any lights listed in the off.lights file from last run
turnOffLights(bridge)
# Main guts of the program
cmd = ("python3 updateLightStatus.py -b " + str(bridgeIP))
print(cmd)
p = subprocess.Popen(str(cmd), shell=True)
#p = subprocess.Popen('python3 updateLightStatus.py -b 10.3.0.120', shell=True)
print("Spun off updateLightStatus process")
# Start an endless loop
while True:
print("Waiting for power failure")
state = checkForPowerOutage(mode,logfile)
if state:
waitSetlights(bridge,check_interval,mode,logfile)
#Pause
time.sleep(check_interval)
# End of endless loop... hmm
def optUsage():
# This function displays options usage information
print("Options Usage:")
print(" -b, --bridge | IP address of your Hue Bridge")
print(" -c, --createnew | Connect to a new Hue Bridge")
print(" -i, --interval | Set how often (in seconds) to check the pwrstatd log. Defaults to 60")
print(" -m, --mode | Set the mode for checking if there's a power outage")
print(" Options: 'cyberpower' or 'nut'")
print(" --logfile | Specify the logfile to monitor if choosing 'nut' mode")
return 0
def turnOffLights(bridge):
try:
with open("off.lights") as f:
prev_status = f.readlines()
f.close()
except:
print("Error reading file.")
# Check if prev_status contains items
if prev_status:
# Same bridge as before, apply the light status!
lights_list = bridge.get_light_objects('list')
# Since the only names in the file are lights that should be off
# let's turn any light name in the file off.
print("Searching off.lights for lights that were previously off...")
for light in lights_list:
for line in prev_status:
if light.name == line[:-1]:
light.on = False
print("Turning off: %s" % line)
return 0
def checkForPowerOutage(mode,logfile):
if mode == "cyberpower":
return checkCyberPowerOutage()
else:
return checkNutOutage(logfile)
def checkCyberPowerOutage():
# This function checks the current pwrstatd process to see if we're on utility power or not.
pwrstatus = subprocess.getstatusoutput("pwrstat -status | grep 'Power Supply'")
if "Utility" not in pwrstatus[1]:
# We have a power failure!
print("%s:Mains power FAILURE" % datestamp())
return True
else:
# No power failure... some other problem. Don't touch the lights.
print("%s:Mains power OK" % datestamp())
return False
def checkNutOutage(logfile):
# This function returns True if the Nut Server sends an outage alert.
#Search for "on battery"
if watchLog("on battery","on line power",str(logfile)):
print("%s:Mains power FAILURE" % datestamp())
return True
else:
print("%s:Mains power OK" % datestamp())
return False
def watchLog(watchfortrue,watchforfalse,watchfile):
# This function will watch watchfile (path to file in filesystem) for watchfor (string)
# then return the line it found
f = open(watchfile, "r")
f.seek(0,2) # Go to the end of the file
while True:
line = f.readline()
if not line:
time.sleep(0.1) # Sleep briefly
continue
if line.find(watchfortrue) >= 0:
f.close()
return True
elif line.find(watchforfalse) >= 0:
f.close()
return False
def waitSetlights(bridge,check_interval,mode,logfile):
# Periodically check for return to Utility power, and then turn off lights that were off prior to event.
print("Waiting for power to return")
while True:
# Loop in here until mains power returns
outage = checkForPowerOutage(mode,logfile)
#
if not outage:
# Turn off the lights that were off prior to the power outage
print("%s: ... Mains power has returned, turning off lights that were not on prior to outage:" % datestamp())
turnOffLights(bridge)
'''
for light in off_lights:
print("%s: Turning off %s" % (datestamp(), light.name))
light.on = False
'''
return 0
time.sleep(check_interval)
return 0
def datestamp():
# this function returns a date string formatted as YYYYMMDD-HHMMSS
return time.strftime("%Y%m%d-%H%M%S")
if __name__ == "__main__":
main(sys.argv[1:])