Skip to content

Commit 70c2c9e

Browse files
committed
Initial import
0 parents  commit 70c2c9e

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed

huectl

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
import json
3+
import requests
4+
import sys
5+
import os
6+
import time
7+
import socket
8+
import copy
9+
10+
prefs = {}
11+
prefs_old = None
12+
13+
class CliFatalError(Exception):
14+
pass
15+
16+
17+
def preffilename():
18+
if sys.platform == 'darwin':
19+
tpl = '~/Library/Preferences/huectl.json'
20+
else:
21+
tpl = '%s/huectl/config.json' % (sys.environ.get('XDG_CONFIG_HOME', '~/.config'), )
22+
return os.path.expanduser(tpl)
23+
24+
25+
def prefs_load():
26+
global prefs, prefs_old
27+
try:
28+
with open(preffilename(), 'r') as fp:
29+
prefs = json.load(fp)
30+
prefs_old = copy.deepcopy(prefs)
31+
except:
32+
prefs = {}
33+
34+
35+
def prefs_save():
36+
global prefs, prefs_old
37+
if prefs_old == prefs:
38+
return
39+
with open(preffilename(), 'w') as fp:
40+
json.dump(prefs, fp)
41+
42+
43+
def discover():
44+
"""Find hue bridge using meethue rendezvous point. As this is quite slow,
45+
try a cached IP first."""
46+
global prefs
47+
if 'bridge_cache' in prefs:
48+
try:
49+
bridgeip = prefs['bridge_cache']['ip']
50+
reply = requests.get('http://%s/api/' % (bridgeip), timeout=3).json()
51+
if len(reply) > 0 and 'error' in reply[0] and reply[0]['error']['type'] == 4:
52+
# good bridge, use it
53+
return bridgeip
54+
except requests.exceptions.ConnectTimeout:
55+
# fallback to rendezvous point
56+
pass
57+
58+
print("Discovering bridge...")
59+
try:
60+
bridgeip = requests.get('https://www.meethue.com/api/nupnp').json()[0]['internalipaddress']
61+
prefs['bridge_cache'] = {'ip': bridgeip}
62+
return bridgeip
63+
except Exception as except_inst:
64+
print("Bridge discovery failed:", except_inst)
65+
raise CliFatalError()
66+
67+
68+
def link(bridgeip):
69+
"""Request username from bridge. User has to press the LINK button while
70+
doing this."""
71+
body = json.dumps({'devicetype': 'huectl#' + socket.gethostname()})
72+
maxtries = 30
73+
sys.stdout.write("Press link button on bridge %s now..." % bridgeip)
74+
sys.stdout.flush()
75+
for try_ in range(0, maxtries):
76+
reg = requests.post('http://%s/api' % (bridgeip, ), body).json()
77+
if 'error' in reg[0] and reg[0]['error']['type'] == 101:
78+
sys.stdout.write(".")
79+
sys.stdout.flush()
80+
else:
81+
prefs['username'] = reg[0]['success']['username']
82+
prefs_save()
83+
print()
84+
return
85+
time.sleep(1)
86+
print("\nExpired. Please try again.")
87+
raise CliFatalError()
88+
89+
90+
class Api(object):
91+
def __init__(self, bridgeip, username):
92+
self.url = 'http://%s/api/%s/' % (bridgeip, username)
93+
self.s = requests.Session()
94+
95+
def lights(self):
96+
return self.s.get(self.url + 'lights').json()
97+
98+
def info(self, light):
99+
return self.s.get(self.url + ('lights/%d' % (light, ))).json()
100+
101+
def set_onoff(self, light, onoff):
102+
body = json.dumps({'on': bool(onoff)})
103+
return self.s.put(self.url + ('lights/%d/state' % (light, )), body).json()
104+
105+
def dim(self, light, pct):
106+
body = json.dumps({'bri': int(pct * 254 / 100), 'on': True})
107+
return self.s.put(self.url + ('lights/%d/state' % (light, )), body).json()
108+
109+
110+
def main():
111+
global prefs
112+
prefs_load()
113+
bridgeip = discover()
114+
if 'username' not in prefs:
115+
link(bridgeip)
116+
117+
api = Api(bridgeip, prefs['username'])
118+
if len(sys.argv) == 1:
119+
command = 'list'
120+
args = []
121+
else:
122+
command = sys.argv[1]
123+
args = sys.argv[2:]
124+
125+
if command == 'list':
126+
lights = api.lights()
127+
for lightid, detail in lights.items():
128+
print("%-5s %-30s %-25s %s" % (lightid, detail['name'], detail['type'], detail['state']))
129+
elif command == 'dim':
130+
light = int(args[0])
131+
pct = int(args[1])
132+
print(api.dim(light, pct))
133+
elif command == 'on':
134+
light = int(args[0])
135+
print(api.set_onoff(light, True))
136+
elif command == 'off':
137+
light = int(args[0])
138+
print(api.set_onoff(light, False))
139+
else:
140+
print("Unknown command given.")
141+
raise CliFatalError()
142+
143+
prefs_save()
144+
145+
146+
try:
147+
main()
148+
except CliFatalError:
149+
sys.exit(1)

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
requests

0 commit comments

Comments
 (0)