nlhue 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +91 -0
- data/Rakefile +1 -0
- data/bin/console +12 -0
- data/bin/setup +7 -0
- data/lib/nlhue/bridge.rb +830 -0
- data/lib/nlhue/disco.rb +362 -0
- data/lib/nlhue/group.rb +79 -0
- data/lib/nlhue/light.rb +20 -0
- data/lib/nlhue/request_queue.rb +131 -0
- data/lib/nlhue/scene.rb +88 -0
- data/lib/nlhue/ssdp.rb +106 -0
- data/lib/nlhue/target.rb +411 -0
- data/lib/nlhue/version.rb +3 -0
- data/lib/nlhue.rb +36 -0
- data/nlhue.gemspec +28 -0
- metadata +148 -0
data/lib/nlhue/ssdp.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Barebones asynchronous SSDP device discovery using EventMachine.
|
2
|
+
# Part of Nitrogen Logic's Ruby interface library for the Philips Hue.
|
3
|
+
# (C)2012 Mike Bourgeous
|
4
|
+
|
5
|
+
require 'socket'
|
6
|
+
require 'eventmachine'
|
7
|
+
|
8
|
+
module NLHue
|
9
|
+
module SSDP
|
10
|
+
SSDP_ADDR = '239.255.255.250'
|
11
|
+
SSDP_PORT = 1900
|
12
|
+
|
13
|
+
# TODO: Support passive discovery of other devices' announcements
|
14
|
+
|
15
|
+
# Eventually calls the given block with a NLHue::SSDP::Response
|
16
|
+
# for each matching device found on the network within timeout
|
17
|
+
# seconds. The block will be called with nil after the
|
18
|
+
# timeout. Returns the connection used for discovery; call
|
19
|
+
# #shutdown on the returned object to abort discovery.
|
20
|
+
def self.discover type='ssdp:all', timeout=5, &block
|
21
|
+
raise 'A block must be given to discover().' unless block_given?
|
22
|
+
|
23
|
+
con = EM::open_datagram_socket('0.0.0.0', 0, SSDPConnection, type, timeout, block)
|
24
|
+
EM.add_timer(timeout) do
|
25
|
+
con.close_connection
|
26
|
+
EM.next_tick do
|
27
|
+
yield nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO: Structure this using EM::Deferrable instead?
|
32
|
+
con
|
33
|
+
end
|
34
|
+
|
35
|
+
# The HTTP response representing a service discovered by SSDP.
|
36
|
+
class Response
|
37
|
+
attr_reader :ip, :response, :headers
|
38
|
+
|
39
|
+
def initialize ip, response
|
40
|
+
@ip = ip
|
41
|
+
@response = response
|
42
|
+
@headers = {}
|
43
|
+
|
44
|
+
response.split(/\r?\n\r?\n/, 2)[0].lines.each do |line|
|
45
|
+
if line.include? ':'
|
46
|
+
key, value = line.split(/: ?/, 2)
|
47
|
+
@headers[key.downcase] = value.strip
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
"#{@ip}:\n\t#{@response.lines.to_a.join("\t")}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Retrieves the value of a header, with case
|
57
|
+
# insensitive matching.
|
58
|
+
def [] header
|
59
|
+
@headers[header.downcase]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
# UDP connection used for SSDP by discover().
|
65
|
+
class SSDPConnection < EM::Connection
|
66
|
+
# type - the SSDP service type (used in the ST: field)
|
67
|
+
# timeout - the number of seconds to wait for responses (used in the MX: field)
|
68
|
+
# receiver is the block passed to discover().
|
69
|
+
def initialize type, timeout, receiver
|
70
|
+
super
|
71
|
+
@type = type
|
72
|
+
@timeout = timeout
|
73
|
+
@receiver = receiver
|
74
|
+
@msg = "M-SEARCH * HTTP/1.1\r\n" +
|
75
|
+
"HOST: #{SSDP_ADDR}:#{SSDP_PORT}\r\n" +
|
76
|
+
"MAN: ssdp:discover\r\n" +
|
77
|
+
"MX: #{timeout.to_i}\r\n" +
|
78
|
+
"ST: #{type}\r\n" +
|
79
|
+
"\r\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
def post_init
|
83
|
+
send_datagram @msg, SSDP_ADDR, SSDP_PORT
|
84
|
+
EM.add_timer(0.5) do
|
85
|
+
send_datagram @msg, SSDP_ADDR, SSDP_PORT
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def receive_data data
|
90
|
+
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
91
|
+
@receiver.call Response.new(ip, data) if @receiver
|
92
|
+
end
|
93
|
+
|
94
|
+
# Closes the UDP socket and ignores any future SSDP messages.
|
95
|
+
def shutdown
|
96
|
+
@receiver = nil
|
97
|
+
close_connection
|
98
|
+
end
|
99
|
+
|
100
|
+
# Indicates whether shutdown has been called.
|
101
|
+
def closed?
|
102
|
+
@receiver.nil?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/nlhue/target.rb
ADDED
@@ -0,0 +1,411 @@
|
|
1
|
+
# Base class representing a light or group known to a Hue bridge.
|
2
|
+
# (C)2015 Mike Bourgeous
|
3
|
+
|
4
|
+
module NLHue
|
5
|
+
# Base class representing a light or group known to a Hue bridge. See
|
6
|
+
# NLHue::Light and NLHue::Group.
|
7
|
+
class Target
|
8
|
+
attr_reader :id, :type, :name, :bridge, :transitiontime
|
9
|
+
|
10
|
+
# bridge - The Bridge that controls this light or group.
|
11
|
+
# id - The light or group's ID (>=0 for groups, >=1 for lights).
|
12
|
+
# info - Parsed Hash of the JSON light or group info object from the bridge.
|
13
|
+
# api_category - The category to pass to the bridge for rate limiting API
|
14
|
+
# requests. Also forms part of the API URL.
|
15
|
+
# api_target - @api_target for lights, @api_target for groups
|
16
|
+
def initialize(bridge, id, info, api_category, api_target)
|
17
|
+
@bridge = bridge
|
18
|
+
@id = id.to_i
|
19
|
+
@api_category = api_category
|
20
|
+
@api_target = api_target
|
21
|
+
|
22
|
+
@changes = Set.new
|
23
|
+
@defer = false
|
24
|
+
|
25
|
+
@info = {api_target => {}}
|
26
|
+
handle_json(info || {})
|
27
|
+
end
|
28
|
+
|
29
|
+
# Updates this light or group object using a Hash parsed from
|
30
|
+
# the JSON info from the Hue bridge.
|
31
|
+
def handle_json(info)
|
32
|
+
raise "Light/group info must be a Hash, not #{info.class}." unless info.is_a?(Hash)
|
33
|
+
|
34
|
+
# A group contains no 'xy' for a short time after creation.
|
35
|
+
# Add fake xy color for lamps that don't support color.
|
36
|
+
info[@api_target] = {} unless info[@api_target].is_a?(Hash)
|
37
|
+
info[@api_target]['xy'] ||= [0.33333, 0.33333]
|
38
|
+
|
39
|
+
info['id'] = @id
|
40
|
+
|
41
|
+
# Preserve deferred changes that have not yet been sent to the bridge
|
42
|
+
@changes.each do |key|
|
43
|
+
info[@api_target][key] = @info[@api_target][key]
|
44
|
+
end
|
45
|
+
|
46
|
+
@info = info
|
47
|
+
@type = @info['type']
|
48
|
+
@name = @info['name'] || @name || "Lightset #{@id}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Gets the current state of this light or group from the
|
52
|
+
# bridge. The block, if given, will be called with true and
|
53
|
+
# the response on success, or false and an Exception on error.
|
54
|
+
def update(&block)
|
55
|
+
tx = rand
|
56
|
+
@bridge.get_api "/#{@api_category}/#{@id}", @api_category do |response|
|
57
|
+
puts "#{tx} Target #{@id} update response: #{response}" # XXX
|
58
|
+
|
59
|
+
begin
|
60
|
+
status, result = @bridge.check_json(response)
|
61
|
+
handle_json(result) if status
|
62
|
+
rescue => e
|
63
|
+
status = false
|
64
|
+
result = e
|
65
|
+
end
|
66
|
+
|
67
|
+
yield status, result if block_given?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns a copy of the hash representing the light or group's
|
72
|
+
# state as parsed from the JSON returned by the bridge, without
|
73
|
+
# any range scaling (e.g. so hue range is 0..65535).
|
74
|
+
def to_h
|
75
|
+
@info.clone
|
76
|
+
end
|
77
|
+
|
78
|
+
# Converts the Hash returned by #state to JSON.
|
79
|
+
def to_json(*args)
|
80
|
+
state.to_json(*args)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Call to queue changes to be sent all at once. Updates will
|
84
|
+
# not be sent to the light or group until #submit is called.
|
85
|
+
# Call #nodefer to stop deferring changes.
|
86
|
+
def defer
|
87
|
+
@defer = true
|
88
|
+
end
|
89
|
+
|
90
|
+
# Stops deferring changes and sends any queued changes
|
91
|
+
# immediately.
|
92
|
+
def nodefer
|
93
|
+
@defer = false
|
94
|
+
set {}
|
95
|
+
end
|
96
|
+
|
97
|
+
# Sets the transition time in centiseconds used for the next
|
98
|
+
# immediate parameter change or deferred batch parameter
|
99
|
+
# change. The transition time will be reset when send_changes
|
100
|
+
# is called. Call with nil to clear the transition time.
|
101
|
+
def transitiontime=(time)
|
102
|
+
if time.nil?
|
103
|
+
@transitiontime = nil
|
104
|
+
else
|
105
|
+
time = 0 if time < 0
|
106
|
+
@transitiontime = time.to_i
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Tells the Bridge object that this Light or Group is ready to
|
111
|
+
# have its deferred data sent. The NLHue::Bridge will schedule
|
112
|
+
# a rate-limited call to #send_changes, which sends all changes
|
113
|
+
# queued since the last call to defer. The block, if given,
|
114
|
+
# will be called with true and the response on success, or
|
115
|
+
# false and an Exception on error. The transition time sent to
|
116
|
+
# the bridge can be controlled with transitiontime=. If no
|
117
|
+
# transition time is set, the default transition time will be
|
118
|
+
# used by the bridge.
|
119
|
+
def submit(&block)
|
120
|
+
puts "Submitting changes to #{self}" # XXX
|
121
|
+
@bridge.add_target self, &block
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns a Hash containing the light's info and current state,
|
125
|
+
# with symbolized key names and hue scaled to 0..360. Example:
|
126
|
+
# {
|
127
|
+
# :id => 1,
|
128
|
+
# :name => 'Hue Lamp 2',
|
129
|
+
# :type => 'Extended color light',
|
130
|
+
# :on => false,
|
131
|
+
# :bri => 220,
|
132
|
+
# :ct => 500,
|
133
|
+
# :x => 0.5,
|
134
|
+
# :y => 0.5,
|
135
|
+
# :hue => 193.5,
|
136
|
+
# :sat => 255,
|
137
|
+
# :colormode => 'hs'
|
138
|
+
# }
|
139
|
+
def state
|
140
|
+
{
|
141
|
+
:id => id,
|
142
|
+
:name => name,
|
143
|
+
:type => type,
|
144
|
+
:on => on?,
|
145
|
+
:bri => bri,
|
146
|
+
:ct => ct,
|
147
|
+
:x => x,
|
148
|
+
:y => y,
|
149
|
+
:hue => hue,
|
150
|
+
:sat => sat,
|
151
|
+
:colormode => colormode
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
# Tells the light or group to flash once if repeat is false, or
|
156
|
+
# several times if repeat is true. Sets the 'alert' property.
|
157
|
+
def alert!(repeat=false)
|
158
|
+
set({ 'alert' => repeat ? 'select' : 'lselect' })
|
159
|
+
end
|
160
|
+
|
161
|
+
# Stops any existing flashing of the light or group.
|
162
|
+
def clear_alert
|
163
|
+
set({ 'alert' => 'none' })
|
164
|
+
end
|
165
|
+
|
166
|
+
# Sets the light or group's alert status to the given string
|
167
|
+
# (one of 'select' (flash once), 'lselect' (flash several
|
168
|
+
# times), or 'none' (stop flashing)). Any other value may
|
169
|
+
# result in an error from the bridge.
|
170
|
+
def alert=(alert)
|
171
|
+
set({ 'alert' => alert })
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns the current alert state of the light or group (or the
|
175
|
+
# stored state if defer() was called, but send() has not yet
|
176
|
+
# been called). Groups are not updated when their constituent
|
177
|
+
# lights are changed individually.
|
178
|
+
def alert
|
179
|
+
(@info[@api_target] || @info[@api_target])['alert']
|
180
|
+
end
|
181
|
+
|
182
|
+
# Sets the on/off state of this light or group (true or false).
|
183
|
+
# Lights must be on before other parameters can be changed.
|
184
|
+
def on=(on)
|
185
|
+
set({ 'on' => !!on })
|
186
|
+
end
|
187
|
+
|
188
|
+
# The light state most recently set with on=, #on! or #off!, or
|
189
|
+
# the last light state received from the bridge due to calling
|
190
|
+
# #update on the light/group or on the NLHue::Bridge.
|
191
|
+
def on?
|
192
|
+
@info[@api_target]['on']
|
193
|
+
end
|
194
|
+
|
195
|
+
# Turns the light or group on.
|
196
|
+
def on!
|
197
|
+
self.on = true
|
198
|
+
end
|
199
|
+
|
200
|
+
# Turns the light or group off.
|
201
|
+
def off!
|
202
|
+
self.on = false
|
203
|
+
end
|
204
|
+
|
205
|
+
# Sets the brightness of this light or group (0-255 inclusive).
|
206
|
+
# Note that a brightness of 0 is not off. The light(s) must
|
207
|
+
# already be switched on for this to work, if not deferred.
|
208
|
+
def bri=(bri)
|
209
|
+
bri = 0 if bri < 0
|
210
|
+
bri = 255 if bri > 255
|
211
|
+
|
212
|
+
set({ 'bri' => bri.to_i })
|
213
|
+
end
|
214
|
+
|
215
|
+
# The brightness most recently set with #bri=, or the last
|
216
|
+
# brightness received from the light or group due to calling
|
217
|
+
# #update on the target or on the bridge.
|
218
|
+
def bri
|
219
|
+
# TODO: Field storing @api_target or @api_target
|
220
|
+
@info[@api_target]['bri'].to_i
|
221
|
+
end
|
222
|
+
|
223
|
+
# Switches the light or group into color temperature mode and
|
224
|
+
# sets the color temperature of the light in mireds (154-500
|
225
|
+
# inclusive, where 154 is highest temperature (bluer), 500 is
|
226
|
+
# lowest temperature (yellower)). The light(s) must be on for
|
227
|
+
# this to work, if not deferred.
|
228
|
+
def ct=(ct)
|
229
|
+
ct = 154 if ct < 154
|
230
|
+
ct = 500 if ct > 500
|
231
|
+
|
232
|
+
set({ 'ct' => ct.to_i, 'colormode' => 'ct' })
|
233
|
+
end
|
234
|
+
|
235
|
+
# The color temperature most recently set with ct=, or the last
|
236
|
+
# color temperature received from the light due to calling
|
237
|
+
# #update on the light or on the bridge.
|
238
|
+
def ct
|
239
|
+
@info[@api_target]['ct'].to_i
|
240
|
+
end
|
241
|
+
|
242
|
+
# Switches the light or group into CIE XYZ color mode and sets
|
243
|
+
# the X color coordinate to the given floating point value
|
244
|
+
# between 0 and 1, inclusive. Lights must be on for this to
|
245
|
+
# work.
|
246
|
+
def x=(x)
|
247
|
+
self.xy = [ x, @info[@api_target]['xy'][1] ]
|
248
|
+
end
|
249
|
+
|
250
|
+
# The X color coordinate most recently set with #x= or #xy=, or
|
251
|
+
# the last X color coordinate received from the light or group.
|
252
|
+
def x
|
253
|
+
@info[@api_target]['xy'][0].to_f
|
254
|
+
end
|
255
|
+
|
256
|
+
# Switches the light or group into CIE XYZ color mode and sets
|
257
|
+
# the Y color coordinate to the given floating point value
|
258
|
+
# between 0 and 1, inclusive. Lights must be on for this to
|
259
|
+
# work.
|
260
|
+
def y=(y)
|
261
|
+
self.xy = [ @info[@api_target]['xy'][0], y ]
|
262
|
+
end
|
263
|
+
|
264
|
+
# The Y color coordinate most recently set with #y= or #xy=, or
|
265
|
+
# the last Y color coordinate received from the light or group.
|
266
|
+
def y
|
267
|
+
@info[@api_target]['xy'][1].to_f
|
268
|
+
end
|
269
|
+
|
270
|
+
# Switches the light or group into CIE XYZ color mode and sets
|
271
|
+
# the XY color coordinates to the given two-element array of
|
272
|
+
# floating point values between 0 and 1, inclusive. Lights
|
273
|
+
# must be on for this to work, if not deferred.
|
274
|
+
def xy=(xy)
|
275
|
+
unless xy.is_a?(Array) && xy.length == 2 && xy[0].is_a?(Numeric) && xy[1].is_a?(Numeric)
|
276
|
+
raise 'Pass a two-element array of numbers to xy=.'
|
277
|
+
end
|
278
|
+
|
279
|
+
xy[0] = 0 if xy[0] < 0
|
280
|
+
xy[0] = 1 if xy[0] > 1
|
281
|
+
xy[1] = 0 if xy[1] < 0
|
282
|
+
xy[1] = 1 if xy[1] > 1
|
283
|
+
|
284
|
+
set({ 'xy' => xy, 'colormode' => 'xy' })
|
285
|
+
end
|
286
|
+
|
287
|
+
# The XY color coordinates most recently set with #x=, #y=, or
|
288
|
+
# #xy=, or the last color coordinates received from the light
|
289
|
+
# or group due to calling #update on the target or the bridge.
|
290
|
+
def xy
|
291
|
+
xy = @info[@api_target]['xy']
|
292
|
+
[ xy[0].to_f, xy[1].to_f ]
|
293
|
+
end
|
294
|
+
|
295
|
+
# Switches the light or group into hue/saturation mode and sets
|
296
|
+
# the hue to the given value (floating point degrees, wrapped
|
297
|
+
# to 0-360). The light(s) must already be on for this to work.
|
298
|
+
def hue=(hue)
|
299
|
+
hue = (hue * 65536 / 360).to_i & 65535
|
300
|
+
set({ 'hue' => hue, 'colormode' => 'hs' })
|
301
|
+
end
|
302
|
+
|
303
|
+
# The hue most recently set with #hue=, or the last hue
|
304
|
+
# received from the light or group due to calling #update on
|
305
|
+
# the target or on the bridge.
|
306
|
+
def hue
|
307
|
+
@info[@api_target]['hue'].to_i * 360 / 65536.0
|
308
|
+
end
|
309
|
+
|
310
|
+
# Switches the light into hue/saturation mode and sets the
|
311
|
+
# light's saturation to the given value (0-255 inclusive).
|
312
|
+
def sat=(sat)
|
313
|
+
sat = 0 if sat < 0
|
314
|
+
sat = 255 if sat > 255
|
315
|
+
|
316
|
+
set({ 'sat' => sat.to_i, 'colormode' => 'hs' })
|
317
|
+
end
|
318
|
+
|
319
|
+
# The saturation most recently set with #saturation=, or the
|
320
|
+
# last saturation received from the light due to calling
|
321
|
+
# #update on the light or on the bridge.
|
322
|
+
def sat
|
323
|
+
@info[@api_target]['sat'].to_i
|
324
|
+
end
|
325
|
+
|
326
|
+
# Sets the light or group's special effect mode (either 'none'
|
327
|
+
# or 'colorloop').
|
328
|
+
def effect= effect
|
329
|
+
effect = 'none' unless effect == 'colorloop'
|
330
|
+
set({ 'effect' => effect })
|
331
|
+
end
|
332
|
+
|
333
|
+
# The light or group's last set special effect mode.
|
334
|
+
def effect
|
335
|
+
@info[@api_target]['effect']
|
336
|
+
end
|
337
|
+
|
338
|
+
# Returns the light or group's current or last set color mode
|
339
|
+
# ('ct' for color temperature, 'hs' for hue/saturation, 'xy'
|
340
|
+
# for CIE XYZ).
|
341
|
+
def colormode
|
342
|
+
@info[@api_target]['colormode']
|
343
|
+
end
|
344
|
+
|
345
|
+
# Sends parameters named in @changes to the bridge. The block,
|
346
|
+
# if given, will be called with true and the response, or false
|
347
|
+
# and an Exception. This should only be called internally or
|
348
|
+
# by the NLHue::Bridge.
|
349
|
+
def send_changes(&block)
|
350
|
+
msg = {}
|
351
|
+
|
352
|
+
@changes.each do |param|
|
353
|
+
case param
|
354
|
+
when 'colormode'
|
355
|
+
case @info[@api_target]['colormode']
|
356
|
+
when 'hs'
|
357
|
+
msg['hue'] = @info[@api_target]['hue'] if @changes.include? 'hue'
|
358
|
+
msg['sat'] = @info[@api_target]['sat'] if @changes.include? 'sat'
|
359
|
+
when 'xy'
|
360
|
+
msg['xy'] = @info[@api_target]['xy']
|
361
|
+
when 'ct'
|
362
|
+
msg['ct'] = @info[@api_target]['ct']
|
363
|
+
end
|
364
|
+
|
365
|
+
when 'bri', 'on', 'alert', 'effect', 'scene'
|
366
|
+
msg[param] = @info[@api_target][param]
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
msg['transitiontime'] = @transitiontime if @transitiontime
|
371
|
+
|
372
|
+
put_target(msg) do |status, result|
|
373
|
+
rmsg = result.to_s
|
374
|
+
# TODO: Parse individual parameters' error messages? Example:
|
375
|
+
# [{"error":{"type":6,"address":"/lights/2/state/zfhue","description":"parameter, zfhue, not available"}},{"success":{"/lights/2/state/transitiontime":0}}]
|
376
|
+
@changes.delete('alert') if rmsg.include? 'Device is set to off'
|
377
|
+
@changes.clear if status || rmsg =~ /(invalid value|not available)/
|
378
|
+
yield status, result if block_given?
|
379
|
+
end
|
380
|
+
|
381
|
+
@transitiontime = nil
|
382
|
+
end
|
383
|
+
|
384
|
+
private
|
385
|
+
# Sets one or more parameters on the local light, then sends
|
386
|
+
# them to the bridge (unless defer was called).
|
387
|
+
def set(params)
|
388
|
+
params.each do |k, v|
|
389
|
+
@changes << k
|
390
|
+
@info[@api_target][k] = v
|
391
|
+
end
|
392
|
+
|
393
|
+
send_changes unless @defer
|
394
|
+
end
|
395
|
+
|
396
|
+
# PUTs the given Hash or Array, converted to JSON, to this
|
397
|
+
# light or group's API endpoint. The given block will be
|
398
|
+
# called as described for NLHue::Bridge#put_api().
|
399
|
+
def put_target(msg, &block)
|
400
|
+
unless msg.is_a?(Hash) || msg.is_a?(Array)
|
401
|
+
raise "Message to PUT must be a Hash or an Array, not #{msg.class.inspect}."
|
402
|
+
end
|
403
|
+
|
404
|
+
api_path = "/#{@api_category}/#{@id}/#{@api_target}"
|
405
|
+
@bridge.put_api api_path, msg.to_json, @api_category do |response|
|
406
|
+
status, result = @bridge.check_json response
|
407
|
+
yield status, result if block_given?
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
data/lib/nlhue.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Nitrogen Logic's Ruby interface library for the Philips Hue.
|
2
|
+
# (C)2013 Mike Bourgeous
|
3
|
+
|
4
|
+
# Dummy benchmarking method that may be overridden by library users.
|
5
|
+
unless private_methods.include?(:bench)
|
6
|
+
def bench label, *args, &block
|
7
|
+
yield
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Logging method that may be overridden by library users.
|
12
|
+
unless private_methods.include?(:log)
|
13
|
+
def log msg
|
14
|
+
puts msg
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Exception logging method that may be overridden by library users.
|
19
|
+
unless private_methods.include?(:log_e)
|
20
|
+
def log_e e, msg=nil
|
21
|
+
e ||= StandardError.new('No exception given to log')
|
22
|
+
if msg
|
23
|
+
puts "#{msg}: #{e}", e.backtrace
|
24
|
+
else
|
25
|
+
puts e, e.backtrace
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require_relative 'nlhue/ssdp'
|
31
|
+
require_relative 'nlhue/disco'
|
32
|
+
require_relative 'nlhue/bridge'
|
33
|
+
require_relative 'nlhue/target'
|
34
|
+
require_relative 'nlhue/light'
|
35
|
+
require_relative 'nlhue/group'
|
36
|
+
require_relative 'nlhue/scene'
|
data/nlhue.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'nlhue/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "nlhue"
|
8
|
+
spec.version = NLHue::VERSION
|
9
|
+
spec.authors = ["Mike Bourgeous"]
|
10
|
+
spec.email = ["mike@nitrogenlogic.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{An EventMachine-based library for interfacing with the Philips Hue lighting system.}
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = "https://github.com/nitrogenlogic/nlhue"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency 'pry'
|
24
|
+
spec.add_development_dependency 'pry-byebug'
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'eventmachine', '~> 1.0'
|
27
|
+
spec.add_runtime_dependency 'UPnP', '~> 1.2'
|
28
|
+
end
|