-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLifxController.py
More file actions
324 lines (259 loc) · 11.7 KB
/
LifxController.py
File metadata and controls
324 lines (259 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
from lifxlan import *
import rumps
from PIL import Image
rumps.debug_mode(True)
# LIFX Controller class
class LifxController(rumps.App):
# Initialise the class
def __init__(self):
super(LifxController, self).__init__('LifxController', None, 'icon.png', True)
self.reset()
# Reset menu, lights and groups
def reset(self):
self.menu.clear()
self.lights = {} # Dictionary mapping LIFX light names to their LIFX objects
self.groups = {} # Dictionary mapping group names to the names of the lights in them
# Add initial menu items to the menu
resetMenuItem = rumps.MenuItem('Hard Reset', callback=self.onReset)
self.menu.update(['Groups', None, 'Individual', None, resetMenuItem, None])
# Will attempt to refresh the active lights
def onReset(self, sender):
# Reset menu and restart light search
self.reset()
self.menu.add(self.quit_button)
self.updateActiveLights()
# Will run when a power button has been clicked
def onPowerUpdate(self, sender):
# Get the associated LIFX object or group
light = self.lights.get(sender.name)
group = self.groups.get(sender.name)
# If no light or group was found, soft fail
if light is None and group is None:
return
# Update state and label, toggle power
sender.state = not sender.state
sender.title = 'Power is ON' if sender.state else 'Power is OFF'
# Update power for all lights in a group, or just the individual light
try:
if group:
[self.lights[name].set_power(sender.state, 0, True) for name in group]
else:
light.set_power(sender.state, 0, True)
# A light did not respond
except WorkflowException:
return
# Will run when a slider has been updated
def onSliderUpdate(self, sender):
# Get the associated menu, LIFX object and group (if applicable)
menu = self.menu.get(sender.name)
light = self.lights.get(sender.name)
group = self.groups.get(sender.name)
# If no menu item or group or light was found, soft fail
if menu is None or (light is None and group is None):
return
# Get all menu items
items = dict(menu.items())
# Get all the current HSBK slider values
h = items['h_' + sender.name].value
s = items['s_' + sender.name].value
b = items['b_' + sender.name].value
k = items['k_' + sender.name].value
# Update values for all lights in a group, or just the individual light
try:
if group:
[self.lights[name].set_color([h, s, b, k], 0, True) for name in group]
else:
light.set_color([h, s, b, k], 0, True)
# Get and set infrared value if supported
if light.get_infrared():
i = items['i_' + sender.name].value
light.set_infrared(i)
# A light did not respond
except WorkflowException:
return
# Add new group light as a submenu with it's own controllable buttons/sliders
def addGroup(self, name):
# Get the associated group
group = self.groups[name]
# If no group was found, soft fail
if group is None:
return
# Get initial light values
# Note: HSBK values are retrieved from first light in group as an estimate
try:
h, s, b, k = self.lights[group[0]].get_color()
powers = [self.lights[l].get_power() for l in group]
p = 'ON' if 65535 in powers else 'OFF'
# Light did not respond
except WorkflowException:
return
# Create power button and colour sliders
powerButton = rumps.MenuItem('Power is ' + p, name=name, callback=self.onPowerUpdate)
powerButton.state = p == 'ON'
hueSlider = rumps.SliderMenuItem('h_' + name, 0, h, 65535, self.onSliderUpdate, name)
saturationSlider = rumps.SliderMenuItem('s_' + name, s, 0, 65535, self.onSliderUpdate, name)
brightnessSlider = rumps.SliderMenuItem('b_' + name, b, 0, 65535, self.onSliderUpdate, name)
kelvinSlider = rumps.SliderMenuItem('k_' + name, k, 2500, 9000, self.onSliderUpdate, name)
# Create light menu and first element (necessary to create submenu)
groupMenu = rumps.MenuItem(name)
groupMenu.add(powerButton)
# Add rest of submenu elements
groupMenu.update([None, 'Hue', hueSlider, 'Saturation', saturationSlider,
'Brightness', brightnessSlider, 'Kelvin', kelvinSlider])
# Add light submenu to the individual light list menu
self.menu.insert_after('Groups', groupMenu)
# Add new individual light as a submenu with it's own controllable buttons/sliders
def addIndividual(self, name):
# Get the associated LIFX object
light = self.lights.get(name)
# If no light was found, soft fail
if light is None:
return
# Get initial light values
try:
h, s, b, k = light.get_color()
i = light.get_infrared()
p = 'ON' if light.get_power() else 'OFF'
# Light did not respond
except WorkflowException:
return
# Create power button and colour sliders
powerButton = rumps.MenuItem('Power is ' + p, name=name, callback=self.onPowerUpdate)
powerButton.state = p == 'ON'
hueSlider = rumps.SliderMenuItem('h_' + name, h, 0, 65535, self.onSliderUpdate, name)
saturationSlider = rumps.SliderMenuItem('s_' + name, s, 0, 65535, self.onSliderUpdate, name)
brightnessSlider = rumps.SliderMenuItem('b_' + name, b, 0, 65535, self.onSliderUpdate, name)
kelvinSlider = rumps.SliderMenuItem('k_' + name, k, 2500, 9000, self.onSliderUpdate, name)
# Create light menu and first element (necessary to create submenu)
lightMenu = rumps.MenuItem(name)
lightMenu.add(powerButton)
# Add rest of submenu elements
lightMenu.update([None, 'Hue', hueSlider, 'Saturation', saturationSlider,
'Brightness', brightnessSlider, 'Kelvin', kelvinSlider])
# Create and add infrared slider if supported
if i:
infraredSlider = rumps.SliderMenuItem('i_' + name, i, 0, 65535, self.onSliderUpdate, name)
lightMenu.update(['Infrared', infraredSlider])
# Add light submenu to the individual light list menu
self.menu.insert_after('Individual', lightMenu)
# Update the state a LIFX light
def updateLightState(self, name):
# Get the associated menu and LIFX object
menu = self.menu.get(name)
light = self.lights.get(name)
# If no menu or light was found, soft fail
if menu is None or light is None:
return
# Get updated light values
try:
h, s, b, k = light.get_color()
i = light.get_infrared()
p = 'ON' if light.get_power() else 'OFF'
groupName = light.get_group()
# Light did not respond
except WorkflowException:
return
# Get all menu items
items = dict(menu.items())
# Update values
powerMenuItem = items.get('Power is ON') if 'Power is ON' in items else items.get('Power is OFF')
powerMenuItem.state = p == 'ON'
powerMenuItem.title = 'Power is ' + p
items['h_' + name].value = h
items['s_' + name].value = s
items['b_' + name].value = b
items['k_' + name].value = k
# Update infrared value if supported
if i:
items['i_' + sender.name].value = i
# Update this light's group if it has one
if groupName in self.groups:
self.updateGroupState(groupName, [h, s, b, k])
# Update the state of a group
def updateGroupState(self, name, colourValues):
# Get the associated group and menu
group = self.groups.get(name)
menu = self.menu.get(name)
# If no group or menu was found, soft fail
if group is None or menu is None:
return
# Get group power values
try:
powers = [self.lights[l].get_power() for l in group]
p = 'ON' if 65535 in powers else 'OFF'
# A light did not respond
except WorkflowException:
return
# Get all group menu items
items = dict(menu.items())
# Update group values
h, s, b, k = colourValues
powerMenuItem = items.get('Power is ON') if 'Power is ON' in items else items.get('Power is OFF')
powerMenuItem.state = p == 'ON'
powerMenuItem.title = 'Power is ' + p
items['h_' + name].value = h
items['s_' + name].value = s
items['b_' + name].value = b
items['k_' + name].value = k
# Removes the menu item from the menu as well as any dictionaries it is in
def removeMenuItem(self, menuItem):
if menuItem in self.menu:
del self.menu[menuItem]
if menuItem in self.lights:
del self.lights[menuItem]
if menuItem in self.groups:
del self.groups[menuItem]
# Timer event that keeps the list of active lights up to date
@rumps.timer(10)
def updateActiveLights(self, _):
# Discovers all active LIFX lights on the network
lights = LifxLAN().get_lights()
# Loop through the discovered lights
for light in lights:
# Get light name and group if it has one
try:
name = light.get_label()
group = light.get_group()
# Light did not respond
except WorkflowException:
continue
# If the light is a new light, add it to the dictionary of lights
if name not in self.lights:
self.lights[name] = light
# If the light has no group, continue onto next light
if group is None:
continue
# If the light has a group that's not in the dictionary of groups, add the new group
if group not in self.groups:
self.groups[group] = [name]
# If the light is a part of a group, add it to that group if it hasn't been already
if name not in self.groups[group]:
self.groups[group].append(name)
# Get active lights and groups
try:
activeLights = [l.get_label() for l in lights]
activeGroups = [l.get_group() for l in lights]
# A light did not respond
except WorkflowException:
return
# Remove any inactive lights and groups from the menu
for menuItem in list(self.menu.keys()):
if menuItem not in self.lights and menuItem not in self.groups:
continue
if menuItem in activeLights or menuItem in activeGroups:
continue
self.removeMenuItem(menuItem)
# Timer event that updates the menu with the active light's and their states
@rumps.timer(2)
def updateAllStates(self, _):
# Update all updated light states
[self.updateLightState(l) for l in list(self.lights.keys())]
# Filter out any new lights or groups that aren't in the menu yet
newLights = [l for l in self.lights if l not in list(self.menu.keys())]
newGroups = [g for g in self.groups if g not in list(self.menu.keys())]
# Add any new lights and groups to the menu
[self.addIndividual(l) for l in newLights]
[self.addGroup(g) for g in newGroups]
if __name__ == '__main__':
# Create LifxController menu and run it
LifxController().run()