Initial commit: Simple python script to control Philips Hue Lamps on a Bridge
This commit is contained in:
		
						commit
						28dcb2b4ba
					
				| 
						 | 
				
			
			@ -0,0 +1,308 @@
 | 
			
		|||
#!/usr/bin/python3
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
Script to set colours of HUE lamps (via the bridge).
 | 
			
		||||
Author: joost witteveen (joosteto@gmail.com)
 | 
			
		||||
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
Get a valid access code from the bridge:
 | 
			
		||||
1) press the button on the bridge
 | 
			
		||||
2) Run the following command:
 | 
			
		||||
   python3 hue.py --getuser yourusername
 | 
			
		||||
3) The access code will be saved in the file 'accesscode', and used for next commands.
 | 
			
		||||
 | 
			
		||||
Make all lamps as bright as possible, and green:
 | 
			
		||||
python3 hue.py  --hue 2 --bri 1
 | 
			
		||||
 | 
			
		||||
Show json response for a '/lights/2' http GET request:
 | 
			
		||||
python3 hue.py --get /lights/2
 | 
			
		||||
 | 
			
		||||
Continuously check for long 'off' button press on switch id=4, and if 
 | 
			
		||||
python3 hue.py --checklong0 4
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
More info about the HUE lamps:
 | 
			
		||||
 | 
			
		||||
https://domoticproject.com/controlling-philips-hue-lights-with-raspberry-pi/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sudo apt-get install avahi-utils python3-requests
 | 
			
		||||
 | 
			
		||||
# Get address of the bridge:
 | 
			
		||||
avahi-browse -rt _hue._tcp
 | 
			
		||||
 | 
			
		||||
#################################################
 | 
			
		||||
python3 hue.py --get /lights/2
 | 
			
		||||
{'capabilities': {'certified': True,
 | 
			
		||||
                  'control': {'colorgamut': [[0.6915, 0.3083],
 | 
			
		||||
                                             [0.17, 0.7],
 | 
			
		||||
                                             [0.1532, 0.0475]],
 | 
			
		||||
                              'colorgamuttype': 'C',
 | 
			
		||||
                              'ct': {'max': 500, 'min': 153},
 | 
			
		||||
                              'maxlumen': 800,
 | 
			
		||||
                              'mindimlevel': 200},
 | 
			
		||||
                  'streaming': {'proxy': True, 'renderer': True}},
 | 
			
		||||
 'config': {'archetype': 'classicbulb',
 | 
			
		||||
            'direction': 'omnidirectional',
 | 
			
		||||
            'function': 'mixed',
 | 
			
		||||
            'startup': {'configured': True, 'mode': 'powerfail'}},
 | 
			
		||||
 'manufacturername': 'Signify Netherlands B.V.',
 | 
			
		||||
 'modelid': 'LCA001',
 | 
			
		||||
 'name': 'Hue color lamp 2',
 | 
			
		||||
 'productid': 'Philips-LCA001-5-A19ECLv6',
 | 
			
		||||
 'productname': 'Hue color lamp',
 | 
			
		||||
 'state': {'alert': 'select',
 | 
			
		||||
           'bri': 25,
 | 
			
		||||
           'colormode': 'hs',
 | 
			
		||||
           'ct': 500,
 | 
			
		||||
           'effect': 'none',
 | 
			
		||||
           'hue': 0,
 | 
			
		||||
           'mode': 'homeautomation',
 | 
			
		||||
           'on': True,
 | 
			
		||||
           'reachable': True,
 | 
			
		||||
           'sat': 127,
 | 
			
		||||
           'xy': [0.5359, 0.3425]},
 | 
			
		||||
 'swconfigid': 'BD38721C',
 | 
			
		||||
 'swupdate': {'lastinstall': '2020-12-05T13:42:29', 'state': 'noupdates'},
 | 
			
		||||
 'swversion': '1.65.9_hB3217DF',
 | 
			
		||||
 'type': 'Extended color light',
 | 
			
		||||
 'uniqueid': '00:17:88:01:06:7a:a7:59-0b'}
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
bridgeAddr="Philips-hue.local"
 | 
			
		||||
user=None
 | 
			
		||||
verbose=None
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
import os
 | 
			
		||||
import pprint
 | 
			
		||||
import requests
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
class LinkButtonNotPressed(Exception):
 | 
			
		||||
    def __init__(self, info):
 | 
			
		||||
        self.info=info
 | 
			
		||||
class BridgeError(Exception):
 | 
			
		||||
    def __init__(self, info):
 | 
			
		||||
        self.info=info
 | 
			
		||||
 | 
			
		||||
def getcmd(arg=""):
 | 
			
		||||
    if arg[0]=='/':
 | 
			
		||||
        arg=arg[1:]
 | 
			
		||||
    url = f'http://{bridgeAddr}/api/{user}/{arg}'
 | 
			
		||||
    headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
 | 
			
		||||
    if verbose:
 | 
			
		||||
        print(f"URL={url}")
 | 
			
		||||
    r = requests.get(url,headers=headers)
 | 
			
		||||
    answ=r.json()
 | 
			
		||||
    try:
 | 
			
		||||
        if 'error' in answ[0]:
 | 
			
		||||
            raise BridgeError(answ[0]['error'])
 | 
			
		||||
    except KeyError: #normally answ is a dict, and test for 'error' in answ[0] will fail
 | 
			
		||||
        pass
 | 
			
		||||
    if verbose:
 | 
			
		||||
        print(f"  -> {r.json()}")
 | 
			
		||||
    return answ
 | 
			
		||||
    
 | 
			
		||||
def getuser(name):
 | 
			
		||||
    """
 | 
			
		||||
curl -d '{"devicetype":"[joosteto]"}' -H "Content-Type: application/json" -X POST 'http://philips-hue.local/api'
 | 
			
		||||
[{"success":{"username":"asdjkflasdjfksdfjsklfjsdkla-ajfdklasdjk"}}]
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    url = f'http://{bridgeAddr}/api/'
 | 
			
		||||
    payload =f'{{"devicetype": "[{name}]"}}'
 | 
			
		||||
    #print(f"payload={payload}")
 | 
			
		||||
    headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
 | 
			
		||||
    r=requests.post(url, data=payload, headers=headers)
 | 
			
		||||
    #print(f"r.text={r.text}")
 | 
			
		||||
    answ=r.json()[0]
 | 
			
		||||
    #answ=[{"success":{"yourusername":"asdjkflasdjfksdfjsklfjsdkla-ajfdklasdjk"}}][0]
 | 
			
		||||
    if 'success' in answ:
 | 
			
		||||
        usercode=answ['success']
 | 
			
		||||
        name=tuple(usercode.keys())[0]
 | 
			
		||||
        code=usercode[name]
 | 
			
		||||
        print(f'access code for name {name}: {code}')
 | 
			
		||||
        if os.path.exists('accesscode'):
 | 
			
		||||
            print("Erase old accesscode file to store new accesscode?")
 | 
			
		||||
            input("^C to abort, Enter to continue ")
 | 
			
		||||
        open('accesscode', 'w').write(code)
 | 
			
		||||
    else:
 | 
			
		||||
        if 'error' in answ:
 | 
			
		||||
            if answ['error']['description']=='link button not pressed':
 | 
			
		||||
                raise LinkButtonNotPressed(answ['error'])
 | 
			
		||||
            print(f'Error: {answ["error"]["description"]}')
 | 
			
		||||
        else:
 | 
			
		||||
            print(f'Error: {answ}')
 | 
			
		||||
 | 
			
		||||
def parsecolorgamuts(lightinfo):
 | 
			
		||||
    cg={}
 | 
			
		||||
    for (lightid, li) in lightinfo.items():
 | 
			
		||||
        cg[lightid]=li['capabilities']['control']['colorgamut']
 | 
			
		||||
    return cg
 | 
			
		||||
 | 
			
		||||
def set_lightstate(lightid, name, value):
 | 
			
		||||
    url = f'http://{bridgeAddr}/api/{user}/lights/{lightid}/state'
 | 
			
		||||
    payload =f'{{"{name}":{value}, "on": true}}'
 | 
			
		||||
    headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
 | 
			
		||||
    if verbose:
 | 
			
		||||
        print(f'url={url}\ndata={payload}\nheaders={headers}')
 | 
			
		||||
    r = requests.put(url, data=payload, headers=headers)
 | 
			
		||||
    if verbose:
 | 
			
		||||
        print(f"  -> response: {r.text}")
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
def loopcolors(cg):
 | 
			
		||||
    tStart=time.time()
 | 
			
		||||
    while True:
 | 
			
		||||
        t=(time.time()-tStart)/5
 | 
			
		||||
        for lightid in cg:
 | 
			
		||||
            tid=(t+int(lightid))%3
 | 
			
		||||
            it0=int(tid)
 | 
			
		||||
            it1=(it0+1)%3
 | 
			
		||||
            xy0=cg[lightid][it0]
 | 
			
		||||
            xy1=cg[lightid][it1]
 | 
			
		||||
            x=(tid-it0)*xy0[0]+(1-tid+it0)*xy1[0]
 | 
			
		||||
            y=(tid-it0)*xy0[1]+(1-tid+it0)*xy1[1]
 | 
			
		||||
            set_lightstate(lightid, "xy", [x,y])
 | 
			
		||||
        time.sleep(0.3)
 | 
			
		||||
 | 
			
		||||
def checkLong0(sensorid, cg):
 | 
			
		||||
    oldstate=None
 | 
			
		||||
    tStart=time.time()
 | 
			
		||||
    while True:
 | 
			
		||||
        sensinf=getcmd(f'/sensors/{sensorid}')
 | 
			
		||||
        sensstate=sensinf['state']['buttonevent']
 | 
			
		||||
        if sensstate==4003 and oldstate != 'longdown':
 | 
			
		||||
            oldstate='longdown'
 | 
			
		||||
            #print(f'long off press: {sensinf["state"]}')
 | 
			
		||||
            tStart=time.time()
 | 
			
		||||
        elif sensstate!=4003:
 | 
			
		||||
            #print(f'no long off press: {sensinf["state"]}')
 | 
			
		||||
            oldstate=None
 | 
			
		||||
        if oldstate=='longdown':
 | 
			
		||||
            t=time.time()-tStart
 | 
			
		||||
            for lightid in cg:
 | 
			
		||||
                tid=(t/16-(int(lightid)/3))%1
 | 
			
		||||
                hue=int(tid*(2**16-1))
 | 
			
		||||
                set_lightstate(lightid, "hue", hue)
 | 
			
		||||
            time.sleep(0.2)
 | 
			
		||||
        else:
 | 
			
		||||
            time.sleep(1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
#gi=getinfo()
 | 
			
		||||
#pprint.pprint(gi)
 | 
			
		||||
 | 
			
		||||
#tryouts()    
 | 
			
		||||
#setlight(1, [0.6, 0.4])
 | 
			
		||||
#setlight(2, [0.6, 0.4])
 | 
			
		||||
#setlight(3, [0.6, 0.4])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    global bridgeAddr
 | 
			
		||||
    global user
 | 
			
		||||
    global verbose
 | 
			
		||||
    parser = argparse.ArgumentParser(description='Communicate with Phillips Hue Bridge (lamps, switches).')
 | 
			
		||||
    #configuration
 | 
			
		||||
    parser.add_argument('--user',
 | 
			
		||||
                        help='user (effectively password) at the bridge. If not given, read from file username')
 | 
			
		||||
    parser.add_argument('--bridge',
 | 
			
		||||
                        help='hostname/IP of the bridge. Default: philips-hue.local')
 | 
			
		||||
    parser.add_argument('--getuser',
 | 
			
		||||
                        help='get username (security code) from bridge (after button on bridge is pressed)')
 | 
			
		||||
 | 
			
		||||
    #actions:
 | 
			
		||||
    parser.add_argument('--get',
 | 
			
		||||
                        help='do a GET request, show the result. Example: --get /lights')
 | 
			
		||||
    parser.add_argument('--loop',
 | 
			
		||||
                        action='store_true',
 | 
			
		||||
                        help='change colors of the lights in a loop')
 | 
			
		||||
    parser.add_argument('--checklong0',
 | 
			
		||||
                        help='continuously check for long 0 press on sensor id')
 | 
			
		||||
    parser.add_argument('--hue',
 | 
			
		||||
                        help="set hue of specified lamps")
 | 
			
		||||
    parser.add_argument('--sat',
 | 
			
		||||
                        help='set saturation of specified lamps')
 | 
			
		||||
    parser.add_argument('--bri',
 | 
			
		||||
                        help='set brightness of specified lamps')
 | 
			
		||||
    parser.add_argument('--xy',
 | 
			
		||||
                        help='set xy color of specified lamps. Example: --xy 0.3,0.7')
 | 
			
		||||
 | 
			
		||||
    #Others
 | 
			
		||||
    parser.add_argument('--lamps',
 | 
			
		||||
                        help='hue,sat,bri,xy -commands work on lamps in this list. Example: --lamps 1,3')
 | 
			
		||||
    parser.add_argument('--verbose', action='store_true')
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    verbose=args.verbose
 | 
			
		||||
    #configuration
 | 
			
		||||
    if args.bridge is not None:
 | 
			
		||||
        bridgeAddr=args.bridge
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        if args.getuser is not None:
 | 
			
		||||
            getuser(args.getuser)
 | 
			
		||||
            
 | 
			
		||||
        if args.user is not None:
 | 
			
		||||
            user=args.user
 | 
			
		||||
        else:
 | 
			
		||||
            user=open('accesscode','r').read().strip()
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        lightsinfo=getcmd('/lights')
 | 
			
		||||
        colorgammuts=parsecolorgamuts(lightsinfo)
 | 
			
		||||
            
 | 
			
		||||
        if args.lamps is not None:
 | 
			
		||||
            lstr=args.lamps
 | 
			
		||||
            if ',' not in lstr: #add "," if only one lamp is given (--lamps 1)
 | 
			
		||||
                lstr=lstr+","
 | 
			
		||||
            lamps=tuple(str(l) for l in eval(lstr))
 | 
			
		||||
        else:
 | 
			
		||||
            lamps=tuple(lightsinfo.keys())
 | 
			
		||||
        #print(f"lamps={lamps}")
 | 
			
		||||
    
 | 
			
		||||
        #actions
 | 
			
		||||
        if args.get is not None:
 | 
			
		||||
            answ=getcmd(args.get)
 | 
			
		||||
            pprint.pprint(answ)
 | 
			
		||||
    
 | 
			
		||||
        if args.loop:
 | 
			
		||||
            loopcolors(colorgammuts)
 | 
			
		||||
    
 | 
			
		||||
        if args.checklong0 is not None:
 | 
			
		||||
            checkLong0(args.checklong0, colorgammuts)
 | 
			
		||||
    
 | 
			
		||||
        for lid in lamps:
 | 
			
		||||
            if args.hue is not None:
 | 
			
		||||
                hue=int(float(args.hue)*2**16//6) % 2**16
 | 
			
		||||
                #print(f"setting {lid} to hue={hue}")
 | 
			
		||||
                set_lightstate(lid, "hue", hue)
 | 
			
		||||
                
 | 
			
		||||
            if args.sat is not None:
 | 
			
		||||
                sat=int(float(args.sat)*255)
 | 
			
		||||
                set_lightstate(lid, "sat", sat)
 | 
			
		||||
                
 | 
			
		||||
            if args.xy is not None:
 | 
			
		||||
                xy=list(eval(args.xy))
 | 
			
		||||
                set_lightstate(lid, "xy", xy)
 | 
			
		||||
    
 | 
			
		||||
            if args.bri is not None:
 | 
			
		||||
                bri=int(255*eval(args.bri))
 | 
			
		||||
                set_lightstate(lid, "bri", bri)
 | 
			
		||||
    except LinkButtonNotPressed as e:
 | 
			
		||||
        print("To get a valid user access code, the button on the bridge needs to be pressed shortly before the --getaccess command is used")
 | 
			
		||||
    except BridgeError as e:
 | 
			
		||||
        print(f"Error message from bridge: {e}")
 | 
			
		||||
        if e.info['description']=='unauthorized user':
 | 
			
		||||
            print("Access code was not accepted by bridge. Run again with the '--getaccess name' argument")
 | 
			
		||||
        
 | 
			
		||||
if __name__=="__main__":
 | 
			
		||||
    main()
 | 
			
		||||
		Loading…
	
		Reference in New Issue