A project utilizing MicroPython and a magnetic reed switch for triggering an alarm (email, Philips Hue light, etc)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

250 lines
7.3 KiB

import socket
from time import sleep
import json
try:
import requests
except:
import urequests as requests
# UPnP SSDP Search request header
HEADER = b"""M-SEARCH * HTTP/1.1\r
HOST: 239.255.255.250:1900\r
MAN: "ssdp:discover"\r
ST: ssdp:all\r
MX: 3\r
\r
"""
def rgb2xy(r, g, b):
'''
Converts an RGB value to xyz coordinates, based on matrix transformations
of colorspace values here (Wide Gamut RGB):
http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
'''
X = 0.7161046 * r + 0.1009296 * g + 0.1471858 * b
Y = 0.2581874 * r + 0.7249378 * g + 0.0168748 * b
Z = 0.0000000 * r + 0.0517813 * g + 0.7734287 * b
x = X / (X+Y+Z)
y = Y / (X+Y+Z)
return [x, y]
class Bridge:
"""Provides methods for connecting to and using Hue Bridge. Supports
Micropython, Python 2, and 3."""
def __init__(self,autosetup=True, debug=1):
self.debug = debug #0=no prints, 1=messages, 2=debug
self.IP = None
self.username = None
if autosetup:
self.setup()
def show(self,str,level=1):
""" Show debug output. """
if self.debug >= level:
print(str)
def setup(self):
""" Loads bridge settings or attempts to establish them, if needed."""
success = self.loadSettings()
if success:
# verify bridge settings work
try:
self.idLights()
success = True
except:
success = False
if not success:
if self.discover():
self.show('Bridge located at {}'.format(self.IP))
self.show('>>> Press link button on Hue bridge to register <<<')
if self.getUsername():
success = self.saveSettings()
else:
self.show("Couldn't get username from bridge.")
else:
self.show("Couldn't find bridge on LAN.")
return success
def discover(self):
""" Locate Hue Bridge IP using UPnP SSDP search. Discovery will return
when bridge is found or 3 seconds after last device response. Returns IP
address or None."""
#On ESP8266, disable AP WLAN to force use of STA interface
#import network
#ap = network.WLAN(network.AP_IF)
#ap.active(False)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(HEADER, ('239.255.255.250',1900)) #UPnP Multicast
s.settimeout(3)
IP = None
while IP == None:
data, addr = s.recvfrom(1024)
self.show(str(data),2)
lines = data.split(b'\r\n')
for l in lines:
tokens = l.split(b' ')
if tokens[0] == b'SERVER:':
product = tokens[3].split(b'/')
if product[0] == b'IpBridge':
IP = str(addr[0])
break
s.close()
self.IP = IP
return IP
def getUsername(self):
""" Get a developer API username from bridge.
Requires that the bridge link button be pressed sometime while polling.
Polls for 20 seconds (20 attempts at 1 second intervals).
Can timeout with error if bridge is non-responsive.
Returns username on success or None on failure."""
url = 'http://{}/api'.format(self.IP)
data = '{"devicetype":"TapLight#mydevice"}'
username = None
count = 20
while count > 0 and username == None:
resp = requests.post(url,data=data)
if resp.status_code == 200:
j = resp.json()[0]
self.show(j,2)
if j.get('success'):
username = str(j['success']['username'])
self.username = username
sleep(1)
count -= 1
return username
def saveSettings(self):
""" Save bridge IP and username to bridge.dat file.
Returns True on success."""
if self.IP and self.username:
f=open('bridge.dat','w')
f.write(json.dumps([self.IP,self.username]))
f.close()
return True
else:
return None
def loadSettings(self):
""" Load bridge IP and username from bridge.dat file and set base URL.
Returns True on success. """
try:
f=open('bridge.dat')
except:
return None
l = json.load(f)
f.close()
self.IP = str(l[0])
self.username = str(l[1])
self.show('Loaded settings {} {}'.format(self.IP,self.username),2)
return True
def resetSettings(self):
"""Delete current saved bridge settings and reinitiate."""
from os import remove
remove('bridge.dat')
self.IP = None
self.username = None
self.setup()
def url(self,path):
"""Return url for API calls."""
return 'http://{}/api/{}/{}'.format(self.IP,self.username,path)
def get(self, path):
"""Perform GET request and return json result."""
url = self.url(path)
self.show(url,2)
resp = requests.get(url).json()
self.show(resp,2)
return resp
def put(self, path, data):
"""Perform PUT request and return response."""
url = self.url(path)
self.show(url,2)
data = json.dumps(data)
self.show(data,2)
resp = requests.put(url, data=data).json()
self.show(resp,2)
return resp
def allLights(self):
"""Returns dictionary containing all lights, with detail."""
"""Large return set, not ideal for controllers with limited RAM."""
return self.get('lights')
def idLights(self):
"""Returns list of all light IDs."""
ids = self.get('groups/0')['lights']
for i in range(len(ids)):
ids[i] = int(ids[i])
return ids
def getLight(self,id):
"""Returns dictionary of light details for given ID."""
return self.get('lights/{}'.format(str(id)))
def getLights(self):
"""Iterates through each light to build and return a dictionary
of light IDs and names."""
dict = {}
for i in self.idLights():
dict[i] = str(self.getLight(i)['name'])
return dict
def setLight(self,id,**kwargs):
"""Set one or more states of a light.
Ex: setLight(1,on=True,bri=254,hue=50000,sat=254)"""
if 'rgb' in kwargs:
r,g,b = kwargs.pop('rgb')
kwargs['xy'] = rgb2xy(r, g, b)
self.put('lights/{}/state'.format(str(id)),kwargs)
def allGroups(self):
"""Returns dictionary containing all groups, with detail."""
return self.get('groups')
def getGroup(self,id):
"""Returns dictionary of group details."""
return self.get('groups/{}'.format(str(id)))
def getGroups(self):
"""Returns dictionary of group IDs and names."""
dict = {}
groups = self.allGroups()
for g in groups:
dict[int(g)] = str(groups[g]['name'])
return dict
def setGroup(self,id,**kwargs):
"""Set one or more states of a group.
Ex: setGroup(1,bri_inc=100,transitiontime=40)"""
if 'rgb' in kwargs:
r,g,b = kwargs.pop('rgb')
kwargs['xy'] = rgb2xy(r, g, b)
self.put('groups/{}/action'.format(str(id)),kwargs)